svelte-incant 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -0
- package/dist/attachment.svelte.d.ts +8 -0
- package/dist/attachment.svelte.js +93 -0
- package/dist/combobox-example.svelte +155 -0
- package/dist/combobox-example.svelte.d.ts +3 -0
- package/dist/components/CodeBlock.svelte +213 -0
- package/dist/components/CodeBlock.svelte.d.ts +12 -0
- package/dist/components/header.svelte +155 -0
- package/dist/components/header.svelte.d.ts +3 -0
- package/dist/components/kbds.svelte +31 -0
- package/dist/components/kbds.svelte.d.ts +7 -0
- package/dist/components/ui/badge/badge.svelte +49 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +32 -0
- package/dist/components/ui/badge/index.d.ts +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/button/button.svelte +82 -0
- package/dist/components/ui/button/button.svelte.d.ts +64 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +4 -0
- package/dist/components/ui/card/card-action.svelte +20 -0
- package/dist/components/ui/card/card-action.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-content.svelte +15 -0
- package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-description.svelte +20 -0
- package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-footer.svelte +20 -0
- package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-header.svelte +23 -0
- package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-title.svelte +20 -0
- package/dist/components/ui/card/card-title.svelte.d.ts +5 -0
- package/dist/components/ui/card/card.svelte +23 -0
- package/dist/components/ui/card/card.svelte.d.ts +5 -0
- package/dist/components/ui/card/index.d.ts +8 -0
- package/dist/components/ui/card/index.js +10 -0
- package/dist/components/ui/command/command-dialog.svelte +40 -0
- package/dist/components/ui/command/command-dialog.svelte.d.ts +12 -0
- package/dist/components/ui/command/command-empty.svelte +17 -0
- package/dist/components/ui/command/command-empty.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-group.svelte +30 -0
- package/dist/components/ui/command/command-group.svelte.d.ts +7 -0
- package/dist/components/ui/command/command-input.svelte +26 -0
- package/dist/components/ui/command/command-input.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-item.svelte +20 -0
- package/dist/components/ui/command/command-item.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-link-item.svelte +20 -0
- package/dist/components/ui/command/command-link-item.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-list.svelte +17 -0
- package/dist/components/ui/command/command-list.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-loading.svelte +7 -0
- package/dist/components/ui/command/command-loading.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-separator.svelte +17 -0
- package/dist/components/ui/command/command-separator.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-shortcut.svelte +20 -0
- package/dist/components/ui/command/command-shortcut.svelte.d.ts +5 -0
- package/dist/components/ui/command/command.svelte +28 -0
- package/dist/components/ui/command/command.svelte.d.ts +8 -0
- package/dist/components/ui/command/index.d.ts +12 -0
- package/dist/components/ui/command/index.js +14 -0
- package/dist/components/ui/dialog/dialog-close.svelte +7 -0
- package/dist/components/ui/dialog/dialog-close.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-content.svelte +45 -0
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +13 -0
- package/dist/components/ui/dialog/dialog-description.svelte +17 -0
- package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
- package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte +20 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-portal.svelte +7 -0
- package/dist/components/ui/dialog/dialog-portal.svelte.d.ts +3 -0
- package/dist/components/ui/dialog/dialog-title.svelte +17 -0
- package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-trigger.svelte +7 -0
- package/dist/components/ui/dialog/dialog-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog.svelte +7 -0
- package/dist/components/ui/dialog/dialog.svelte.d.ts +3 -0
- package/dist/components/ui/dialog/index.d.ts +11 -0
- package/dist/components/ui/dialog/index.js +13 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.js +4 -0
- package/dist/components/ui/input/input.svelte +52 -0
- package/dist/components/ui/input/input.svelte.d.ts +13 -0
- package/dist/components/ui/kbd/index.d.ts +3 -0
- package/dist/components/ui/kbd/index.js +5 -0
- package/dist/components/ui/kbd/kbd-group.svelte +20 -0
- package/dist/components/ui/kbd/kbd-group.svelte.d.ts +5 -0
- package/dist/components/ui/kbd/kbd.svelte +25 -0
- package/dist/components/ui/kbd/kbd.svelte.d.ts +5 -0
- package/dist/components/ui/popover/index.d.ts +6 -0
- package/dist/components/ui/popover/index.js +8 -0
- package/dist/components/ui/popover/popover-close.svelte +7 -0
- package/dist/components/ui/popover/popover-close.svelte.d.ts +4 -0
- package/dist/components/ui/popover/popover-content.svelte +31 -0
- package/dist/components/ui/popover/popover-content.svelte.d.ts +10 -0
- package/dist/components/ui/popover/popover-portal.svelte +7 -0
- package/dist/components/ui/popover/popover-portal.svelte.d.ts +3 -0
- package/dist/components/ui/popover/popover-trigger.svelte +17 -0
- package/dist/components/ui/popover/popover-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/popover/popover.svelte +7 -0
- package/dist/components/ui/popover/popover.svelte.d.ts +3 -0
- package/dist/components/ui/table/index.d.ts +9 -0
- package/dist/components/ui/table/index.js +11 -0
- package/dist/components/ui/table/table-body.svelte +20 -0
- package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-caption.svelte +20 -0
- package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-cell.svelte +23 -0
- package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-footer.svelte +20 -0
- package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-head.svelte +23 -0
- package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-header.svelte +20 -0
- package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-row.svelte +23 -0
- package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
- package/dist/components/ui/table/table.svelte +22 -0
- package/dist/components/ui/table/table.svelte.d.ts +5 -0
- package/dist/components/ui/tabs/index.d.ts +5 -0
- package/dist/components/ui/tabs/index.js +7 -0
- package/dist/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-list.svelte +20 -0
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +20 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs.svelte +19 -0
- package/dist/components/ui/tabs/tabs.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/index.d.ts +6 -0
- package/dist/components/ui/tooltip/index.js +8 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +11 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip.svelte.d.ts +4 -0
- package/dist/focus.svelte +56 -0
- package/dist/focus.svelte.d.ts +13 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +7 -0
- package/dist/overlay-component.svelte +19 -0
- package/dist/overlay-component.svelte.d.ts +6 -0
- package/dist/palette.svelte +132 -0
- package/dist/palette.svelte.d.ts +7 -0
- package/dist/palette.svelte.js +177 -0
- package/dist/shortcut.svelte +26 -0
- package/dist/shortcut.svelte.d.ts +8 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.js +32 -0
- package/package.json +87 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Svelte Incant
|
|
2
|
+
|
|
3
|
+
A keyboard shortcut management library for Svelte 5.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bun install svelte-incant
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Add `Palette` component to your root layout to enable the shortcut overlay:
|
|
14
|
+
|
|
15
|
+
```svelte
|
|
16
|
+
<script>
|
|
17
|
+
import { Palette } from 'svelte-incant';
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Palette />
|
|
21
|
+
|
|
22
|
+
<!-- ... -->
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Register keyboard shortcuts with the `Shortcut` component:
|
|
26
|
+
|
|
27
|
+
```svelte
|
|
28
|
+
<script>
|
|
29
|
+
import { Shortcut } from 'svelte-incant';
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<Shortcut
|
|
33
|
+
keys={['control', 's']}
|
|
34
|
+
description="Save document"
|
|
35
|
+
action={() => console.log('Save document')}
|
|
36
|
+
/>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For focusing elements (like inputs), use the `Focus` component:
|
|
40
|
+
|
|
41
|
+
```svelte
|
|
42
|
+
<script>
|
|
43
|
+
import { Focus } from 'svelte-incant';
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<Focus keys={['control', 'e']} description="Focus search input">
|
|
47
|
+
<input type="text" placeholder="Search..." />
|
|
48
|
+
</Focus>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or attach shortcuts directly to an element using the `@attach` directive:
|
|
52
|
+
|
|
53
|
+
```svelte
|
|
54
|
+
<script>
|
|
55
|
+
import { shortcut } from 'svelte-incant';
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<input
|
|
59
|
+
type="text"
|
|
60
|
+
placeholder="Type something..."
|
|
61
|
+
{@attach shortcut({
|
|
62
|
+
keys: ['meta', 'i'],
|
|
63
|
+
description: 'Focus text input'
|
|
64
|
+
})}
|
|
65
|
+
/>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The `@attach` directive focuses the element it attaches to directly, as opposed to the `Focus` component, which wraps the children in a `div` and focuses that.
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- **Shortcut Palette**: Press `?` to open the shortcut palette and see all registered shortcuts
|
|
73
|
+
- **Route-specific Shortcuts**: Shortcuts only run when their component is mounted, allowing different shortcuts in different routes
|
|
74
|
+
- **Focus Management**: Easily manage focus states with keyboard shortcuts
|
|
75
|
+
- **Component-based**: Use components or directives to register shortcuts
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Shortcut } from './palette.svelte.js';
|
|
2
|
+
import type { Attachment } from 'svelte/attachments';
|
|
3
|
+
type ShortcutInput = Omit<Shortcut, 'action' | 'keys'> & {
|
|
4
|
+
keys: string | string[] | string[][];
|
|
5
|
+
action?: () => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function shortcut(shortcut: ShortcutInput): Attachment<HTMLElement>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { mount, unmount } from 'svelte';
|
|
2
|
+
import { add_shortcut, remove_shortcut } from './palette.svelte.js';
|
|
3
|
+
import OverlayComponent from './overlay-component.svelte';
|
|
4
|
+
import { PressedKeys, watch } from 'runed';
|
|
5
|
+
const pressed_keys = new PressedKeys();
|
|
6
|
+
const voidElements = new Set([
|
|
7
|
+
'area',
|
|
8
|
+
'base',
|
|
9
|
+
'br',
|
|
10
|
+
'col',
|
|
11
|
+
'embed',
|
|
12
|
+
'hr',
|
|
13
|
+
'img',
|
|
14
|
+
'input',
|
|
15
|
+
'link',
|
|
16
|
+
'meta',
|
|
17
|
+
'param',
|
|
18
|
+
'source',
|
|
19
|
+
'track',
|
|
20
|
+
'wbr'
|
|
21
|
+
]);
|
|
22
|
+
function setupAnchor(element, targetNode, isVoidElement, keys) {
|
|
23
|
+
const anchor = document.createElement('div');
|
|
24
|
+
anchor.style.pointerEvents = 'none';
|
|
25
|
+
if (isVoidElement) {
|
|
26
|
+
anchor.style.position = 'absolute';
|
|
27
|
+
anchor.style.left = `${element.offsetLeft}px`;
|
|
28
|
+
anchor.style.top = `${element.offsetTop}px`;
|
|
29
|
+
anchor.style.width = `${element.offsetWidth}px`;
|
|
30
|
+
anchor.style.height = `${element.offsetHeight}px`;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const style = window.getComputedStyle(element);
|
|
34
|
+
if (style.position === 'static') {
|
|
35
|
+
element.style.position = 'relative';
|
|
36
|
+
}
|
|
37
|
+
anchor.style.position = 'absolute';
|
|
38
|
+
anchor.style.top = '0';
|
|
39
|
+
anchor.style.left = '0';
|
|
40
|
+
anchor.style.width = '100%';
|
|
41
|
+
anchor.style.height = '100%';
|
|
42
|
+
}
|
|
43
|
+
const instance = mount(OverlayComponent, {
|
|
44
|
+
target: anchor,
|
|
45
|
+
props: { keys }
|
|
46
|
+
});
|
|
47
|
+
targetNode.appendChild(anchor);
|
|
48
|
+
return { anchor, instance };
|
|
49
|
+
}
|
|
50
|
+
function setupOutline(element) {
|
|
51
|
+
element.style.transition = 'outline 0s, outline-offset 0s';
|
|
52
|
+
watch(() => pressed_keys.has('alt'), (value) => {
|
|
53
|
+
if (value) {
|
|
54
|
+
element.style.outline = '2px dotted currentColor';
|
|
55
|
+
element.style.outlineOffset = '2px';
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
element.style.outline = '';
|
|
59
|
+
element.style.outlineOffset = '';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function shortcut(shortcut) {
|
|
64
|
+
return (element) => {
|
|
65
|
+
const action = () => {
|
|
66
|
+
element.focus();
|
|
67
|
+
element.click();
|
|
68
|
+
shortcut.action?.();
|
|
69
|
+
};
|
|
70
|
+
const shortcutData = {
|
|
71
|
+
...shortcut,
|
|
72
|
+
action
|
|
73
|
+
};
|
|
74
|
+
add_shortcut(shortcutData);
|
|
75
|
+
let targetNode = element;
|
|
76
|
+
const tagName = element.tagName.toLowerCase();
|
|
77
|
+
const isVoidElement = voidElements.has(tagName);
|
|
78
|
+
if (isVoidElement) {
|
|
79
|
+
targetNode = element.parentElement;
|
|
80
|
+
}
|
|
81
|
+
if (!targetNode) {
|
|
82
|
+
remove_shortcut(shortcut.keys);
|
|
83
|
+
return () => { };
|
|
84
|
+
}
|
|
85
|
+
const { anchor, instance } = setupAnchor(element, targetNode, isVoidElement, shortcut.keys);
|
|
86
|
+
setupOutline(element);
|
|
87
|
+
return () => {
|
|
88
|
+
unmount(instance);
|
|
89
|
+
anchor.remove();
|
|
90
|
+
remove_shortcut(shortcut.keys);
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Card from './components/ui/card';
|
|
3
|
+
import { shortcut } from './attachment.svelte.js';
|
|
4
|
+
import { Button } from './components/ui/button/index.js';
|
|
5
|
+
import * as Command from './components/ui/command/index.js';
|
|
6
|
+
import * as Popover from './components/ui/popover/index.js';
|
|
7
|
+
import * as Tabs from './components/ui/tabs/index.js';
|
|
8
|
+
import CodeBlock from './components/CodeBlock.svelte';
|
|
9
|
+
|
|
10
|
+
import { cn } from './utils.js';
|
|
11
|
+
import CheckIcon from '@lucide/svelte/icons/check';
|
|
12
|
+
import ChevronsUpDownIcon from '@lucide/svelte/icons/chevrons-up-down';
|
|
13
|
+
import { tick } from 'svelte';
|
|
14
|
+
|
|
15
|
+
const frameworks = [
|
|
16
|
+
{
|
|
17
|
+
value: 'sveltekit',
|
|
18
|
+
label: 'SvelteKit'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: 'next.js',
|
|
22
|
+
label: 'Next.js'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: 'nuxt.js',
|
|
26
|
+
label: 'Nuxt.js'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: 'remix',
|
|
30
|
+
label: 'Remix'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: 'astro',
|
|
34
|
+
label: 'Astro'
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
let open = $state(false);
|
|
39
|
+
let value = $state('');
|
|
40
|
+
let triggerRef = $state<HTMLButtonElement>(null!);
|
|
41
|
+
|
|
42
|
+
const selectedValue = $derived(frameworks.find((f) => f.value === value)?.label);
|
|
43
|
+
|
|
44
|
+
const comboboxDemoCode =
|
|
45
|
+
`<script>
|
|
46
|
+
import { shortcut } from 'svelte-incant';
|
|
47
|
+
import { Button } from './components/ui/button';
|
|
48
|
+
import * as Popover from './components/ui/popover';
|
|
49
|
+
<` +
|
|
50
|
+
`/script>
|
|
51
|
+
|
|
52
|
+
<Popover.Root bind:open>
|
|
53
|
+
<Popover.Trigger bind:ref={triggerRef}>
|
|
54
|
+
{#snippet child({ props })}
|
|
55
|
+
<Button
|
|
56
|
+
{...props}
|
|
57
|
+
variant="outline"
|
|
58
|
+
class="w-[200px] justify-between"
|
|
59
|
+
role="combobox"
|
|
60
|
+
aria-expanded={open}
|
|
61
|
+
{@attach shortcut({
|
|
62
|
+
keys: ['meta', 'k'],
|
|
63
|
+
description: 'Focus framework combobox'
|
|
64
|
+
})}
|
|
65
|
+
>
|
|
66
|
+
{selectedValue || 'Select a framework...'}
|
|
67
|
+
</Button>
|
|
68
|
+
{/snippet}
|
|
69
|
+
</Popover.Trigger>
|
|
70
|
+
<!-- Popover content -->
|
|
71
|
+
</Popover.Root>`;
|
|
72
|
+
|
|
73
|
+
function closeAndFocusTrigger() {
|
|
74
|
+
open = false;
|
|
75
|
+
tick().then(() => {
|
|
76
|
+
triggerRef.focus();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<p class="text-sm text-muted-foreground">
|
|
82
|
+
Press <kbd class="rounded border bg-muted px-1 py-0.5 text-xs">⌥</kbd> (alt) to see the focus
|
|
83
|
+
shortcut hint. Press <kbd class="rounded border bg-muted px-1 py-0.5 text-xs">⌘ K</kbd> to focus the
|
|
84
|
+
combobox.
|
|
85
|
+
</p>
|
|
86
|
+
|
|
87
|
+
<Tabs.Root value="example" class="w-full">
|
|
88
|
+
<Card.Root>
|
|
89
|
+
<Tabs.Content value="example">
|
|
90
|
+
<Card.Content class="grid h-80 place-items-center">
|
|
91
|
+
<Popover.Root bind:open>
|
|
92
|
+
<Popover.Trigger bind:ref={triggerRef}>
|
|
93
|
+
{#snippet child({ props })}
|
|
94
|
+
<Button
|
|
95
|
+
{...props}
|
|
96
|
+
variant="outline"
|
|
97
|
+
class="w-[200px] justify-between"
|
|
98
|
+
role="combobox"
|
|
99
|
+
aria-expanded={open}
|
|
100
|
+
{@attach shortcut({
|
|
101
|
+
keys: ['meta', 'k'],
|
|
102
|
+
description: 'Focus framework combobox'
|
|
103
|
+
})}
|
|
104
|
+
>
|
|
105
|
+
{selectedValue || 'Select a framework...'}
|
|
106
|
+
<ChevronsUpDownIcon class="opacity-50" />
|
|
107
|
+
</Button>
|
|
108
|
+
{/snippet}
|
|
109
|
+
</Popover.Trigger>
|
|
110
|
+
<Popover.Content class="w-[200px] p-0">
|
|
111
|
+
<Command.Root>
|
|
112
|
+
<Command.Input placeholder="Search framework..." />
|
|
113
|
+
<Command.List>
|
|
114
|
+
<Command.Empty>No framework found.</Command.Empty>
|
|
115
|
+
<Command.Group value="frameworks">
|
|
116
|
+
{#each frameworks as framework (framework.value)}
|
|
117
|
+
<Command.Item
|
|
118
|
+
value={framework.value}
|
|
119
|
+
onSelect={() => {
|
|
120
|
+
value = framework.value;
|
|
121
|
+
closeAndFocusTrigger();
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
<CheckIcon class={cn(value !== framework.value && 'text-transparent')} />
|
|
125
|
+
{framework.label}
|
|
126
|
+
</Command.Item>
|
|
127
|
+
{/each}
|
|
128
|
+
</Command.Group>
|
|
129
|
+
</Command.List>
|
|
130
|
+
</Command.Root>
|
|
131
|
+
</Popover.Content>
|
|
132
|
+
</Popover.Root>
|
|
133
|
+
</Card.Content>
|
|
134
|
+
</Tabs.Content>
|
|
135
|
+
<Tabs.Content value="code">
|
|
136
|
+
<Card.Content>
|
|
137
|
+
<CodeBlock language="xml" code={comboboxDemoCode} />
|
|
138
|
+
|
|
139
|
+
<p class="mt-4 text-sm text-muted-foreground">
|
|
140
|
+
Full combobox code from <a
|
|
141
|
+
href="https://shadcn-svelte.com/docs/components/combobox"
|
|
142
|
+
target="_blank"
|
|
143
|
+
class="font-mono underline"
|
|
144
|
+
>
|
|
145
|
+
https://shadcn-svelte.com/docs/components/combobox
|
|
146
|
+
</a>
|
|
147
|
+
</p>
|
|
148
|
+
</Card.Content>
|
|
149
|
+
</Tabs.Content>
|
|
150
|
+
</Card.Root>
|
|
151
|
+
<Tabs.List>
|
|
152
|
+
<Tabs.Trigger value="example">Example</Tabs.Trigger>
|
|
153
|
+
<Tabs.Trigger value="code">Code</Tabs.Trigger>
|
|
154
|
+
</Tabs.List>
|
|
155
|
+
</Tabs.Root>
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import hljs from 'highlight.js/lib/core';
|
|
3
|
+
import javascript from 'highlight.js/lib/languages/javascript';
|
|
4
|
+
import xml from 'highlight.js/lib/languages/xml';
|
|
5
|
+
import 'highlight.js/styles/github.css';
|
|
6
|
+
import copy from 'copy-to-clipboard';
|
|
7
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
8
|
+
|
|
9
|
+
hljs.registerLanguage('javascript', javascript);
|
|
10
|
+
hljs.registerLanguage('xml', xml);
|
|
11
|
+
|
|
12
|
+
let codeElement: HTMLElement;
|
|
13
|
+
|
|
14
|
+
function escapeHtml(value: string): string {
|
|
15
|
+
return value
|
|
16
|
+
.replace(/&/g, '&')
|
|
17
|
+
.replace(/</g, '<')
|
|
18
|
+
.replace(/>/g, '>')
|
|
19
|
+
.replace(/"/g, '"')
|
|
20
|
+
.replace(/'/g, ''');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type Props = {
|
|
24
|
+
autodetect?: boolean;
|
|
25
|
+
language?: string;
|
|
26
|
+
ignoreIllegals?: boolean;
|
|
27
|
+
code?: string;
|
|
28
|
+
setLanguage?: (language: string) => void;
|
|
29
|
+
} & HTMLAttributes<HTMLDivElement>;
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
autodetect = true,
|
|
33
|
+
language = '',
|
|
34
|
+
setLanguage = () => {},
|
|
35
|
+
ignoreIllegals = true,
|
|
36
|
+
code,
|
|
37
|
+
...restProps
|
|
38
|
+
}: Props = $props();
|
|
39
|
+
|
|
40
|
+
let copying = $state(0);
|
|
41
|
+
let highlightedCode: string = $state('');
|
|
42
|
+
|
|
43
|
+
const cannotDetectLanguage = $derived(!autodetect && !hljs.getLanguage(language));
|
|
44
|
+
|
|
45
|
+
const className = $derived(
|
|
46
|
+
cannotDetectLanguage ? '' : `hljs ${language} ${restProps.class ?? ''}`
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
$effect(() => {
|
|
50
|
+
if (!code) return;
|
|
51
|
+
if (cannotDetectLanguage) {
|
|
52
|
+
highlightedCode = escapeHtml(code);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (autodetect) {
|
|
56
|
+
const result = hljs.highlightAuto(code);
|
|
57
|
+
setLanguage(result.language ?? '');
|
|
58
|
+
highlightedCode = result.value;
|
|
59
|
+
} else {
|
|
60
|
+
const result = hljs.highlight(code, {
|
|
61
|
+
language,
|
|
62
|
+
ignoreIllegals
|
|
63
|
+
});
|
|
64
|
+
highlightedCode = result.value;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
$effect(() => {
|
|
69
|
+
if (codeElement) {
|
|
70
|
+
// eslint-disable-next-line svelte/no-dom-manipulating
|
|
71
|
+
codeElement.innerHTML = highlightedCode;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function onCopy() {
|
|
76
|
+
if (!code) return;
|
|
77
|
+
copy(code);
|
|
78
|
+
copying++;
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
copying--;
|
|
81
|
+
}, 2000);
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<div class="outerWrapper">
|
|
86
|
+
<button class="copyButton" onclick={onCopy} aria-label="Copy code">
|
|
87
|
+
{#if copying}
|
|
88
|
+
<div>
|
|
89
|
+
<svg
|
|
90
|
+
viewBox="0 0 24 24"
|
|
91
|
+
width="14"
|
|
92
|
+
height="14"
|
|
93
|
+
stroke="currentColor"
|
|
94
|
+
stroke-width="1.5"
|
|
95
|
+
stroke-linecap="round"
|
|
96
|
+
stroke-linejoin="round"
|
|
97
|
+
fill="none"
|
|
98
|
+
shape-rendering="geometricPrecision"
|
|
99
|
+
>
|
|
100
|
+
<path d="M20 6L9 17l-5-5" />
|
|
101
|
+
</svg>
|
|
102
|
+
</div>
|
|
103
|
+
{:else}
|
|
104
|
+
<div>
|
|
105
|
+
<svg
|
|
106
|
+
viewBox="0 0 24 24"
|
|
107
|
+
width="14"
|
|
108
|
+
height="14"
|
|
109
|
+
stroke="currentColor"
|
|
110
|
+
stroke-width="1.5"
|
|
111
|
+
stroke-linecap="round"
|
|
112
|
+
stroke-linejoin="round"
|
|
113
|
+
fill="none"
|
|
114
|
+
shape-rendering="geometricPrecision"
|
|
115
|
+
>
|
|
116
|
+
<path
|
|
117
|
+
d="M8 17.929H6c-1.105 0-2-.912-2-2.036V5.036C4 3.91 4.895 3 6 3h8c1.105 0 2 .911 2 2.036v1.866m-6 .17h8c1.105 0 2 .91 2 2.035v10.857C20 21.09 19.105 22 18 22h-8c-1.105 0-2-.911-2-2.036V9.107c0-1.124.895-2.036 2-2.036z"
|
|
118
|
+
/>
|
|
119
|
+
</svg>
|
|
120
|
+
</div>
|
|
121
|
+
{/if}
|
|
122
|
+
</button>
|
|
123
|
+
|
|
124
|
+
<div class="wrapper">
|
|
125
|
+
<div class={`${className} root`}>
|
|
126
|
+
<code bind:this={codeElement}></code>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<style>
|
|
132
|
+
:root {
|
|
133
|
+
--gray0: #fff;
|
|
134
|
+
--gray1: hsl(0, 0%, 99%);
|
|
135
|
+
--gray2: hsl(0, 0%, 97.3%);
|
|
136
|
+
--gray3: hsl(0, 0%, 95.1%);
|
|
137
|
+
--gray4: hsl(0, 0%, 93%);
|
|
138
|
+
--gray5: hsl(0, 0%, 90.9%);
|
|
139
|
+
--gray6: hsl(0, 0%, 88.7%);
|
|
140
|
+
--gray7: hsl(0, 0%, 85.8%);
|
|
141
|
+
--gray8: hsl(0, 0%, 78%);
|
|
142
|
+
--gray9: hsl(0, 0%, 56.1%);
|
|
143
|
+
--gray10: hsl(0, 0%, 52.3%);
|
|
144
|
+
--gray11: hsl(0, 0%, 43.5%);
|
|
145
|
+
--gray12: hsl(0, 0%, 9%);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.root {
|
|
149
|
+
padding: 16px;
|
|
150
|
+
margin: 0;
|
|
151
|
+
background: var(--gray1);
|
|
152
|
+
border-radius: 0;
|
|
153
|
+
position: relative;
|
|
154
|
+
line-height: 17px;
|
|
155
|
+
white-space: pre-wrap;
|
|
156
|
+
background: linear-gradient(to top, var(--gray2), var(--gray1) 16px);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.wrapper {
|
|
160
|
+
overflow: hidden;
|
|
161
|
+
margin: 0;
|
|
162
|
+
position: relative;
|
|
163
|
+
border-radius: 6px;
|
|
164
|
+
margin-top: 16px;
|
|
165
|
+
border: 1px solid var(--gray3);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.copyButton {
|
|
169
|
+
position: absolute;
|
|
170
|
+
top: 12px;
|
|
171
|
+
right: 12px;
|
|
172
|
+
z-index: 1;
|
|
173
|
+
width: 26px;
|
|
174
|
+
height: 26px;
|
|
175
|
+
border: 1px solid var(--gray4);
|
|
176
|
+
border-radius: 6px;
|
|
177
|
+
display: flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
justify-content: center;
|
|
180
|
+
background: var(--gray0);
|
|
181
|
+
cursor: pointer;
|
|
182
|
+
opacity: 0;
|
|
183
|
+
color: var(--gray12);
|
|
184
|
+
transition:
|
|
185
|
+
background 200ms,
|
|
186
|
+
box-shadow 200ms,
|
|
187
|
+
opacity 200ms;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.copyButton:hover {
|
|
191
|
+
background: var(--gray1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.copyButton:focus-visible {
|
|
195
|
+
box-shadow: 0 0 0 1px var(--gray4);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.copyButton > div {
|
|
199
|
+
display: flex;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.outerWrapper {
|
|
203
|
+
position: relative;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.outerWrapper:hover .copyButton {
|
|
207
|
+
opacity: 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
code {
|
|
211
|
+
font-size: 12px;
|
|
212
|
+
}
|
|
213
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import 'highlight.js/styles/github.css';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = {
|
|
4
|
+
autodetect?: boolean;
|
|
5
|
+
language?: string;
|
|
6
|
+
ignoreIllegals?: boolean;
|
|
7
|
+
code?: string;
|
|
8
|
+
setLanguage?: (language: string) => void;
|
|
9
|
+
} & HTMLAttributes<HTMLDivElement>;
|
|
10
|
+
declare const CodeBlock: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type CodeBlock = ReturnType<typeof CodeBlock>;
|
|
12
|
+
export default CodeBlock;
|