Skip to content

Commit

Permalink
Refactor file structure, add octicon
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurClemens committed Aug 28, 2022
1 parent 6b02112 commit 79a2568
Show file tree
Hide file tree
Showing 14 changed files with 5,577 additions and 94 deletions.
125 changes: 100 additions & 25 deletions lib/components/pagination/pagination.ex → lib/components.ex
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
defmodule PrimerLive.Components.Pagination do
defmodule PrimerLive.Components do
use Phoenix.Component
use Phoenix.HTML

alias PrimerLive.Helpers.{Schema, Classes}
alias PrimerLive.Components.Pagination.Options
alias PrimerLive.Options

# ------------------------------------------------------------------------------------
# pagination
# ------------------------------------------------------------------------------------

@doc ~S"""
Creates a control to navigate search results.
use PrimerLive
...
<.pagination
page_count={@page_count}
current_page={@current_page}
link_path={fn page_num -> "/page/#{page_num}" end}
/>
### Example
```
<.pagination
page_count={@page_count}
current_page={@current_page}
link_path={fn page_num -> "/page/#{page_num}" end}
/>
```
### Features
- Configure the page number ranges for siblings and both ends
- Optionally disable page number display (minimal UI)
- Custom labels
- Custom classnames for all elements
### Options
- `PrimerLive.Options.Pagination`
- Additional HTML attributes to be passed to the outer HTML element
Values to pass through `assigns`:
- Component options defined in `PrimerLive.Components.Pagination.Options`
- Additional HTML attributes
### Reference
Primer reference: https://primer.style/css/components/pagination
- [Primer/CSS Pagination](https://primer.style/css/components/pagination)
"""

def pagination(assigns) do
option_names = Schema.get_keys(Options)
option_names = Schema.get_keys(Options.Pagination)

with {:ok, options} <- Options.parse(assigns) do
with {:ok, options} <- Options.Pagination.parse(assigns) do
assigns =
assigns
|> assign(options |> Map.from_struct())
Expand All @@ -54,8 +69,8 @@ defmodule PrimerLive.Components.Pagination do
%{
current_page: current_page,
page_count: page_count,
far_end_page_link_count: far_end_page_link_count,
surrounding_page_link_count: surrounding_page_link_count,
boundary_count: boundary_count,
sibling_count: sibling_count,
class: class,
classes: input_classes
} = assigns
Expand Down Expand Up @@ -102,12 +117,12 @@ defmodule PrimerLive.Components.Pagination do
get_pagination_elements(
page_count,
current_page,
far_end_page_link_count,
surrounding_page_link_count
boundary_count,
sibling_count
)

