Created
January 5, 2021 00:46
-
-
Save humphreyja/6d87a1cbbbb29c8be3c54924b45b3c2a to your computer and use it in GitHub Desktop.
Hotwire Server Rendered Popup Date Picker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<% frame_name = 'calendar_interface' %> | |
<% frame_name = "#{frame_name}_#{@target}" if @target %> | |
<% start_of_month = @display_date.beginning_of_month %> | |
<% cache "ui/calendar/#{frame_name}/#{start_of_month}--#{@selected_date}" do %> | |
<% end_of_month = @display_date.end_of_month %> | |
<% dow_start = @display_date.beginning_of_month.days_to_week_start + 2 %> | |
<% dow_start = dow_start >= 7 ? dow_start - 7 : dow_start %> | |
<% dow_start = dow_start <= 0 ? dow_start + 7 : dow_start %> | |
<% today = Date.today %> | |
<%= turbo_frame_tag frame_name do %> | |
<div class="calendar"> | |
<header> | |
<span class="flex row align-items-center"> | |
<%= link_to calendar_interface_path(selected: @selected_date, show: @display_date - 1.month, target: @target), 'data-turbo-frame': 'calendar_interface', 'data-popup-keepalive': 'true', 'aria-label': "previous month" do %> | |
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="arrow-circle-left" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-arrow-circle-left fa-w-16 fa-5x"><path fill="currentColor" d="M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248-111 248-248 248zm28.9-143.6L209.4 288H392c13.3 0 24-10.7 24-24v-16c0-13.3-10.7-24-24-24H209.4l75.5-72.4c9.7-9.3 9.9-24.8.4-34.3l-11-10.9c-9.4-9.4-24.6-9.4-33.9 0L107.7 239c-9.4 9.4-9.4 24.6 0 33.9l132.7 132.7c9.4 9.4 24.6 9.4 33.9 0l11-10.9c9.5-9.5 9.3-25-.4-34.3z" class=""></path></svg> | |
<% end %> | |
<% if start_of_month != today.beginning_of_month %> | |
<%= link_to calendar_interface_path(selected: @selected_date, show: today, target: @target), 'data-turbo-frame': 'calendar_interface', 'data-popup-keepalive': 'true', 'aria-label': "today" do %> | |
<svg style="height:16px" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="share" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-share fa-w-16 fa-5x"><path fill="currentColor" d="M503.691 189.836L327.687 37.851C312.281 24.546 288 35.347 288 56.015v80.053C127.371 137.907 0 170.1 0 322.326c0 61.441 39.581 122.309 83.333 154.132 13.653 9.931 33.111-2.533 28.077-18.631C66.066 312.814 132.917 274.316 288 272.085V360c0 20.7 24.3 31.453 39.687 18.164l176.004-152c11.071-9.562 11.086-26.753 0-36.328z" class=""></path></svg> | |
<% end %> | |
<% else %> | |
<span style="width:24px"></span> | |
<% end %> | |
</span> | |
<h4> | |
<%= @display_date.strftime("%B %Y") %> | |
</h4> | |
<span class="flex row align-items-center"> | |
<span style="width:24px"></span> | |
<%= link_to calendar_interface_path(selected: @selected_date, show: @display_date + 1.month, target: @target), 'data-turbo-frame': 'calendar_interface', 'data-popup-keepalive': 'true', 'aria-label': "next month" do %> | |
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="arrow-circle-right" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-arrow-circle-right fa-w-16 fa-5x"><path fill="currentColor" d="M256 8c137 0 248 111 248 248S393 504 256 504 8 393 8 256 119 8 256 8zm-28.9 143.6l75.5 72.4H120c-13.3 0-24 10.7-24 24v16c0 13.3 10.7 24 24 24h182.6l-75.5 72.4c-9.7 9.3-9.9 24.8-.4 34.3l11 10.9c9.4 9.4 24.6 9.4 33.9 0L404.3 273c9.4-9.4 9.4-24.6 0-33.9L271.6 106.3c-9.4-9.4-24.6-9.4-33.9 0l-11 10.9c-9.5 9.6-9.3 25.1.4 34.4z" class=""></path></svg> | |
<% end %> | |
</span> | |
</header> | |
<header> | |
<ol> | |
<% ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map do |day_name| %> | |
<li> | |
<%= day_name %> | |
</li> | |
<% end %> | |
</ol> | |
</header> | |
<ol> | |
<% current_day = start_of_month - dow_start.days %> | |
<% 6.times do |week_num| %> | |
<% next if (current_day + 1.day) > end_of_month %> | |
<li> | |
<ol> | |
<% 7.times do |day_num| %> | |
<% current_day += 1.day %> | |
<li data-popup-dismiss="true" data-calendar-day-value="<%= current_day %>" class="<%= current_day.month != @display_date.month ? 'outside' : '' %> <%= current_day == @selected_date ? 'selected' : '' %> <%= current_day == today ? 'today' : '' %>"> | |
<%= current_day.day %> | |
</li> | |
<% end %> | |
</ol> | |
</li> | |
<% end %> | |
</ol> | |
</div> | |
<% end %> | |
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Again I use stimulus to control changing the input, label, and selected but you can do the same with just plain JS | |
import { Controller } from 'stimulus' | |
export default class extends Controller { | |
static get targets() { return ['input', 'link', 'label'] } | |
calendarClicked({ target }) { | |
if (target.dataset.calendarDayValue) { | |
// Set the hidden input value | |
this.inputTarget.value = target.dataset.calendarDayValue | |
// Change the selected value in the turbo frame param | |
this.linkTarget.href = this.linkTarget.href.replace(/selected\=.*\&/, `selected=${target.dataset.calendarDayValue}&`) | |
// you may want to reformat the date before inserting it into the label | |
this.labelTarget.textContent = target.dataset.calendarDayValue | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class InterfacesController < ApplicationController | |
def calendar | |
@selected_date = params[:selected] ? Date.parse(params[:selected]) : Date.today | |
@display_date = params[:show] ? Date.parse(params[:show]) : @selected_date | |
@target = params[:target] # Since we are rendering turbo frames, this is used to add a "_@target" to the frame name for displaying multiple calendars | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// I use stimulus to control clicking the link when the user clicks the details summary but you can do the same with just plain JS | |
import { Controller } from 'stimulus' | |
export default class extends Controller { | |
static get targets() { return ['link'] } | |
initialize() { | |
if (this.hasLinkTarget) this.linkTarget.hidden = true | |
} | |
open() { | |
this.element.open = true; | |
} | |
close() { | |
this.element.open = false; | |
} | |
closeOnClickOutside({ target }) { | |
if (this.element.contains(target)) { | |
// Close if clicking a link in the popup | |
if (target !== this.linkTarget && target.tagName === 'A' && !target.dataset.popupKeepalive) { | |
this.close() | |
} | |
if (target.dataset.popupDismiss) { | |
this.close() | |
} | |
return | |
} | |
this.close() | |
} | |
update() { | |
if (!this.element.open) return | |
if (this.hasLinkTarget) this.linkTarget.click() // clicking the link will load the turboframe | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$day-width: 40px; | |
$day-height: 40px; | |
$popup-bg-color: white; | |
$popup-text-color: black; | |
.calendar { | |
text-align: center; | |
header { | |
display: flex; | |
flex-direction: row; | |
justify-content: space-between; | |
padding: 5px 0; | |
h4 { | |
font-size: 16px; | |
white-space: nowrap; | |
} | |
ol { | |
display: flex; | |
flex-direction: row; | |
opacity: 0.5; | |
li { | |
width: $day-width; | |
} | |
} | |
a { | |
padding: 0 5px; | |
text-decoration: none; | |
svg { | |
height: 20px; | |
} | |
} | |
} | |
ol { | |
list-style: none; | |
margin: 0; | |
padding: 0; | |
} | |
& > ol ol { | |
display: flex; | |
flex-direction: row; | |
li { | |
width: $day-width; | |
height: $day-height; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
cursor: pointer; | |
transition: background-color 0.2s ease; | |
text-shadow: 0px 1px rgba(0, 0, 0, 0.3); | |
&:hover { | |
background-color: rgba(white, 0.07); | |
} | |
&.selected { | |
background-color: rgba(white, 0.2); | |
} | |
&.outside { | |
opacity: 0.6; | |
text-shadow: none; | |
} | |
&.today { | |
position: relative; | |
&::after { | |
position: absolute; | |
content: '•'; | |
top: -1px; | |
font-size: 10px; | |
} | |
} | |
} | |
} | |
} | |
details[data-controller~="popup"] { | |
position: relative; | |
outline: none; | |
z-index: 10; | |
a[data-popup-target~="link"] { | |
display: none; | |
} | |
summary { | |
list-style: none; | |
outline: none; | |
user-select: none; | |
&::-webkit-details-marker { display: none; } | |
} | |
.popup-menu { | |
margin-top: 4px; | |
margin-bottom: 4px; | |
position: absolute; | |
background: $popup-bg-color; | |
color: $popup-text-color; | |
box-shadow: 0 0 42px -10px rgba(black, 0.43); | |
padding: 8px 12px; | |
min-width: 100px; | |
font-size: 16px; | |
&.right { right: 0 } | |
&.left { left: 0 } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<details data-controller="popup input_calendar" data-action="toggle->popup#update click@window->popup#closeOnClickOutside"> | |
<summary data-action="toggle->popup#update"> | |
<h4 data-input_calendar-target="label"><%= my_selected_date %></h4> | |
<%= link_to 'Select Date', calendar_interface_path(selected: my_selected_date.to_date, target: 'MYFORM'), 'data-turbo-frame': 'calendar_interface_MYFORM', 'data-popup-target': 'link', 'data-input_calendar-target': 'link' %> | |
</summary> | |
<%= form.hidden_field :my_selected_date, value: my_selected_date.to_date, 'data-input_calendar-target': 'input' %> | |
<%= turbo_frame_tag "calendar_interface_MYFORM", class: 'popup-menu', 'data-action': 'click->input_calendar#calendarClicked' %> | |
</details> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo of this is located here https://hotwire-components.herokuapp.com