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

feat: dropdown component #483

Merged
merged 16 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
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
27 changes: 19 additions & 8 deletions lib/atomic_web/components/calendar/calendar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule AtomicWeb.Components.Calendar do
import AtomicWeb.CalendarUtils
import AtomicWeb.Components.CalendarMonth
import AtomicWeb.Components.CalendarWeek
import AtomicWeb.Components.Dropdown

alias Timex.Duration

Expand Down Expand Up @@ -85,14 +86,24 @@ defmodule AtomicWeb.Components.Calendar do
</div>
<div class="hidden md:ml-4 md:flex md:items-center">
<div class="relative">
<a @click="mode_view = !mode_view" class="flex cursor-pointer items-center py-2 pr-2 pl-3 text-sm font-medium text-zinc-700 hover:bg-zinc-50" id="menu-button" aria-expanded="false" aria-haspopup="true">
<%= if @mode == "month" do
gettext("Month view")
else
gettext("Week view")
end %>
<.icon name={:chevron_down} solid class="ml-2 h-5 w-5 text-zinc-400" />
</a>
<.dropdown
id="calendar-dropdown"
orientation={:down}
items={[
%{name: gettext("Week view"), link: @current_week_path},
%{name: gettext("Month view"), link: @current_month_path}
]}
>
<:wrapper>
<.button color={:white} icon={:chevron_down} icon_position={:right} icon_variant={:solid}>
<%= if @mode == "month" do %>
<%= gettext("Month view") %>
<% else %>
<%= gettext("Week view") %>
<% end %>
</.button>
</:wrapper>
</.dropdown>

<div x-bind:class="mode_view ?'block' : 'hidden'" class="absolute right-0 mt-3 w-36 origin-top-right overflow-hidden rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div class="py-1" role="none">
Expand Down
45 changes: 45 additions & 0 deletions lib/atomic_web/components/dropdown.ex
MarioRodrigues10 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule AtomicWeb.Components.Dropdown do
@moduledoc false
use Phoenix.Component

import AtomicWeb.Components.Icon
alias Phoenix.LiveView.JS

attr :id, :string, required: true, doc: "The id of the dropdown."

attr :items, :list, default: [], doc: "The items to display in the dropdown."

attr :icon_variant, :atom,
default: :outline,
values: [:solid, :outline, :mini],
doc: "The icon variation to display."

attr :orientation, :atom,
default: :down,
doc: "The orientation of the dropdown.",
values: [:down, :up]

slot :wrapper,
required: true,
doc: "Slot to be rendered as a wrapper of the dropdown. For example, a custom button."

def dropdown(assigns) do
~H"""
<div class="relative inline-block text-left" phx-click={JS.toggle(to: "##{@id}", in: "block", out: "hidden")} phx-click-away={JS.hide(to: "##{@id}")}>
<%= render_slot(@wrapper) %>
<div id={@id} class={"#{if @orientation == :down, do: "origin-top-right top-full", else: "origin-bottom-right bottom-full"} absolute right-0 z-10 mt-2 hidden w-56 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5"}>
<div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
<%= for item <- @items do %>
<a href={item.link} class="block flex items-center gap-x-2 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">
<%= if item[:icon] do %>
<.icon solid={@icon_variant == :solid} mini={@icon_variant == :mini} name={item.icon} class="ml-2 inline-block h-5 w-5" />
<% end %>
<%= item.name %>
</a>
<% end %>
</div>
</div>
</div>
"""
end
end
1 change: 1 addition & 0 deletions lib/atomic_web/live/membership_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule AtomicWeb.MembershipLive.Index do
import AtomicWeb.Helpers
import AtomicWeb.Components.Pagination
import AtomicWeb.Components.Table
import AtomicWeb.Components.Dropdown
MarioRodrigues10 marked this conversation as resolved.
Show resolved Hide resolved

alias Atomic.Organizations

