Skip to content

Commit

Permalink
Factor out dropdowns into component
Browse files Browse the repository at this point in the history
  • Loading branch information
FyreByrd committed Feb 4, 2025
1 parent 702f5da commit 02c16bb
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 122 deletions.
53 changes: 30 additions & 23 deletions source/SIL.AppBuilder.Portal/src/lib/components/Dropdown.svelte
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
<!--
@component
A simple dropdown menu from DaisyUI.
-->
<!-- TODO: this component isn't used anywhere... -->
<script lang="ts">
interface Props {
cols?: number;
label?: import('svelte').Snippet;
content?: import('svelte').Snippet;
onNavEnd?: () => void;
/** class="dropdown ..." */
dropdownClasses?: string;
/** class="btn btn-ghost ..." */
labelClasses?: string;
/** class="dropdown-content z-10 drop-shadow-lg rounded-md bg-base-200 ..." */
contentClasses?: string;
label: import('svelte').Snippet;
content: import('svelte').Snippet;
onclick?: () => void;
open?: boolean;
}
let { cols = 6, label, content, onNavEnd }: Props = $props();
let {
dropdownClasses = '',
labelClasses = '',
contentClasses = '',
label,
content,
onclick,
open = $bindable(false)
}: Props = $props();
</script>

<div class="dropdown">
<!-- When .dropdown is focused, .dropdown-content is revealed making this actually interactive -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<label tabindex="0" class="btn btn-ghost p-0.5 no-animation flex-nowrap">
{@render label?.()}
<div
role="button"
tabindex="0"
class="dropdown-content menu drop-shadow-lg mt-2.5 bg-base-100 z-10"
class:min-w-[21rem]={cols == 6}
class:min-w-[17.25rem]={cols == 5}
onblur={() => onNavEnd?.()}
>
{@render content?.()}
</div>
</label>
</div>
<svelte:window onclick={() => (open = false)} />