~H"""
<nav class={classes.pagination_container} aria-label={@labels.aria_label_container}>
<nav class={classes.pagination_container} {@extra} aria-label={@labels.aria_label_container}>
<div class={classes.pagination}>
<%= if show_prev_next do %>
<%= if has_previous_page do %>
Expand Down Expand Up @@ -162,19 +177,19 @@ defmodule PrimerLive.Components.Pagination do
defp get_pagination_elements(
page_count,
current_page,
far_end_page_link_count,
surrounding_page_link_count
boundary_count,
sibling_count
) do
list = 1..page_count

# Create list slices for each part, e.g. [1,2] and [5,6,7,8,9] and [99,100]
section_start = Enum.take(list, far_end_page_link_count)
section_end = Enum.take(list, -far_end_page_link_count)
section_start = Enum.take(list, boundary_count)
section_end = Enum.take(list, -boundary_count)

section_middle =
Enum.slice(
list,
(current_page - surrounding_page_link_count)..(current_page + surrounding_page_link_count)
(current_page - sibling_count)..(current_page + sibling_count)
)

# Join the parts, make sure the numbers a unique, and loop over the result to insert a '0' whenever
Expand All @@ -201,4 +216,64 @@ defmodule PrimerLive.Components.Pagination do
end)
|> Enum.reverse()
end

# ------------------------------------------------------------------------------------
# octicon
# ------------------------------------------------------------------------------------

@doc ~S"""
Renders an icon from the set of GitHub icons.
### Example
```
<.octicon name="arrow-left-24" />
```
### Options
- `PrimerLive.Options.Octicon`
- Additional HTML attributes to be passed to the SVG element
### Reference
- [List of icons](https://primer.style/octicons/)
- [Primer/Octicons Usage](https://primer.style/octicons/guidelines/usage)
"""
def octicon(assigns) do
option_names = Schema.get_keys(Options.Octicon)

with {:ok, options} <- Options.Octicon.parse(assigns) do
class =
Classes.join_classnames([
"octicon",
assigns[:class]
])

assigns =
assigns
|> assign(options |> Map.from_struct())
|> assign(:class, class)
|> assign(:extra, assigns_to_attributes(assigns, option_names))

icon_fn = PrimerLive.Octicons.name_to_function() |> Map.get(assigns.name)

case is_function(icon_fn) do
true -> icon_fn.(assigns)
false -> render_no_icon_error_message(assigns)
end
else
{:error, changeset} ->
Schema.show_errors(changeset, "octicon")
end
end

defp render_no_icon_error_message(assigns) do
~H"""
<Schema.error_message component_name="octicon">
<p>name <%= @name %> does not exist</p>
</Schema.error_message>
"""
end
end
50 changes: 50 additions & 0 deletions lib/helpers/octicons.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule PrimerLive.Helpers.Octicons do
@moduledoc false

require EEx

@priv_path "priv/octicon_builder"
@module_path "lib/octicons.ex"

EEx.function_from_file(
:defp,
:module_wrapper,
"#{@priv_path}/PrimerLive.Octicons.template.eex",
[:icons]
)

def build() do
with {:ok, filenames} <- File.ls("#{@priv_path}/icons") do
with true <- Enum.count(filenames) > 0 do
else
_ ->
IO.inspect("Could not proceed: directory '#{@priv_path}/icons' is empty.")
end

icons =
filenames
|> Enum.map(
&%{
svg:
File.read!("#{@priv_path}/icons/#{&1}")
|> String.replace(~r/\<svg/, "<svg class={@class} {@extra}"),
name: &1 |> String.replace(".svg", ""),
function_name: &1 |> String.replace(".svg", "") |> String.replace("-", "_")
}
)

result = module_wrapper(icons)

with :ok <- File.write(@module_path, result) do
IO.puts("PrimerLive.Octicons module written")
else
error ->
IO.puts("Error writing PrimerLive.Octicons module")
error
end
else
_error ->
IO.inspect("Could not read directory '#{@priv_path}/icons'. Does this directory exist?")
end
end
end
19 changes: 14 additions & 5 deletions lib/helpers/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ defmodule PrimerLive.Helpers.Schema do
## Examples
iex> PrimerLive.Helpers.Schema.get_keys(PrimerLive.Components.Pagination.Options)
[:class, :classes, :current_page, :far_end_page_link_count, :is_numbered, :labels, :link_options, :link_path, :page_count, :surrounding_page_link_count]
iex> PrimerLive.Helpers.Schema.get_keys(PrimerLive.Options.Pagination)
[:boundary_count, :class, :classes, :current_page, :is_numbered, :labels, :link_options, :link_path, :page_count, :sibling_count]
"""
def get_keys(module) do
module.__struct__()
Expand All @@ -24,7 +24,7 @@ defmodule PrimerLive.Helpers.Schema do
iex> import Phoenix.LiveViewTest, only: [rendered_to_string: 1]
iex> import PrimerLive.Helpers.TestHelpers, only: [format_html: 1]
iex> %PrimerLive.Components.Pagination.Options{page_count: 1, current_page: 1, link_path: nil} |> PrimerLive.Components.Pagination.Options.changeset() |> PrimerLive.Helpers.Schema.show_errors("Pagination") |> rendered_to_string() |> format_html()
iex> %PrimerLive.Options.Pagination{page_count: 1, current_page: 1, link_path: nil} |> PrimerLive.Options.Pagination.changeset() |> PrimerLive.Helpers.Schema.show_errors("Pagination") |> rendered_to_string() |> format_html()
"<div class=\"flash flash-error\"><p>Pagination component received invalid options:</p><p>link_path: can&#39;t be blank</p></div>"
"""
def show_errors(changeset, component_name) do
Expand All @@ -43,13 +43,22 @@ defmodule PrimerLive.Helpers.Schema do
end)

~H"""
<div class="flash flash-error">
<p><%= assigns.component_name %> component received invalid options:</p>
<.error_message component_name={@component_name}>
<%= for {option_name, messages} <- errors do %>
<%= for message <- messages do %>
<p><%= option_name %>: <%= message %></p>
<% end %>
<% end %>
</.error_message>
"""
end

@doc false
def error_message(assigns) do
~H"""
<div class="flash flash-error">
<p><%= @component_name %> component received invalid options:</p>
<%= render_slot(@inner_block) %>
</div>
"""
end
Expand Down
Loading

0 comments on commit 79a2568

Please sign in to comment.