Skip to content

Commit

Permalink
Merge pull request #83 from bluzky/feature/sidebar
Browse files Browse the repository at this point in the history
WIP Implement sidebar component
  • Loading branch information
bluzky authored Nov 23, 2024
2 parents c0d36d4 + eddcec4 commit 4a67653
Show file tree
Hide file tree
Showing 36 changed files with 1,306 additions and 1,862 deletions.
13 changes: 12 additions & 1 deletion lib/salad_ui.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ defmodule SaladUI do

# alias OrangeCmsWeb.Components.LadUI.LadJS
alias Phoenix.LiveView.JS

defp classes(input) do
SaladUI.Merge.merge(input)
TwMerge.merge(input)
end
end
end
Expand All @@ -23,33 +24,43 @@ defmodule SaladUI do

defmacro __using__(_) do
quote do
import SaladUI.Accordion
import SaladUI.Alert
import SaladUI.AlertDialog
import SaladUI.Avatar
import SaladUI.Badge
import SaladUI.Breadcrumb
import SaladUI.Button
import SaladUI.Card
import SaladUI.Chart
import SaladUI.Checkbox
import SaladUI.Collapsible
import SaladUI.Dialog
import SaladUI.DropdownMenu
import SaladUI.Form
import SaladUI.Helpers
import SaladUI.HoverCard
import SaladUI.Icon
import SaladUI.Input
import SaladUI.Label
import SaladUI.Menu
import SaladUI.Pagination
import SaladUI.Popover
import SaladUI.Progress
import SaladUI.RadioGroup
import SaladUI.ScrollArea
import SaladUI.Select
import SaladUI.Separator
import SaladUI.Sheet
import SaladUI.Sidebar
import SaladUI.Skeleton
import SaladUI.Slider
import SaladUI.Switch
import SaladUI.Table
import SaladUI.Tabs
import SaladUI.Textarea
import SaladUI.Toggle
import SaladUI.ToggleGroup
import SaladUI.Tooltip
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/salad_ui/alert.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ defmodule SaladUI.Alert do
@variants %{
variant: %{
"default" => "bg-background text-foreground",
"destructive" => "bg-background border-destructive/50 text-destructive dark:border-destructive [&>span]:text-destructive"
"destructive" =>
"bg-background border-destructive/50 text-destructive dark:border-destructive [&>span]:text-destructive"
}
}

Expand Down
34 changes: 22 additions & 12 deletions lib/salad_ui/collapsible.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,40 +22,48 @@ defmodule SaladUI.Collapsible do
required: true,
doc: "Id to identify collapsible component, collapsible_trigger uses this id to toggle content visibility"

attr :open, :boolean, default: false, doc: "Initial state of collapsible content"
attr :open, :boolean, default: true, doc: "Initial state of collapsible content"
attr :class, :string, default: nil
attr :rest, :global, include: ~w(title)
slot(:inner_block, required: true)

def collapsible(assigns) do
assigns =
assigns
|> assign(:builder, %{open: assigns[:open], id: assigns[:id]})
|> assign(:open, normalize_boolean(assigns[:open]))
assign(assigns, :open, normalize_boolean(assigns[:open]))

~H"""
<div
phx-toggle-collapsible={toggle_collapsible(@builder)}
data-state="closed"
phx-toggle-collapsible={toggle_collapsible(@id)}
phx-mounted={@open && JS.exec("phx-toggle-collapsible", to: "##{@id}")}
class={classes(["inline-block relative", @class])}
class={classes(["inline-block relative collapsible-root", @class])}
id={@id}
{@rest}
>
<%= render_slot(@inner_block, @builder) %>
<%= render_slot(@inner_block) %>
</div>
"""
end

@doc """
Render trigger for collapsible component.
"""
attr :builder, :map, required: true, doc: "Builder instance for collapsible component"
attr(:class, :string, default: nil)
attr :as_tag, :any, default: "div"
attr :rest, :global
slot(:inner_block, required: true)


def collapsible_trigger(assigns) do
~H"""
<div phx-click={JS.exec("phx-toggle-collapsible", to: "#" <> @builder.id)} class={@class}>
<.dynamic
tag={@as_tag}
onclick={exec_closest("phx-toggle-collapsible", ".collapsible-root")}
class={@class}
{@rest}
>
<%= render_slot(@inner_block) %>
</div>
</.dynamic>
"""
end

