your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { AllCardDefinitions } from '$lib/cards';
3 import type { CardDefinition } from '$lib/cards/types';
4 import { Command, Dialog } from 'bits-ui';
5
6 const CardDefGroups = [
7 'Core',
8 ...Array.from(
9 new Set(
10 AllCardDefinitions.map((cardDef) => cardDef.groups)
11 .flat()
12 .filter((g) => g)
13 )
14 )
15 .sort()
16 .filter((g) => g !== 'Core')
17 ];
18
19 let {
20 open = $bindable(false),
21 onselect
22 }: { open: boolean; onselect: (cardDef: CardDefinition) => void } = $props();
23
24 function handleKeydown(e: KeyboardEvent) {
25 if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
26 e.preventDefault();
27 open = true;
28 }
29 }
30</script>
31
32<svelte:document onkeydown={handleKeydown} />
33
34<Dialog.Root bind:open>
35 <Dialog.Portal>
36 <Dialog.Overlay
37 class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80"
38 />
39 <Dialog.Content
40 class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-36 left-[50%] z-50 w-full max-w-[94%] translate-x-[-50%] outline-hidden sm:max-w-lg md:w-full"
41 >
42 <Dialog.Title class="sr-only">Command Menu</Dialog.Title>
43 <Dialog.Description class="sr-only">
44 This is the command menu. Use the arrow keys to navigate and press ⌘K to open the search
45 bar.
46 </Dialog.Description>
47 <Command.Root
48 class="border-base-200 dark:border-base-800 mx-auto flex h-full w-full max-w-[90vw] flex-col overflow-hidden rounded-2xl border bg-white dark:bg-black"
49 >
50 <Command.Input
51 class="focus-override placeholder:text-base-900/50 dark:placeholder:text-base-50/50 border-base-200 dark:border-base-800 bg-base-100 mx-1 mt-1 inline-flex truncate rounded-2xl rounded-tl-2xl px-4 text-sm transition-colors focus:ring-0 focus:outline-hidden dark:bg-black"
52 placeholder="Search for a card..."
53 />
54 <Command.List
55 class="focus:outline-accent-500/50 max-h-[50vh] overflow-x-hidden overflow-y-auto rounded-br-2xl rounded-bl-2xl bg-white px-2 pb-2 focus:border-0 dark:bg-black"
56 >
57 <Command.Viewport>
58 <Command.Empty
59 class="text-base-900 dark:text-base-100 flex w-full items-center justify-center pt-8 pb-6 text-sm"
60 >
61 No results found.
62 </Command.Empty>
63
64 {#each CardDefGroups as group, index}
65 {#if group && AllCardDefinitions.some((cardDef) => cardDef.groups?.includes(group))}
66 <Command.Group>
67 <Command.GroupHeading
68 class="text-base-600 dark:text-base-400 px-3 pt-4 pb-2 text-xs"
69 >
70 {group}
71 </Command.GroupHeading>
72 <Command.GroupItems>
73 {#each AllCardDefinitions.filter( (cardDef) => cardDef.groups?.includes(group) ) as cardDef}
74 <Command.Item
75 onSelect={() => {
76 open = false;
77 onselect(cardDef);
78 }}
79 class="rounded-button data-selected:bg-accent-500/10 flex h-10 cursor-pointer items-center gap-2 rounded-xl px-3 py-2.5 text-sm outline-hidden select-none"
80 keywords={[group, cardDef.type, ...(cardDef.keywords || [])]}
81 >
82 {#if cardDef.icon}
83 <div class="text-base-700 dark:text-base-300">
84 {@html cardDef.icon}
85 </div>
86 {/if}
87 {cardDef.name}
88 </Command.Item>
89 {/each}
90 </Command.GroupItems>
91 </Command.Group>
92 {#if index < CardDefGroups.length - 1}
93 <Command.Separator class="bg-base-900/5 dark:bg-base-50/5 my-1 h-px w-full" />
94 {/if}
95 {/if}
96 {/each}
97 </Command.Viewport>
98 </Command.List>
99 </Command.Root>
100 </Dialog.Content>
101 </Dialog.Portal>
102</Dialog.Root>