<details class="dropdown {dropdownClasses}" bind:open>
<summary class="btn btn-ghost {labelClasses}" onclick={() => onclick?.()}>
{@render label()}
</summary>
<div class="dropdown-content z-10 drop-shadow-lg rounded-md bg-base-200 {contentClasses}">
{@render content()}
</div>
</details>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { LanguageIcon } from '$lib/icons';
import { languageTag, type AvailableLanguageTag } from '$lib/paraglide/runtime';
import Icon from '@iconify/svelte';
import Dropdown from './Dropdown.svelte';
function switchToLanguage(newLanguage: AvailableLanguageTag) {
const canonicalPath = i18n.route(page.url.pathname);
Expand All @@ -14,20 +15,20 @@
</script>

{#key languageTag()}
<div class="dropdown dropdown-end">
<div
role="button"
class="btn btn-ghost m-2 p-2 rounded-xl items-middle justify-center flex-nowrap"
tabindex="0"
>
<Dropdown
dropdownClasses="dropdown-end"
labelClasses="m-2 p-2 rounded-xl items-middle justify-center flex-nowrap"
contentClasses="overflow-y-auto"
>
{#snippet label()}
<LanguageIcon color="white" />
</div>
<div class="dropdown-content z-10 bg-base-200 w-48 rounded-md overflow-y-auto">
<ul class="menu menu-compact gap-1 p-2" tabindex="-1">
{/snippet}
{#snippet content()}
<ul class="menu menu-compact gap-1 p-2">
{#each i18n.config.runtime.availableLanguageTags as lang}
<li>
<div
class="btn flex {lang === languageTag() ? 'active' : 'inactive'}"
class="btn flex flex-nowrap {lang === languageTag() ? 'active' : 'inactive'}"
onclick={() => switchToLanguage(lang)}
onkeypress={() => switchToLanguage(lang)}
role="button"
Expand All @@ -39,6 +40,6 @@
</li>
{/each}
</ul>
</div>
</div>
{/snippet}
</Dropdown>
{/key}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import { superForm } from 'sveltekit-superforms';
import type { ProjectActionSchema, ProjectForAction } from '../common';
import { canArchive, canClaimProject, canReactivate } from '../common';
import Dropdown from '$lib/components/Dropdown.svelte';
interface Props {
data: SuperValidated<Infer<ProjectActionSchema>>;
project: ProjectForAction;
Expand Down Expand Up @@ -50,26 +50,20 @@
)
);
}
let dropdownOpen: boolean = $state(false);
function close() {
dropdownOpen = false;
}
</script>

<svelte:window onclick={() => close()} />

<details class="dropdown dropdown-bottom dropdown-end" bind:open={dropdownOpen}>
<summary
class="btn btn-ghost max-h-fit min-h-fit p-1 inline"
onclick={() => {
$form.projectId = project.Id;
}}
>
<Dropdown
dropdownClasses="dropdown-bottom dropdown-end"
labelClasses="max-h-fit min-h-fit p-1 inline"
contentClasses="p-1 min-w-36 w-auto"
onclick={() => {
$form.projectId = project.Id;
}}
>
{#snippet label()}
<IconContainer icon="charm:menu-kebab" width={20} />
</summary>
<div class="dropdown-content p-1 bg-base-200 z-10 rounded-md min-w-36 w-auto shadow-lg">
{/snippet}
{#snippet content()}
<form method="POST" action="?/{endpoint}" use:enhance>
<input type="hidden" name="singleId" value={project.Id} />
<ul class="menu menu-compact overflow-hidden rounded-md">
Expand Down Expand Up @@ -107,5 +101,5 @@
{/if}
</ul>
</form>
</div>
</details>
{/snippet}
</Dropdown>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { page } from '$app/state';
import Dropdown from '$lib/components/Dropdown.svelte';
import * as m from '$lib/paraglide/messages';
import Icon from '@iconify/svelte';
Expand All @@ -10,21 +11,27 @@
['active', m.projects_switcher_dropdown_activeProjects()],
['archived', m.projects_switcher_dropdown_archived()]
]);
let open = $state(false);
</script>

<div class="dropdown dropdown-start">
<!-- When .dropdown is focused, .dropdown-content is revealed making this actually interactive -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<h1 tabindex="0" class="p-4 pl-6 cursor-pointer">
<div class="flex flex-row items-center">
{textsForPaths.get(page.params.filter)}
<div class="dropdown-icon">
<Icon width="24" class="dropdown-icon" icon="gridicons:dropdown" />
<Dropdown
dropdownClasses="dropdown-start"
labelClasses="no-animation hover:bg-transparent"
contentClasses="overflow-y-auto left-2 p-2 border m-2"
bind:open
>
{#snippet label()}
<h1 class="p-4 pl-6 cursor-pointer">
<div class="flex flex-row items-center">
{textsForPaths.get(page.params.filter)}
<div class="dropdown-icon" class:open>
<Icon width="24" class="dropdown-icon" icon="gridicons:dropdown" />
</div>
</div>
</div>
</h1>
<div class="dropdown-content z-10 overflow-y-auto left-2">
<div class="p-2 border m-2 rounded-md bg-base-200 px-4">
</h1>
{/snippet}
{#snippet content()}
<div class="px-4">
{#each textsForPaths as route}
<a
href="/projects/{route[0]}{page.params.id ? '/' + page.params.id : ''}"
Expand All @@ -35,15 +42,15 @@
</a>
{/each}
</div>
</div>
</div>
{/snippet}
</Dropdown>

<style>
.dropdown-icon {
transition: transform 0.15s;
transform: rotate(0deg);
}
.dropdown:focus-within .dropdown-icon {
.dropdown-icon.open {
transform: rotate(180deg);
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { signOut } from '@auth/sveltekit/client';
import { RoleId } from 'sil.appbuilder.portal.common/prisma';
import type { LayoutData } from './$types';
import Dropdown from '$lib/components/Dropdown.svelte';
interface Props {
data: LayoutData;
Expand Down Expand Up @@ -53,16 +54,20 @@
</div>
<div class="navbar-end">
<LanguageSelector />
<div class="dropdown dropdown-end">
<div role="button" class="btn btn-ghost m-2 p-2 rounded-xl" tabindex="0">
<Dropdown
dropdownClasses="dropdown-end"
labelClasses="m-2 p-2 rounded-xl"
contentClasses="w-36 overflow-y-auto"
>
{#snippet label()}
<img
src={page.data.session?.user?.image}
alt="User profile"
referrerpolicy="no-referrer"
class="h-full rounded-xl"
/>
</div>
<div class="dropdown-content w-36 z-10 bg-base-200 rounded-md overflow-y-auto">
{/snippet}
{#snippet content()}
<ul class="menu menu-compact gap-1 p-2">
<li>
<a href="/users/{page.data.session?.user?.userId ?? ''}/settings/profile">
Expand All @@ -83,8 +88,8 @@
<button onclick={() => signOut({ callbackUrl: '/' })}>{m.header_signOut()}</button>
</li>
</ul>
</div>
</div>
{/snippet}
</Dropdown>
</div>
</div>
<div class="flex grow min-h-0">
Expand Down
Loading

0 comments on commit 02c16bb

Please sign in to comment.