Expand Down
23 changes: 13 additions & 10 deletions lib/atomic_web/live/membership_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@
<div x-data="{ menu: false }" class="flex w-100 flex-row justify-end">
<!-- Export option dropdown -->
<div class="relative block font-semibold">
<button
type="button"
class="group flex border-2 rounded-md bg-white border-orange-500 py-2 px-3.5 text-sm font-medium text-orange-500 shadow-sm hover:bg-orange-600 hover:text-white"
<.dropdown
orientation={:down}
id="export-menu-button"
aria-expanded="false"
aria-haspopup="true"
@click="menu = ! menu"
@click.away="menu = false"
@keydown.escape="menu = false"
icon_variant={:solid}
items={[
%{name: "Export to CSV", link: Routes.data_export_path(@socket, :export_memberships_csv, @params["organization_id"]), icon: :queue_list},
%{name: "Export to Excel", link: Routes.data_export_path(@socket, :export_memberships_xlsx, @params["organization_id"]), icon: :table_cells}
]}
>
<.icon name={:cloud_arrow_down} solid class="mr-2 -ml-1 w-5 h-5 text-orange-500 group-hover:text-white" /> Export Memberships
</button>
<:wrapper>
<.button color={:primary} variant={:inverted} class="group">
<.icon name={:cloud_arrow_down} solid class="mr-2 -ml-1 w-5 h-5 text-orange-500 group-hover:text-white" /> Export Memberships
</.button>
</:wrapper>
</.dropdown>
<div
class="absolute right-0 z-10 mt-2.5 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-zinc-900/5 focus:outline-none"
role="menu"
Expand Down
132 changes: 57 additions & 75 deletions lib/atomic_web/templates/layout/_live_navbar.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -124,48 +124,38 @@
</li>
<% end %>
<!-- Profile menu or "Sign in" -->
<li class="-mx-6 mt-auto -mb-2" @click="menu = ! menu">
<li class="-mx-6 mt-auto -mb-2 left-1/4" @click="menu = ! menu">
<span class="sr-only">Open user menu</span>
<button class="flex items-center gap-x-4 px-6 py-3 text-sm font-semibold leading-6 text-zinc-700 select-none">
<AtomicWeb.Components.Avatar.avatar
name={@current_user.name}
src={
if @current_user.profile_picture do
Uploaders.ProfilePicture.url({@current_user, @current_user.profile_picture}, :original)
else
nil
end
}
size={:xs}
color={:light_gray}
class="!text-sm"
/>
<span class="hidden lg:flex lg:items-center">
<span class="text-sm font-semibold leading-6 text-zinc-900" aria-hidden="true"><%= @current_user.name %></span>
<svg class="-rotate-90 ml-2 h-5 w-5 text-zinc-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</span>
<div
class="absolute right-0 z-10 w-32 mb-16 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-zinc-900/5 focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu-button"
tabindex="-1"
x-show="menu"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
@click.away="menu = false"
@keydown.escape="menu = false"
>
<%= link("Your profile", to: Routes.profile_show_path(@socket, :show, @current_user), class: "block px-3 py-1 text-sm leading-6 text-zinc-900 hover:bg-zinc-50") %>
<%= link("Sign out", to: Routes.user_session_path(@socket, :delete), method: :delete, class: "block px-3 py-1 text-sm leading-6 text-zinc-900 hover:bg-zinc-50") %>
</div>
</button>
<AtomicWeb.Components.Dropdown.dropdown
orientation={:top}
items={[
%{name: "Your profile", link: Routes.profile_show_path(@socket, :show, @current_user)},
%{name: "Sign out", link: Routes.user_session_path(@socket, :delete)}
]}
id="user-menu-button"
>
<:wrapper>
<button class="flex items-center gap-x-4 px-12 py-3 text-sm font-semibold leading-6 text-zinc-700 select-none">
<AtomicWeb.Components.Avatar.avatar
name={@current_user.name}
src={
if @current_user.profile_picture do
Uploaders.ProfilePicture.url({@current_user, @current_user.profile_picture}, :original)
else
nil
end
}
size={:xs}
color={:light_gray}
class="!text-sm"
/>
<span class="hidden lg:flex lg:items-center">
<span class="text-sm font-semibold leading-6 text-zinc-900" aria-hidden="true"><%= @current_user.name %></span>
<.icon name={:chevron_right} solid class="h-5 w-5 text-zinc-400" />
</span>
</button>
</:wrapper>
</AtomicWeb.Components.Dropdown.dropdown>
</li>
<% else %>
<div class="border-b border-zinc-200 mt-auto"></div>
Expand All @@ -192,41 +182,33 @@
<%= if @is_authenticated? do %>
<!-- Profile menu or "Sign in" -->
<div class="relative block lg:hidden font-semibold">
<button type="button" class="-m-1.5 flex items-center p-1.5" id="user-menu-button" aria-expanded="false" aria-haspopup="true" @click="menu = ! menu" @click.away="menu = false" @keydown.escape="menu = false">
<span class="sr-only">Open user menu</span>
<AtomicWeb.Components.Avatar.avatar
name={@current_user.name}
src={
if @current_user.profile_picture do
Uploaders.ProfilePicture.url({@current_user, @current_user.profile_picture}, :original)
else
nil
end
}
size={:xs}
color={:light_gray}
class="!text-[16px]"
/>
</button>
<div
class="absolute right-0 z-10 mt-2.5 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-zinc-900/5 focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu-button"
tabindex="-1"
x-show="menu"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
@click.away="menu = false"
@keydown.escape="menu = false"
<AtomicWeb.Components.Dropdown.dropdown
orientation={:down}
items={[
%{name: "Your profile", link: Routes.profile_show_path(@socket, :show, @current_user)},
%{name: "Sign out", link: Routes.user_session_path(@socket, :delete)}
]}
id="user-dropdown-menu"
>
<%= link("Your profile", to: Routes.profile_show_path(@socket, :show, @current_user), class: "block px-3 py-1 text-sm leading-6 text-zinc-900 hover:bg-zinc-50") %>
<%= link("Sign out", to: Routes.user_session_path(@socket, :delete), method: :delete, class: "block px-3 py-1 text-sm leading-6 text-zinc-900 hover:bg-zinc-50") %>
</div>
<:wrapper>
<button type="button" class="-m-1.5 flex items-center p-1.5" id="user-menu-button" aria-expanded="false" aria-haspopup="true" @click="menu = ! menu" @click.away="menu = false" @keydown.escape="menu = false">
<span class="sr-only">Open user menu</span>
<AtomicWeb.Components.Avatar.avatar
name={@current_user.name}
src={
if @current_user.profile_picture do
Uploaders.ProfilePicture.url({@current_user, @current_user.profile_picture}, :original)
else
nil
end
}
size={:xs}
color={:light_gray}
class="!text-[16px]"
/>
</button>
</:wrapper>
</AtomicWeb.Components.Dropdown.dropdown>
</div>
<% else %>
<div class="flex lg:hidden flex-1 justify-end">
Expand Down
121 changes: 121 additions & 0 deletions storybook/components/dropdown.story.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
defmodule AtomicWeb.Storybook.Components.Dropdown do
use PhoenixStorybook.Story, :component

