Skip to content

Commit

Permalink
add date and time components
Browse files Browse the repository at this point in the history
  • Loading branch information
woylie committed Dec 6, 2023
1 parent 1162c9f commit 14b7a6d
Showing 1 changed file with 311 additions and 0 deletions.
311 changes: 311 additions & 0 deletions lib/doggo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,317 @@ defmodule Doggo do
"""
end

@doc """
Renders a `DateTime` or `NaiveDateTime` in a `<time>` tag.
## Examples
By default, the given value is formatted for display with `to_string/1`. This:
<.datetime value={~U[2023-02-05 12:22:06.003Z]} />
Will be rendered as:
<time datetime="2023-02-05T12:22:06.003Z">
2023-02-05 12:22:06.003Z
</time>
You can also pass a custom formatter function. For example, if you are using
[ex_cldr_dates_times](https://hex.pm/packages/ex_cldr_dates_times) in your
application, you could do this:
<.datetime
value={~U[2023-02-05 14:22:06.003Z]}
formatter={&MyApp.Cldr.DateTime.to_string!/1}
/>
Which, depending on your locale, may be rendered as:
<time datetime="2023-02-05T14:22:06.003Z">
Feb 2, 2023, 14:22:06 PM
</time>
"""

attr :value, :any,
required: true,
doc: """
Either a `DateTime` or `NaiveDateTime`.
"""

attr :formatter, :any,
doc: """
A function that takes a `DateTime` or a `NaiveDateTime` as an argument and
returns the value formatted for display. Defaults to `to_string/1`.
"""

attr :title_formatter, :any,
default: nil,
doc: """
When provided, this function is used to format the date time value for the
`title` attribute. If the attribute is not set, no `title` attribute will
be added.
"""

attr :precision, :atom,
values: [:minute, :second, :millisecond, :microsecond, nil],
default: nil,
doc: """
Precision to truncate the given value with. The truncation is applied on
both the display value and the value of the `datetime` attribute.
"""

attr :timezone, :string,
default: nil,
doc: """
If set and the given value is a `DateTime`, the value will be shifted to
that time zone. This affects both the display value and the `datetime` tag.
Note that you need to
[configure a time zone database](https://hexdocs.pm/elixir/DateTime.html#module-time-zone-database)
for this to work.
"""

def datetime(
%{value: value, precision: precision, timezone: timezone} = assigns
) do
value =
value
|> shift_zone(timezone)
|> truncate_datetime(precision)

assigns =
assigns
|> assign(:value, value)
|> assign_new(:formatter, fn -> &to_string/1 end)

~H"""
<time
:if={@value}
datetime={datetime_attr(@value)}
title={time_title_attr(@value, @title_formatter)}
>
<%= @formatter.(@value) %>
</time>
"""
end

defp truncate_datetime(nil, _), do: nil
defp truncate_datetime(v, nil), do: v
defp truncate_datetime(v, :minute), do: %{v | second: 0, microsecond: {0, 0}}

defp truncate_datetime(%DateTime{} = dt, precision) do
DateTime.truncate(dt, precision)
end

defp truncate_datetime(%NaiveDateTime{} = dt, precision) do
NaiveDateTime.truncate(dt, precision)
end

defp truncate_datetime(%Time{} = t, precision) do
Time.truncate(t, precision)
end

defp shift_zone(%DateTime{} = dt, tz) when is_binary(tz) do
DateTime.shift_zone!(dt, tz)
end

defp shift_zone(v, _), do: v

defp datetime_attr(%DateTime{} = dt) do
DateTime.to_iso8601(dt)
end

defp datetime_attr(%NaiveDateTime{} = dt) do
NaiveDateTime.to_iso8601(dt)
end

defp time_title_attr(v, nil), do: v
defp time_title_attr(v, fun) when is_function(fun, 1), do: fun.(v)

@doc """
Renders a `Date`, `DateTime`, or `NaiveDateTime` in a `<time>` tag.
## Examples
By default, the given value is formatted for display with `to_string/1`. This:
<.date value={~D[2023-02-05]} />
Will be rendered as:
<time datetime="2023-02-05">
2023-02-05
</time>
You can also pass a custom formatter function. For example, if you are using
[ex_cldr_dates_times](https://hex.pm/packages/ex_cldr_dates_times) in your
application, you could do this:
<.date
value={~D[2023-02-05]}
formatter={&MyApp.Cldr.Date.to_string!/1}
/>
Which, depending on your locale, may be rendered as:
<time datetime="2023-02-05">
Feb 2, 2023
</time>
"""

attr :value, :any,
required: true,
doc: """
Either a `Date`, `DateTime`, or `NaiveDateTime`.
"""

attr :formatter, :any,
doc: """
A function that takes a `Date` as an argument and returns the value
formatted for display. Defaults to `to_string/1`.
"""

attr :title_formatter, :any,
default: nil,
doc: """
When provided, this function is used to format the date value for the
`title` attribute. If the attribute is not set, no `title` attribute will
be added.
"""

attr :timezone, :string,
default: nil,
doc: """
If set and the given value is a `DateTime`, the value will be shifted to
that time zone. This affects both the display value and the `datetime` tag.
Note that you need to
[configure a time zone database](https://hexdocs.pm/elixir/DateTime.html#module-time-zone-database)
for this to work.
"""

def date(%{value: value, timezone: timezone} = assigns) do
value
|> shift_zone(timezone)
|> to_date()

assigns =
assigns
|> assign(:value, value)
|> assign_new(:formatter, fn -> &to_string/1 end)

~H"""
<time
:if={@value}
datetime={Date.to_iso8601(@value)}
title={time_title_attr(@value, @title_formatter)}
>
<%= @formatter.(@value) %>
</time>
"""
end

defp to_date(%Date{} = d), do: d
defp to_date(%DateTime{} = dt), do: DateTime.to_date(dt)
defp to_date(%NaiveDateTime{} = dt), do: NaiveDateTime.to_date(dt)
defp to_date(nil), do: nil

@doc """
Renders a `Time`, `DateTime`, or `NaiveDateTime` in a `<time>` tag.
## Examples
By default, the given value is formatted for display with `to_string/1`. This:
<.time value={~T[12:22:06.003Z]} />
Will be rendered as:
<time datetime="12:22:06.003">
12:22:06.003
</time>
You can also pass a custom formatter function. For example, if you are using
[ex_cldr_dates_times](https://hex.pm/packages/ex_cldr_dates_times) in your
application, you could do this:
<.time
value={~T[12:22:06.003]}
formatter={&MyApp.Cldr.Time.to_string!/1}
/>
Which, depending on your locale, may be rendered as:
<time datetime="14:22:06.003">
14:22:06 PM
</time>
"""

attr :value, :any,
required: true,
doc: """
Either a `Time`, `DateTime`, or `NaiveDateTime`.
"""

attr :formatter, :any,
doc: """
A function that takes a `Time`, `DateTime`, or `NaiveDateTime` as an
argument and returns the value formatted for display. Defaults to
`to_string/1`.
"""

attr :title_formatter, :any,
default: nil,
doc: """
When provided, this function is used to format the time value for the
`title` attribute. If the attribute is not set, no `title` attribute will
be added.
"""

attr :precision, :atom,
values: [:minute, :second, :millisecond, :microsecond, nil],
default: nil,
doc: """
Precision to truncate the given value with. The truncation is applied on
both the display value and the value of the `datetime` attribute.
"""

attr :timezone, :string,
default: nil,
doc: """
If set and the given value is a `DateTime`, the value will be shifted to
that time zone. This affects both the display value and the `datetime` tag.
Note that you need to
[configure a time zone database](https://hexdocs.pm/elixir/DateTime.html#module-time-zone-database)
for this to work.
"""

def time(%{value: value, precision: precision, timezone: timezone} = assigns) do
value =
value
|> shift_zone(timezone)
|> truncate_datetime(precision)
|> to_time()

assigns =
assigns
|> assign(:value, value)
|> assign_new(:formatter, fn -> &to_string/1 end)

~H"""
<time
:if={@value}
datetime={Time.to_iso8601(@value)}
title={time_title_attr(@value, @title_formatter)}
>
<%= @formatter.(@value) %>
</time>
"""
end

defp to_time(%Time{} = t), do: t
defp to_time(%DateTime{} = dt), do: DateTime.to_time(dt)
defp to_time(%NaiveDateTime{} = dt), do: NaiveDateTime.to_time(dt)
defp to_time(nil), do: nil

@doc """
Shows the flash messages as alerts.
Expand Down

0 comments on commit 14b7a6d

Please sign in to comment.