-
-
Notifications
You must be signed in to change notification settings - Fork 10
chore: Migrate Popover Dropdown component #180
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
Open
arnolicious
wants to merge
9
commits into
immich-app:main
Choose a base branch
from
arnolicious:feat/dropdown-component
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
29beece
chore: move popover dropdown component to ui lib and use bits-ui
arnolicious b46280e
fix: remove unused imports
arnolicious 3c91ade
fix: remove lodash dependency, add examples
arnolicious de40187
fix: removed unused variable
arnolicious d364fad
fix: remove lodash types
arnolicious ceeb74b
fix: format
arnolicious 97d0915
fix: lockfile
arnolicious 85f6285
chore: refactor to align with select interface
arnolicious b3af9b5
Merge branch 'main' into feat/dropdown-component
arnolicious File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| <script lang="ts" module> | ||
| import { type SelectItem, type Variants } from '@immich/ui'; | ||
| export type DropdownItem = SelectItem & { | ||
| icon?: string; | ||
| }; | ||
|
|
||
| type T = DropdownItem; | ||
| </script> | ||
|
|
||
| <script lang="ts" generics="T extends DropdownItem"> | ||
| import { Button, type SelectProps } from '@immich/ui'; | ||
| import { mdiCheck } from '@mdi/js'; | ||
| import { Popover } from 'bits-ui'; | ||
| import { fly } from 'svelte/transition'; | ||
| import Icon from '../Icon/Icon.svelte'; | ||
| import type { Snippet } from 'svelte'; | ||
| import Text from '../Text/Text.svelte'; | ||
|
|
||
| type Props = SelectProps<T> & { | ||
| fullWidth?: boolean; | ||
| position?: 'bottom-left' | 'bottom-right'; | ||
| variant?: Variants; | ||
| trigger?: Snippet<[{ props: Record<string, unknown>; selectedIcon?: string | undefined }]>; | ||
| hideTextOnSmallScreen?: boolean; | ||
| }; | ||
|
|
||
| let { | ||
| data, | ||
| class: className, | ||
| color = 'primary', | ||
| size = 'small', | ||
| onChange, | ||
| placeholder, | ||
| shape, | ||
| hideTextOnSmallScreen = true, | ||
| value = $bindable(), | ||
| fullWidth, | ||
| variant, | ||
| position = 'bottom-right', | ||
| trigger, | ||
| }: Props = $props(); | ||
|
|
||
| const handleSelectOption = (option: T) => { | ||
| onChange?.(option); | ||
| value = option; | ||
| showDropdown = false; | ||
| }; | ||
|
|
||
| const asOptions = (items: string[] | T[]) => { | ||
| return items.map((item) => { | ||
| if (typeof item === 'string') { | ||
| return { value: item, label: item } as T; | ||
| } | ||
|
|
||
| const label = item.label ?? item.value; | ||
| return { ...item, label }; | ||
| }); | ||
| }; | ||
|
|
||
| const options = $derived(asOptions(data)); | ||
|
|
||
| let showDropdown = $state(false); | ||
| </script> | ||
|
|
||
| <!-- BUTTON TITLE --> | ||
| <Popover.Root bind:open={showDropdown}> | ||
| <Popover.Trigger> | ||
| {#snippet child({ props })} | ||
| {#if trigger} | ||
| {@render trigger({ props, selectedIcon: value?.icon })} | ||
| {:else} | ||
| <Button {...props} {fullWidth} title={placeholder} {color} {shape} {variant} {size}> | ||
| {#if value?.icon} | ||
| <Icon icon={value.icon} /> | ||
| {/if} | ||
|
|
||
| <Text class={hideTextOnSmallScreen ? 'hidden sm:block' : ''}> | ||
| {#if !value} | ||
| {placeholder} | ||
| {:else} | ||
| {value?.label} | ||
| {/if} | ||
| </Text> | ||
| </Button> | ||
| {/if} | ||
| {/snippet} | ||
| </Popover.Trigger> | ||
| <Popover.Portal> | ||
| <Popover.Content align={position === 'bottom-left' ? 'start' : 'end'} forceMount> | ||
| {#snippet child({ props, wrapperProps, open })} | ||
| <!-- DROP DOWN MENU --> | ||
| {#if open} | ||
| <div {...wrapperProps}> | ||
| <div | ||
| {...props} | ||
| class={[ | ||
| 'flex max-h-[50vh] min-w-[250px] flex-col overflow-y-auto rounded-2xl bg-gray-100 py-2 text-sm font-medium text-black shadow-lg dark:bg-gray-700 dark:text-white', | ||
| className, | ||
| props.class, | ||
| ]} | ||
| transition:fly={{ y: -30, duration: 250 }} | ||
| > | ||
| {#each options as option (option.value)} | ||
| {@const buttonStyle = option.disabled | ||
| ? '' | ||
| : 'transition-all hover:bg-gray-300 dark:hover:bg-gray-800'} | ||
| <button | ||
| type="button" | ||
| class="grid grid-cols-[36px_1fr] place-items-center p-2 disabled:opacity-40 {buttonStyle}" | ||
| disabled={option.disabled} | ||
| onclick={() => !option.disabled && handleSelectOption(option)} | ||
| > | ||
| {#if value?.value === option.value} | ||
| <div class="text-primary"> | ||
| <Icon icon={mdiCheck} /> | ||
| </div> | ||
| <p class="text-primary justify-self-start"> | ||
| {option.label} | ||
| </p> | ||
| {:else} | ||
| <div></div> | ||
| <p class="justify-self-start"> | ||
| {option.label} | ||
| </p> | ||
| {/if} | ||
| </button> | ||
| {/each} | ||
| </div> | ||
| </div> | ||
| {/if} | ||
| {/snippet} | ||
| </Popover.Content> | ||
| </Popover.Portal> | ||
| </Popover.Root> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <script lang="ts"> | ||
| import ComponentDescription from '$docs/components/ComponentDescription.svelte'; | ||
| import ComponentExamples from '$docs/components/ComponentExamples.svelte'; | ||
| import ComponentPage from '$docs/components/ComponentPage.svelte'; | ||
| import BasicExample from './BasicExample.svelte'; | ||
| import basicExample from './BasicExample.svelte?raw'; | ||
| import OutlineButton from './OutlineButton.svelte'; | ||
| import outlineButton from './OutlineButton.svelte?raw'; | ||
| import BigList from './BigList.svelte'; | ||
| import bigList from './BigList.svelte?raw'; | ||
| import CustomTrigger from './CustomTrigger.svelte'; | ||
| import customTrigger from './CustomTrigger.svelte?raw'; | ||
| </script> | ||
|
|
||
| <ComponentPage name="Dropdown"> | ||
| <ComponentDescription> | ||
| Allows the user to select a single option from a dropdown list that is triggered by a button | ||
| </ComponentDescription> | ||
| <ComponentExamples | ||
| examples={[ | ||
| { title: 'Basic', code: basicExample, component: BasicExample }, | ||
| { title: 'Outline Button', code: outlineButton, component: OutlineButton }, | ||
| { | ||
| title: 'Big List', | ||
| code: bigList, | ||
| component: BigList, | ||
| }, | ||
| { | ||
| title: 'Custom Trigger', | ||
| code: customTrigger, | ||
| component: CustomTrigger, | ||
| }, | ||
| ]} | ||
| /> | ||
| </ComponentPage> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <script lang="ts"> | ||
| import { Stack } from '@immich/ui'; | ||
| import Dropdown from '@immich/ui/components/Dropdown/Dropdown.svelte'; | ||
| import { mdiPartyPopper, mdiTrashCan, mdiReact } from '@mdi/js'; | ||
| </script> | ||
|
|
||
| <Stack class="mb-8 max-w-[250px]" gap={8}> | ||
| <Dropdown | ||
| placeholder="Select a framework" | ||
| data={[ | ||
| { label: 'Svelte', value: 'svelte', icon: mdiPartyPopper }, | ||
| { label: 'React', value: 'react', icon: mdiReact }, | ||
| { label: 'Angular', value: 'angular', icon: mdiTrashCan }, | ||
| ]} | ||
| onChange={() => {}} | ||
| /> | ||
| </Stack> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <script lang="ts"> | ||
| import { Stack } from '@immich/ui'; | ||
| import Dropdown from '@immich/ui/components/Dropdown/Dropdown.svelte'; | ||
| </script> | ||
|
|
||
| <Stack class="mb-8 max-w-[250px]" gap={8}> | ||
| <Dropdown | ||
| placeholder="Select a fruit" | ||
| data={[ | ||
| { label: 'Apple', value: 'apple' }, | ||
| { label: 'Banana', value: 'banana' }, | ||
| { label: 'Cherry', value: 'cherry' }, | ||
| { label: 'Date', value: 'date' }, | ||
| { label: 'Elderberry', value: 'elderberry' }, | ||
| { label: 'Fig', value: 'fig' }, | ||
| { label: 'Grape', value: 'grape' }, | ||
| { label: 'Honeydew', value: 'honeydew' }, | ||
| { label: 'Kiwi', value: 'kiwi' }, | ||
| { label: 'Lemon', value: 'lemon' }, | ||
| { label: 'Mango', value: 'mango' }, | ||
| { label: 'Nectarine', value: 'nectarine' }, | ||
| { label: 'Orange', value: 'orange' }, | ||
| { label: 'Papaya', value: 'papaya' }, | ||
| { label: 'Quince', value: 'quince' }, | ||
| { label: 'Raspberry', value: 'raspberry' }, | ||
| { label: 'Strawberry', value: 'strawberry' }, | ||
| { label: 'Tangerine', value: 'tangerine' }, | ||
| { label: 'Ugli fruit', value: 'ugli-fruit' }, | ||
| { label: 'Vanilla bean', value: 'vanilla-bean' }, | ||
| { label: 'Watermelon', value: 'watermelon' }, | ||
| { label: 'Xigua', value: 'xigua' }, | ||
| { label: 'Yellow passion fruit', value: 'yellow-passion-fruit' }, | ||
| { label: 'Zucchini', value: 'zucchini' }, | ||
| ]} | ||
| onChange={() => {}} | ||
| /> | ||
| </Stack> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <script lang="ts"> | ||
| import { Button, Stack } from '@immich/ui'; | ||
| import Dropdown, { type DropdownItem } from '@immich/ui/components/Dropdown/Dropdown.svelte'; | ||
| import Icon from '@immich/ui/components/Icon/Icon.svelte'; | ||
|
|
||
| let options = [ | ||
| { label: 'Svelte', value: 'svelte' }, | ||
| { label: 'React', value: 'react' }, | ||
| { label: 'Angular', value: 'angular' }, | ||
| ]; | ||
| let selectedOption = $state<DropdownItem | null>(options[0]); | ||
| </script> | ||
|
|
||
| <Stack class="mb-8 max-w-[250px]" gap={8}> | ||
| <Dropdown placeholder="Select a framework" data={options}> | ||
| {#snippet trigger({ props, selectedIcon })} | ||
| <Button {...props} color="success" variant="filled" fullWidth size="large" shape="round"> | ||
| {selectedOption?.value || 'Select an option'} | ||
| {#if selectedIcon} | ||
| <Icon icon={selectedIcon} /> | ||
| {/if} | ||
| </Button> | ||
| {/snippet} | ||
| </Dropdown> | ||
| </Stack> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <script lang="ts"> | ||
| import { Stack } from '@immich/ui'; | ||
| import Dropdown from '@immich/ui/components/Dropdown/Dropdown.svelte'; | ||
| </script> | ||
|
|
||
| <Stack class="mb-8 max-w-[250px]" gap={8}> | ||
| <Dropdown | ||
| placeholder="Select a framework" | ||
| color="secondary" | ||
| variant="outline" | ||
| data={[ | ||
| { label: 'Svelte', value: 'svelte' }, | ||
| { label: 'React', value: 'react' }, | ||
| { label: 'Angular', value: 'angular' }, | ||
| ]} | ||
| /> | ||
| </Stack> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably do this in JS land with
{value?.label || placeholder}. That's definitely exclusively personal preference thoughThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yeah agreed, that's a remnant of what I had there before