your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { cn } from '@foxui/core';
3 import type { Editor, Range } from '@tiptap/core';
4 import Icon from '../Icon.svelte';
5 import type { RichTextTypes } from '..';
6
7 type Props = {
8 items: {
9 value: RichTextTypes;
10 label: string;
11 command: ({ editor, range }: { editor: Editor; range: Range }) => void;
12 }[];
13 range: Range;
14 editor: Editor;
15 active?: number;
16 };
17
18 let { items, range, editor, active = 0 }: Props = $props();
19
20 let activeIndex = $state(0);
21
22 export function setItems(value: any[]) {
23 items = value;
24 }
25
26 export function setRange(value: Range) {
27 range = value;
28 }
29
30 export function onKeyDown(event: KeyboardEvent) {
31 if (event.repeat) {
32 return false;
33 }
34 switch (event.key) {
35 case 'ArrowUp': {
36 if (activeIndex <= 0) {
37 activeIndex = items.length - 1;
38 } else {
39 activeIndex--;
40 }
41 return true;
42 }
43 case 'ArrowDown': {
44 if (activeIndex >= items.length - 1) {
45 activeIndex = 0;
46 } else {
47 activeIndex++;
48 }
49 return true;
50 }
51 case 'Enter': {
52 const selected = items[activeIndex];
53
54 if (selected) {
55 selected.command({ editor, range });
56 return true;
57 }
58 }
59 }
60
61 return false;
62 }
63</script>
64
65<menu
66 class={cn(
67 'bg-base-50/50 border-base-500/20 overflow-hidden rounded-2xl border shadow-lg backdrop-blur-xl',
68 'dark:bg-base-900/50 dark:border-base-500/10',
69 'motion-safe:animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
70 'divide-base-300/30 dark:divide-base-800 divide-y text-sm'
71 )}
72>
73 {#each items as item, index (item.value)}
74 <button
75 onclick={() => item.command({ editor, range })}
76 class={cn(
77 'text-base-900 dark:text-base-200 group relative isolate flex w-full min-w-28 cursor-pointer items-center gap-3 px-3 py-2 font-medium [&_svg]:size-3.5',
78 activeIndex === index
79 ? 'text-accent-700 dark:text-accent-400 bg-accent-500/10'
80 : 'hover:bg-accent-500/10'
81 )}
82 >
83 <Icon name={item.value} />
84 {item.label}
85 </button>
86 {/each}
87</menu>