Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add date and time components #112

Merged
merged 3 commits into from
Dec 6, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
323 changes: 317 additions & 6 deletions lib/doggo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ defmodule Doggo do
Submit
</.button>
"""
@doc type: :component

attr :type, :string, values: ["button", "reset", "submit"], default: "button"

attr :variant, :atom,
Expand Down Expand Up @@ -184,12 +186,7 @@ defmodule Doggo do
Submit
</.button>
"""
attr :navigate, :string, default: nil
attr :patch, :string, default: nil
attr :href, :any, default: nil
attr :replace, :boolean, default: false
attr :method, :string, default: "get"
attr :csrf_token, :any, default: true
@doc type: :component

attr :variant, :atom,
values: [:primary, :secondary, :info, :success, :warning, :danger],
Expand Down Expand Up @@ -248,6 +245,320 @@ 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>
"""
@doc type: :component

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>
"""
@doc type: :component

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>
"""
@doc type: :component

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