alias AtomicWeb.Components.Dropdown

def function, do: &Dropdown.dropdown/1

def variations do
[
%Variation{
id: :default,
attributes: %{
id: "dropdown",
items: [
%{name: "Profile", link: "#"},
%{name: "Settings", link: "#"},
%{name: "Logout", link: "#"}
],
orientation: :down
},
slots: [
"""
<:wrapper>
<button class="bg-blue-500 text-white px-4 py-2 rounded-md">Button</button>
</:wrapper>
"""
]
},
%VariationGroup{
id: :orientation,
description: "Orientation",
variations: [
%Variation{
id: :button,
attributes: %{
id: "dropdown-down",
items: [
%{name: "Profile", link: "#"},
%{name: "Settings", link: "#"},
%{name: "Logout", link: "#"}
],
orientation: :down
},
slots: [
"""
<:wrapper>
<button class="bg-blue-500 text-white px-4 py-2 rounded-md">Button Down</button>
</:wrapper>
"""
]
},
%Variation{
id: :top,
attributes: %{
id: "dropdown-top",
items: [
%{name: "Profile", link: "#"},
%{name: "Settings", link: "#"},
%{name: "Logout", link: "#"}
],
orientation: :top
},
slots: [
"""
<:wrapper>
<button class="bg-blue-500 text-white px-4 py-2 rounded-md">Button Top</button>
</:wrapper>
"""
]
}
]
},
%VariationGroup{
id: :icons,
description: "Icons",
variations: [
%Variation{
id: :button,
attributes: %{
id: "dropdown-solid-icons",
icon_variant: :solid,
items: [
%{name: "Profile", link: "#", icon: :user},
%{name: "Settings", link: "#", icon: :cog},
%{name: "Logout", link: "#", icon: :arrow_left_on_rectangle}
],
orientation: :down
},
slots: [
"""
<:wrapper>
<button class="bg-blue-500 text-white px-4 py-2 rounded-md">Dropdown with Solid Icons</button>
</:wrapper>
"""
]
},
%Variation{
id: :top,
attributes: %{
id: "dropdown-outline-icons",
icon_variant: :outline,
items: [
%{name: "Profile", link: "#", icon: :user},
%{name: "Settings", link: "#", icon: :cog},
%{name: "Logout", link: "#", icon: :arrow_left_on_rectangle}
],
orientation: :top
},
slots: [
"""
<:wrapper>
<button class="bg-blue-500 text-white px-4 py-2 rounded-md">Dropdown with Outline Icons</button>
</:wrapper>
"""
]
}
]
}
]
end
end
Loading