Expand Down Expand Up @@ -85,12 +93,14 @@ defmodule SaladUI.Collapsible do
@doc """
Show collapsible content.
"""
def toggle_collapsible(js \\ %JS{}, %{id: id} = _builder) do
JS.toggle(js,
def toggle_collapsible(js \\ %JS{}, id) do
js
|> JS.toggle(
to: "##{id} .collapsible-content",
in: {"ease-out duration-200", "opacity-0", "opacity-100"},
out: {"ease-out", "opacity-100", "opacity-70"},
time: 200
)
|> JS.toggle_attribute({"data-state", "open", "closed"}, to: "##{id}")
end
end
30 changes: 28 additions & 2 deletions lib/salad_ui/dropdown_menu.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,23 @@ defmodule SaladUI.DropdownMenu do
end

attr :class, :string, default: nil
attr :as_tag, :any, default: "div"
slot :inner_block, required: true

attr :rest, :global

def dropdown_menu_trigger(assigns) do
~H"""
<div
<.dynamic
tag={@as_tag}
class={classes(["dropdown-menu-trigger peer", @class])}
data-state="closed"
{@rest}
phx-click={toggle()}
phx-click-away={hide()}
>
<%= render_slot(@inner_block) %>
</div>
</.dynamic>
"""
end

Expand Down Expand Up @@ -94,6 +97,29 @@ defmodule SaladUI.DropdownMenu do
"""
end

@doc """
Render
"""
attr(:class, :string, default: nil)
attr(:rest, :global)
slot(:inner_block, required: true)

def dropdown_menu_shortcut(assigns) do
~H"""
<span
class={
classes([
"ml-auto text-xs tracking-widest opacity-60",
@class
])
}
{@rest}
>
<%= render_slot(@inner_block) %>
</span>
"""
end

defp toggle(js \\ %JS{}) do
JS.toggle_attribute(js, {"data-state", "open", "closed"})
end
Expand Down
176 changes: 176 additions & 0 deletions lib/salad_ui/helpers.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defmodule SaladUI.Helpers do
@moduledoc false
use Phoenix.Component

import Phoenix.Component

@doc """
Expand Down Expand Up @@ -48,6 +50,16 @@ defmodule SaladUI.Helpers do
end
end

@doc """
Normalize id to be used in HTML id attribute
It will replace all non-alphanumeric characters with `-` and downcase the string
"""
def id(id) do
id
|> String.replace(~r/[^a-zA-Z0-9]/, "-")
|> String.downcase()
end

@doc """
Variant helper for generating classes based on side and align
"""
Expand Down Expand Up @@ -123,6 +135,170 @@ defmodule SaladUI.Helpers do
"#{shared_classes} #{variation_classes}"
end

@doc """
Common function for building variant
## Examples
```elixir
config =
%{
variants: %{
variant: %{
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: %{
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
},
},
default_variants: %{
variant: "default",
size: "default",
},
}
class_input = %{variant: "outline", size: "lg"}
variant_class(config, class_input)
```
"""
def variant_class(config, class_input) do
variants = Map.get(config, :variants, %{})
default_variants = Map.get(config, :default_variants, %{})

variants
|> Map.keys()
|> Enum.map(fn variant_key ->
# Get the variant value from input or use default
variant_value =
Map.get(class_input, variant_key) ||
Map.get(default_variants, variant_key)

# Get the variant options map
variant_options = Map.get(variants, variant_key, %{})

# Get the CSS classes for this variant value
Map.get(variant_options, String.to_existing_atom(variant_value))
end)
|> Enum.reject(&is_nil/1)
|> Enum.join(" ")
end

@doc """
This function build css style string from map of css style
## Examples
```elixir
css_style = %{
"background-color": "red",
"color": "white",
"font-size": "16px",
}
style(css_style)
# => "background-color: red; color: white; font-size: 16px;"
```
"""
def style(items) when is_list(items) do
{acc_map, acc_list} =
Enum.reduce(items, {%{}, []}, fn item, {acc_map, acc_list} ->
cond do
is_map(item) ->
{Map.merge(acc_map, item), acc_list}

is_binary(item) ->
{acc_map, [item | acc_list]}

true ->
{acc_map, [item | acc_list]}
end
end)

style = Enum.map_join(acc_map, "; ", fn {k, v} -> "#{k}: #{v}" end) <> ";"
Enum.join([style | acc_list], "; ")
end

@doc """
This function build js script to invoke JS stored in given attribute.
Similar to JS.exec/2 but this function target the nearest ancestor element.
## Examples
```heex
<button click={exec_closest("phx-hide-sheet", ".ancestor_class")}>
Close
</button>
```
"""
def exec_closest(attribute, ancestor_selector) do
"""
var el = this.closest("#{ancestor_selector}"); liveSocket.execJS(el, el.getAttribute("#{attribute}"));
"""
end

@doc """
This component is used to render dynamic tag based on the `tag` attribute. `tag` attribute can be a string or a function component.
This is just a wrapper around `dynamic_tag` function from Phoenix LiveView which only support string tag.
## Examples
```heex
<.dynamic tag={@tag} class="bg-primary text-primary-foreground">
Hello World
</.dynamic>
```
"""
def dynamic(%{tag: name} = assigns) when is_function(name, 1) do
assigns = Map.delete(assigns, :tag)
name.(assigns)
end

def dynamic(assigns) do
name = assigns[:tag] || "div"

assigns =
assigns
|> Map.delete(:tag)
|> assign(:name, name)

dynamic_tag(assigns)
end

@doc """
This component mimic behavior of `asChild` attribute from shadcn/ui.
It works by passing all attribute from `as_child` tag to `tag` function component, add pass `child` attribute to the `as_tag` attribute of the `tag` function component.
The `tag` function component should accept `as_tag` attribute to render the child component.
## Examples
```heex
<.as_child tag={&dropdown_menu_trigger/1} child={&sidebar_menu_button/1} class="bg-primary text-primary-foreground">
Hello World
</.as_child>
```
Normally this can be archieved by using `dropdown_menu_trigger` component directly but this will fire copile warning.
```heex
<.dropdown_menu_trigger as_tag={&sidebar_menu_button/1} class="bg-primary text-primary-foreground">
Hello World
</.dropdown_menu_trigger>
"""
def as_child(%{tag: tag, child: child_tag} = assigns) when is_function(tag, 1) do
assigns
|> Map.drop([:tag, :child])
|> assign(:as_tag, child_tag)
|> tag.()
end

# Translate error message
# borrowed from https://github.com/petalframework/petal_components/blob/main/lib/petal_components/field.ex#L414
defp translate_error({msg, opts}) do
Expand Down
Loading

0 comments on commit 4a67653

Please sign in to comment.