Skip to content

Instantly share code, notes, and snippets.

@supersimple
Created March 21, 2021 18:18
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save supersimple/ca5b91712a74381a5396f134649f155d to your computer and use it in GitHub Desktop.
Save supersimple/ca5b91712a74381a5396f134649f155d to your computer and use it in GitHub Desktop.
Datepicker in PETAL
# index.html.leex
<%= live_component @socket, AppWeb.Live.Components.DatePickerComponent, id: "date_picker", selected_date: @selected_date, calendar_open: @calendar_open %>
# date_picker_component.ex
defmodule AppWeb.Live.Components.DatePickerComponent do
use AppWeb, :live_component
def render(assigns) do
~L"""
<div class="date_picker" x-data="{ calendar_open : <%= @calendar_open %> }">
<h1>Showing games on: <button @click="{ calendar_open ? calendar_open = false : calendar_open = true }"><%= format_selected_date(@calendar.selected_day) %></button></h1>
<div class="calendar" x-show="calendar_open" @click.away="calendar_open = false">
<button class="previous_month" phx-click="change_month" phx-value-month="<%= @calendar.previous_month %>" phx-target="<%= @myself %>"><svg width="5" height="8" viewBox="0 0 5 8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 7L1 4L4 1" stroke="#829AB1" stroke-width="1"/></svg></button>
<h3 class="month"><%= @calendar.selected_month %></h3>
<button class="next_month" phx-click="change_month" phx-value-month="<%= @calendar.next_month %>" phx-target="<%= @myself %>"><svg width="5" height="8" viewBox="0 0 5 8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1L4 4L1 7" stroke="#829AB1" stroke-width="1"/></svg></button>
<ul class="dow"><li>S</li><li>M</li><li>T</li><li>W</li><li>T</li><li>F</li><li>S</li></ul>
<%= for week <- @calendar.days_by_week do %>
<ul class="week">
<%= for date <- week do %>
<%= print_calendar_date(%{date: date, selected_day: @calendar.selected_day, target: @myself}, assigns) %>
<% end %>
</ul>
<% end %>
</div>
</div>
"""
end
def preload(assigns) do
assigns
end
def mount(socket) do
{:ok, socket}
end
def update(assigns, socket) do
socket = assign(socket, :calendar, calendar_info(assigns.selected_date, assigns.selected_date))
{:ok, assign(socket, assigns)}
end
def handle_event("change_month", %{"month" => month}, socket) do
selected_day = get_in(socket.assigns, [:calendar, :selected_day])
socket = assign(socket, calendar: calendar_info(month, selected_day), calendar_open: true)
{:noreply, socket}
end
def handle_event("change_selected_day", %{"date" => date}, socket) do
socket = assign(socket, calendar: calendar_info(date, date), calendar_open: true)
send(self(), {:selected_date, date})
{:noreply, socket}
end
defp calendar_info(month, selected_day) do
month = cast_to_date(month)
selected_day = cast_to_date(selected_day)
%{
selected_day: selected_day,
selected_month: Calendar.strftime(month, "%B"),
previous_month: previous_month(month),
next_month: next_month(month),
days_by_week: days_by_week(month)
}
end
defp previous_month(%{month: 1} = date), do: %{date | year: date.year - 1, month: 12, day: 1}
defp previous_month(%{month: month} = date), do: %{date | month: month - 1, day: 1}
defp next_month(%{month: 12} = date), do: %{date | year: date.year + 1, month: 1, day: 1}
defp next_month(%{month: month} = date), do: %{date | month: month + 1, day: 1}
defp days_by_week(date) do
month_start = Date.beginning_of_month(date)
month_end = Date.end_of_month(date)
offset_start = Date.day_of_week(month_start, :sunday) - 1
offset_end = 7 - Date.day_of_week(month_end, :sunday)
date_list = Date.range(month_start, month_end) |> Enum.map(& &1)
padding_start = for _o <- 1..offset_start, do: nil
padding_end = for _o <- 1..offset_end, do: nil
padded_month_list = padding_start ++ date_list ++ padding_end
Enum.chunk_every(padded_month_list, 7)
end
defp cast_to_date(%Date{} = date), do: date
defp cast_to_date(date), do: Date.from_iso8601!(date)
defp print_calendar_date(%{date: date, selected_day: date, target: _tgt}, assigns) do
~L"""
<li aria-current="date"><button disabled><%= date.day %></button></li>
"""
end
defp print_calendar_date(%{date: nil, selected_day: _selected_day, target: _tgt}, assigns) do
~L"<li></li>"
end
defp print_calendar_date(%{date: date, selected_day: _selected_day, target: tgt}, assigns) do
~L"""
<li><button phx-click="change_selected_day" phx-value-date="<%= date %>" phx-target="<%= tgt %>"><%= date.day %></button></li>
"""
end
defp format_selected_date(date) do
date
|> cast_to_date()
|> Calendar.strftime("%b. %d, %Y")
end
end
# index.ex
defmodule AppWeb.Live.Index do
use AppWeb, :live_view
@impl true
def render(assigns) do
Phoenix.View.render(AppWeb.View, "index.html", assigns)
end
@impl true
def handle_info({:selected_date, date}, socket) do
socket = assign(socket, selected_date: date, calendar_open: true)
{:noreply, socket}
end
...
# app.scss
.date_picker {
@apply relative z-50;
h1 {
@apply text-base leading-6;
button {
@apply text-sb-purple-700 focus:outline-none;
}
}
.calendar {
@apply bg-white rounded-lg p-4 shadow-lg absolute top-6 left-28;
.month {
@apply text-center text-sb-gray-900 text-xs pb-2;
}
.previous_month {
@apply absolute top-5 left-7;
}
.next_month {
@apply absolute top-5 right-7;
}
.dow {
@apply text-xs text-sb-gray-500;
li {
@apply float-left block w-5 h-5 leading-5 text-center;
}
}
.week {
@apply text-sm text-sb-gray-900;
li {
@apply float-left block w-5 h-5 leading-5 text-center rounded-sm;
button {
@apply focus:outline-none
}
&[aria-current="date"]{
@apply bg-sb-purple-100;
}
button:disabled {
@apply opacity-100;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment