@mrintel/villain-ui 0.2.0 → 0.3.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/dist/components/buttons/Button.svelte +33 -0
- package/dist/components/buttons/Button.svelte.d.ts +11 -0
- package/dist/components/buttons/ButtonGroup.svelte +30 -0
- package/dist/components/buttons/ButtonGroup.svelte.d.ts +8 -0
- package/dist/components/buttons/FloatingActionButton.svelte +44 -0
- package/dist/components/buttons/FloatingActionButton.svelte.d.ts +11 -0
- package/dist/components/buttons/IconButton.svelte +53 -0
- package/dist/components/buttons/IconButton.svelte.d.ts +13 -0
- package/dist/components/buttons/LinkButton.svelte +37 -0
- package/dist/components/buttons/LinkButton.svelte.d.ts +12 -0
- package/dist/components/buttons/buttonClasses.d.ts +10 -0
- package/dist/components/buttons/buttonClasses.js +10 -0
- package/dist/components/buttons/index.d.ts +5 -0
- package/dist/components/buttons/index.js +5 -0
- package/dist/components/cards/Card.svelte +46 -0
- package/dist/components/cards/Card.svelte.d.ts +11 -0
- package/dist/components/cards/Container.svelte +33 -0
- package/dist/components/cards/Container.svelte.d.ts +10 -0
- package/dist/components/cards/Divider.svelte +52 -0
- package/dist/components/cards/Divider.svelte.d.ts +9 -0
- package/dist/components/cards/Grid.svelte +44 -0
- package/dist/components/cards/Grid.svelte.d.ts +10 -0
- package/dist/components/cards/Panel.svelte +32 -0
- package/dist/components/cards/Panel.svelte.d.ts +10 -0
- package/dist/components/cards/SectionHeader.svelte +38 -0
- package/dist/components/cards/SectionHeader.svelte.d.ts +11 -0
- package/dist/components/cards/index.d.ts +6 -0
- package/dist/components/cards/index.js +6 -0
- package/dist/components/data/Avatar.svelte +67 -0
- package/dist/components/data/Avatar.svelte.d.ts +10 -0
- package/dist/components/data/Badge.svelte +32 -0
- package/dist/components/data/Badge.svelte.d.ts +8 -0
- package/dist/components/data/CodeBlock.svelte +121 -0
- package/dist/components/data/CodeBlock.svelte.d.ts +32 -0
- package/dist/components/data/List.svelte +64 -0
- package/dist/components/data/List.svelte.d.ts +8 -0
- package/dist/components/data/Pagination.svelte +123 -0
- package/dist/components/data/Pagination.svelte.d.ts +9 -0
- package/dist/components/data/Stat.svelte +103 -0
- package/dist/components/data/Stat.svelte.d.ts +11 -0
- package/dist/components/data/Table.svelte +76 -0
- package/dist/components/data/Table.svelte.d.ts +9 -0
- package/dist/components/data/Tag.svelte +53 -0
- package/dist/components/data/Tag.svelte.d.ts +9 -0
- package/dist/components/data/index.d.ts +8 -0
- package/dist/components/data/index.js +8 -0
- package/dist/components/forms/Checkbox.svelte +51 -0
- package/dist/components/forms/Checkbox.svelte.d.ts +10 -0
- package/dist/components/forms/FileUpload.svelte +164 -0
- package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
- package/dist/components/forms/Input.svelte +57 -0
- package/dist/components/forms/Input.svelte.d.ts +13 -0
- package/dist/components/forms/InputGroup.svelte +7 -0
- package/dist/components/forms/InputGroup.svelte.d.ts +20 -0
- package/dist/components/forms/RadioGroup.svelte +87 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts +15 -0
- package/dist/components/forms/RangeSlider.svelte +116 -0
- package/dist/components/forms/RangeSlider.svelte.d.ts +14 -0
- package/dist/components/forms/Select.svelte +71 -0
- package/dist/components/forms/Select.svelte.d.ts +16 -0
- package/dist/components/forms/Switch.svelte +56 -0
- package/dist/components/forms/Switch.svelte.d.ts +10 -0
- package/dist/components/forms/Textarea.svelte +57 -0
- package/dist/components/forms/Textarea.svelte.d.ts +13 -0
- package/dist/components/forms/index.d.ts +9 -0
- package/dist/components/forms/index.js +9 -0
- package/dist/components/navigation/Breadcrumbs.svelte +59 -0
- package/dist/components/navigation/Breadcrumbs.svelte.d.ts +14 -0
- package/dist/components/navigation/ContextMenu.svelte +83 -0
- package/dist/components/navigation/ContextMenu.svelte.d.ts +11 -0
- package/dist/components/navigation/DropdownMenu.svelte +80 -0
- package/dist/components/navigation/DropdownMenu.svelte.d.ts +10 -0
- package/dist/components/navigation/Menu.svelte +48 -0
- package/dist/components/navigation/Menu.svelte.d.ts +15 -0
- package/dist/components/navigation/Navbar.svelte +32 -0
- package/dist/components/navigation/Navbar.svelte.d.ts +9 -0
- package/dist/components/navigation/Sidebar.svelte +35 -0
- package/dist/components/navigation/Sidebar.svelte.d.ts +10 -0
- package/dist/components/navigation/Tabs.svelte +54 -0
- package/dist/components/navigation/Tabs.svelte.d.ts +15 -0
- package/dist/components/navigation/index.d.ts +7 -0
- package/dist/components/navigation/index.js +7 -0
- package/dist/components/overlays/Alert.svelte +99 -0
- package/dist/components/overlays/Alert.svelte.d.ts +11 -0
- package/dist/components/overlays/CommandPalette.svelte +217 -0
- package/dist/components/overlays/CommandPalette.svelte.d.ts +16 -0
- package/dist/components/overlays/Drawer.svelte +167 -0
- package/dist/components/overlays/Drawer.svelte.d.ts +14 -0
- package/dist/components/overlays/Dropdown.svelte +30 -0
- package/dist/components/overlays/Dropdown.svelte.d.ts +9 -0
- package/dist/components/overlays/Modal.svelte +130 -0
- package/dist/components/overlays/Modal.svelte.d.ts +13 -0
- package/dist/components/overlays/Popover.svelte +131 -0
- package/dist/components/overlays/Popover.svelte.d.ts +11 -0
- package/dist/components/overlays/ProgressBar.svelte +45 -0
- package/dist/components/overlays/ProgressBar.svelte.d.ts +10 -0
- package/dist/components/overlays/SkeletonLoader.svelte +82 -0
- package/dist/components/overlays/SkeletonLoader.svelte.d.ts +9 -0
- package/dist/components/overlays/Spinner.svelte +43 -0
- package/dist/components/overlays/Spinner.svelte.d.ts +7 -0
- package/dist/components/overlays/Toast.svelte +140 -0
- package/dist/components/overlays/Toast.svelte.d.ts +13 -0
- package/dist/components/overlays/Tooltip.svelte +115 -0
- package/dist/components/overlays/Tooltip.svelte.d.ts +10 -0
- package/dist/components/overlays/index.d.ts +11 -0
- package/dist/components/overlays/index.js +11 -0
- package/dist/components/typography/Code.svelte +14 -0
- package/dist/components/typography/Code.svelte.d.ts +6 -0
- package/dist/components/typography/Heading.svelte +22 -0
- package/dist/components/typography/Heading.svelte.d.ts +9 -0
- package/dist/components/typography/Text.svelte +24 -0
- package/dist/components/typography/Text.svelte.d.ts +9 -0
- package/dist/components/typography/index.d.ts +3 -0
- package/dist/components/typography/index.js +3 -0
- package/dist/components/utilities/Accordion.svelte +67 -0
- package/dist/components/utilities/Accordion.svelte.d.ts +14 -0
- package/dist/components/utilities/Carousel.svelte +152 -0
- package/dist/components/utilities/Carousel.svelte.d.ts +16 -0
- package/dist/components/utilities/Collapse.svelte +60 -0
- package/dist/components/utilities/Collapse.svelte.d.ts +10 -0
- package/dist/components/utilities/Portal.svelte +72 -0
- package/dist/components/utilities/Portal.svelte.d.ts +21 -0
- package/dist/components/utilities/ScrollArea.svelte +41 -0
- package/dist/components/utilities/ScrollArea.svelte.d.ts +8 -0
- package/dist/components/utilities/index.d.ts +5 -0
- package/dist/components/utilities/index.js +5 -0
- package/dist/index.d.ts +15 -175
- package/dist/index.js +24 -4560
- package/dist/lib/internal/id.d.ts +12 -0
- package/dist/lib/internal/id.js +15 -0
- package/dist/theme.css +218 -0
- package/package.json +14 -7
- package/dist/index.css +0 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { createId } from '../../lib/internal/id.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
content: string;
|
|
7
|
+
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
8
|
+
delay?: number;
|
|
9
|
+
trigger?: Snippet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
content,
|
|
14
|
+
placement = 'top',
|
|
15
|
+
delay = 200,
|
|
16
|
+
trigger
|
|
17
|
+
}: Props = $props();
|
|
18
|
+
|
|
19
|
+
let visible = $state(false);
|
|
20
|
+
let actualPlacement = $state(placement);
|
|
21
|
+
let tooltipElement = $state<HTMLDivElement>();
|
|
22
|
+
let timeoutId: number | undefined;
|
|
23
|
+
|
|
24
|
+
const tooltipId = createId('tooltip');
|
|
25
|
+
|
|
26
|
+
const placementClasses = {
|
|
27
|
+
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
|
|
28
|
+
bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
|
|
29
|
+
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
|
|
30
|
+
right: 'left-full top-1/2 -translate-y-1/2 ml-2'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const oppositePlacement = {
|
|
34
|
+
top: 'bottom',
|
|
35
|
+
bottom: 'top',
|
|
36
|
+
left: 'right',
|
|
37
|
+
right: 'left'
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
function handleMouseEnter() {
|
|
41
|
+
if (typeof window === 'undefined') return;
|
|
42
|
+
timeoutId = window.setTimeout(() => {
|
|
43
|
+
visible = true;
|
|
44
|
+
}, delay);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleMouseLeave() {
|
|
48
|
+
if (timeoutId) {
|
|
49
|
+
clearTimeout(timeoutId);
|
|
50
|
+
}
|
|
51
|
+
visible = false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function handleFocus() {
|
|
55
|
+
handleMouseEnter();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function handleBlur() {
|
|
59
|
+
handleMouseLeave();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Reset actualPlacement when visibility changes
|
|
63
|
+
$effect(() => {
|
|
64
|
+
if (!visible) {
|
|
65
|
+
actualPlacement = placement;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Check viewport bounds and flip placement if needed
|
|
70
|
+
$effect(() => {
|
|
71
|
+
if (typeof window === 'undefined') return;
|
|
72
|
+
|
|
73
|
+
if (visible && tooltipElement) {
|
|
74
|
+
const rect = tooltipElement.getBoundingClientRect();
|
|
75
|
+
const viewportWidth = window.innerWidth;
|
|
76
|
+
const viewportHeight = window.innerHeight;
|
|
77
|
+
|
|
78
|
+
// Determine if current placement overflows and flip if needed
|
|
79
|
+
if (actualPlacement === 'top' && rect.top < 0 && actualPlacement !== oppositePlacement[placement]) {
|
|
80
|
+
actualPlacement = 'bottom';
|
|
81
|
+
} else if (actualPlacement === 'bottom' && rect.bottom > viewportHeight && actualPlacement !== oppositePlacement[placement]) {
|
|
82
|
+
actualPlacement = 'top';
|
|
83
|
+
} else if (actualPlacement === 'left' && rect.left < 0 && actualPlacement !== oppositePlacement[placement]) {
|
|
84
|
+
actualPlacement = 'right';
|
|
85
|
+
} else if (actualPlacement === 'right' && rect.right > viewportWidth && actualPlacement !== oppositePlacement[placement]) {
|
|
86
|
+
actualPlacement = 'left';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<div class="relative inline-block">
|
|
93
|
+
<div
|
|
94
|
+
aria-describedby={visible ? tooltipId : undefined}
|
|
95
|
+
onmouseenter={handleMouseEnter}
|
|
96
|
+
onmouseleave={handleMouseLeave}
|
|
97
|
+
onfocus={handleFocus}
|
|
98
|
+
onblur={handleBlur}
|
|
99
|
+
role="presentation"
|
|
100
|
+
class="contents"
|
|
101
|
+
>
|
|
102
|
+
{@render trigger?.()}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{#if visible}
|
|
106
|
+
<div
|
|
107
|
+
bind:this={tooltipElement}
|
|
108
|
+
id={tooltipId}
|
|
109
|
+
role="tooltip"
|
|
110
|
+
class="absolute {placementClasses[actualPlacement]} z-50 glass-panel rounded-md px-3 py-2 text-sm text-text whitespace-nowrap animate-[fade-in_0.15s_var(--ease-luxe)] pointer-events-none"
|
|
111
|
+
>
|
|
112
|
+
{content}
|
|
113
|
+
</div>
|
|
114
|
+
{/if}
|
|
115
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
content: string;
|
|
4
|
+
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
5
|
+
delay?: number;
|
|
6
|
+
trigger?: Snippet;
|
|
7
|
+
}
|
|
8
|
+
declare const Tooltip: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Tooltip = ReturnType<typeof Tooltip>;
|
|
10
|
+
export default Tooltip;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { default as Modal } from './Modal.svelte';
|
|
2
|
+
export { default as Alert } from './Alert.svelte';
|
|
3
|
+
export { default as Spinner } from './Spinner.svelte';
|
|
4
|
+
export { default as Tooltip } from './Tooltip.svelte';
|
|
5
|
+
export { default as ProgressBar } from './ProgressBar.svelte';
|
|
6
|
+
export { default as SkeletonLoader } from './SkeletonLoader.svelte';
|
|
7
|
+
export { default as Toast } from './Toast.svelte';
|
|
8
|
+
export { default as Drawer } from './Drawer.svelte';
|
|
9
|
+
export { default as Popover } from './Popover.svelte';
|
|
10
|
+
export { default as Dropdown } from './Dropdown.svelte';
|
|
11
|
+
export { default as CommandPalette } from './CommandPalette.svelte';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { default as Modal } from './Modal.svelte';
|
|
2
|
+
export { default as Alert } from './Alert.svelte';
|
|
3
|
+
export { default as Spinner } from './Spinner.svelte';
|
|
4
|
+
export { default as Tooltip } from './Tooltip.svelte';
|
|
5
|
+
export { default as ProgressBar } from './ProgressBar.svelte';
|
|
6
|
+
export { default as SkeletonLoader } from './SkeletonLoader.svelte';
|
|
7
|
+
export { default as Toast } from './Toast.svelte';
|
|
8
|
+
export { default as Drawer } from './Drawer.svelte';
|
|
9
|
+
export { default as Popover } from './Popover.svelte';
|
|
10
|
+
export { default as Dropdown } from './Dropdown.svelte';
|
|
11
|
+
export { default as CommandPalette } from './CommandPalette.svelte';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
children?: import('svelte').Snippet;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let { children }: Props = $props();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<code
|
|
10
|
+
class="inline-flex items-center transition-all duration-300 hover:shadow-[0_0_12px_rgba(127,61,255,0.3)]"
|
|
11
|
+
style="background: rgba(127, 61, 255, 0.1); border: 1px solid var(--color-border); padding: 0.125rem 0.375rem; border-radius: var(--radius-sm); font-family: var(--font-mono); color: var(--color-accent-soft); font-size: 0.9em;"
|
|
12
|
+
>
|
|
13
|
+
{@render children?.()}
|
|
14
|
+
</code>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
4
|
+
glow?: boolean;
|
|
5
|
+
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
6
|
+
children?: import('svelte').Snippet;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { level = 1, glow = false, as, children }: Props = $props();
|
|
10
|
+
|
|
11
|
+
const element = $derived(as ?? `h${level}`);
|
|
12
|
+
const baseClasses = $derived(
|
|
13
|
+
`transition-all duration-300 ${glow ? 'text-glow' : ''}`
|
|
14
|
+
);
|
|
15
|
+
const styles = $derived(
|
|
16
|
+
`font-size: var(--text-h${level}-size); line-height: var(--text-h${level}-line-height); font-weight: var(--text-h${level}-weight); font-family: var(--font-heading); color: var(--color-text);`
|
|
17
|
+
);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<svelte:element this={element} class={baseClasses} style={styles}>
|
|
21
|
+
{@render children?.()}
|
|
22
|
+
</svelte:element>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
3
|
+
glow?: boolean;
|
|
4
|
+
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const Heading: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type Heading = ReturnType<typeof Heading>;
|
|
9
|
+
export default Heading;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'body' | 'caption';
|
|
4
|
+
color?: 'default' | 'soft' | 'muted';
|
|
5
|
+
as?: 'p' | 'span' | 'div';
|
|
6
|
+
children?: import('svelte').Snippet;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { variant = 'body', color = 'default', as = 'p', children }: Props = $props();
|
|
10
|
+
|
|
11
|
+
const colorMap = {
|
|
12
|
+
default: 'var(--color-text)',
|
|
13
|
+
soft: 'var(--color-text-soft)',
|
|
14
|
+
muted: 'var(--color-text-muted)'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const styles = $derived(
|
|
18
|
+
`font-size: var(--text-${variant}-size); line-height: var(--text-${variant}-line-height); font-weight: var(--text-${variant}-weight); font-family: var(--font-body); color: ${colorMap[color]};`
|
|
19
|
+
);
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<svelte:element this={as} class="transition-colors duration-300" style={styles}>
|
|
23
|
+
{@render children?.()}
|
|
24
|
+
</svelte:element>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
variant?: 'body' | 'caption';
|
|
3
|
+
color?: 'default' | 'soft' | 'muted';
|
|
4
|
+
as?: 'p' | 'span' | 'div';
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const Text: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type Text = ReturnType<typeof Text>;
|
|
9
|
+
export default Text;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Collapse from './Collapse.svelte';
|
|
4
|
+
|
|
5
|
+
interface AccordionItem {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
content: string | Snippet;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
items: AccordionItem[];
|
|
13
|
+
openItems?: string | string[];
|
|
14
|
+
mode?: 'single' | 'multiple';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { items, openItems = $bindable([]), mode = 'single' }: Props = $props();
|
|
18
|
+
|
|
19
|
+
// Normalize openItems to array
|
|
20
|
+
let openItemsArray = $derived(
|
|
21
|
+
mode === 'single'
|
|
22
|
+
? typeof openItems === 'string'
|
|
23
|
+
? [openItems]
|
|
24
|
+
: []
|
|
25
|
+
: Array.isArray(openItems)
|
|
26
|
+
? openItems
|
|
27
|
+
: []
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
function isItemOpen(id: string): boolean {
|
|
31
|
+
return openItemsArray.includes(id);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toggleItem(id: string) {
|
|
35
|
+
if (mode === 'single') {
|
|
36
|
+
// Single mode: only one item open at a time
|
|
37
|
+
if (isItemOpen(id)) {
|
|
38
|
+
openItems = '';
|
|
39
|
+
} else {
|
|
40
|
+
openItems = id;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
// Multiple mode: toggle independently
|
|
44
|
+
if (isItemOpen(id)) {
|
|
45
|
+
openItems = openItemsArray.filter(itemId => itemId !== id);
|
|
46
|
+
} else {
|
|
47
|
+
openItems = [...openItemsArray, id];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<div class="space-y-2">
|
|
54
|
+
{#each items as item (item.id)}
|
|
55
|
+
<Collapse
|
|
56
|
+
title={item.title}
|
|
57
|
+
open={isItemOpen(item.id)}
|
|
58
|
+
onToggle={() => toggleItem(item.id)}
|
|
59
|
+
>
|
|
60
|
+
{#if typeof item.content === 'string'}
|
|
61
|
+
{item.content}
|
|
62
|
+
{:else}
|
|
63
|
+
{@render item.content()}
|
|
64
|
+
{/if}
|
|
65
|
+
</Collapse>
|
|
66
|
+
{/each}
|
|
67
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface AccordionItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
content: string | Snippet;
|
|
6
|
+
}
|
|
7
|
+
interface Props {
|
|
8
|
+
items: AccordionItem[];
|
|
9
|
+
openItems?: string | string[];
|
|
10
|
+
mode?: 'single' | 'multiple';
|
|
11
|
+
}
|
|
12
|
+
declare const Accordion: import("svelte").Component<Props, {}, "openItems">;
|
|
13
|
+
type Accordion = ReturnType<typeof Accordion>;
|
|
14
|
+
export default Accordion;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface CarouselItem {
|
|
5
|
+
id: string;
|
|
6
|
+
content: Snippet | string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
items: CarouselItem[];
|
|
11
|
+
currentIndex?: number;
|
|
12
|
+
autoplay?: boolean;
|
|
13
|
+
autoplayInterval?: number;
|
|
14
|
+
showDots?: boolean;
|
|
15
|
+
showArrows?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
items,
|
|
20
|
+
currentIndex = $bindable(0),
|
|
21
|
+
autoplay = false,
|
|
22
|
+
autoplayInterval = 3000,
|
|
23
|
+
showDots = true,
|
|
24
|
+
showArrows = true
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
let startX = $state(0);
|
|
28
|
+
let currentX = $state(0);
|
|
29
|
+
let isDragging = $state(false);
|
|
30
|
+
|
|
31
|
+
function goToNext() {
|
|
32
|
+
if (!items.length) return;
|
|
33
|
+
currentIndex = (currentIndex + 1) % items.length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function goToPrev() {
|
|
37
|
+
if (!items.length) return;
|
|
38
|
+
currentIndex = (currentIndex - 1 + items.length) % items.length;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function goToIndex(index: number) {
|
|
42
|
+
if (items.length === 0 || index < 0 || index >= items.length) return;
|
|
43
|
+
currentIndex = index;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function handleTouchStart(event: TouchEvent) {
|
|
47
|
+
isDragging = true;
|
|
48
|
+
startX = event.touches[0].clientX;
|
|
49
|
+
currentX = startX;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function handleTouchMove(event: TouchEvent) {
|
|
53
|
+
if (!isDragging) return;
|
|
54
|
+
currentX = event.touches[0].clientX;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function handleTouchEnd() {
|
|
58
|
+
if (!isDragging) return;
|
|
59
|
+
if (!items.length) {
|
|
60
|
+
isDragging = false;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
isDragging = false;
|
|
64
|
+
|
|
65
|
+
const diff = startX - currentX;
|
|
66
|
+
const threshold = 50;
|
|
67
|
+
|
|
68
|
+
if (diff > threshold) {
|
|
69
|
+
goToNext();
|
|
70
|
+
} else if (diff < -threshold) {
|
|
71
|
+
goToPrev();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
startX = 0;
|
|
75
|
+
currentX = 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
$effect(() => {
|
|
79
|
+
if (!autoplay || items.length < 2) return;
|
|
80
|
+
|
|
81
|
+
const interval = setInterval(goToNext, autoplayInterval);
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
clearInterval(interval);
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Clamp currentIndex when items change
|
|
89
|
+
$effect(() => {
|
|
90
|
+
if (items.length && (currentIndex < 0 || currentIndex >= items.length)) {
|
|
91
|
+
currentIndex = 0;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<div class="glass-panel rounded-lg overflow-hidden relative">
|
|
97
|
+
<div
|
|
98
|
+
class="flex transition-transform duration-300 ease-luxe"
|
|
99
|
+
style="transform: translateX(-{currentIndex * 100}%);"
|
|
100
|
+
ontouchstart={handleTouchStart}
|
|
101
|
+
ontouchmove={handleTouchMove}
|
|
102
|
+
ontouchend={handleTouchEnd}
|
|
103
|
+
>
|
|
104
|
+
{#each items as item (item.id)}
|
|
105
|
+
<div class="min-w-full flex items-center justify-center p-8">
|
|
106
|
+
{#if typeof item.content === 'string'}
|
|
107
|
+
<div class="text-text">{item.content}</div>
|
|
108
|
+
{:else}
|
|
109
|
+
{@render item.content()}
|
|
110
|
+
{/if}
|
|
111
|
+
</div>
|
|
112
|
+
{/each}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{#if showArrows && items.length}
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
onclick={goToPrev}
|
|
119
|
+
class="absolute left-4 top-1/2 -translate-y-1/2 glass-panel rounded-full p-2 text-text hover:accent-glow transition-all"
|
|
120
|
+
aria-label="Previous item"
|
|
121
|
+
>
|
|
122
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
123
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
124
|
+
</svg>
|
|
125
|
+
</button>
|
|
126
|
+
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
onclick={goToNext}
|
|
130
|
+
class="absolute right-4 top-1/2 -translate-y-1/2 glass-panel rounded-full p-2 text-text hover:accent-glow transition-all"
|
|
131
|
+
aria-label="Next item"
|
|
132
|
+
>
|
|
133
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
134
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
135
|
+
</svg>
|
|
136
|
+
</button>
|
|
137
|
+
{/if}
|
|
138
|
+
|
|
139
|
+
{#if showDots && items.length}
|
|
140
|
+
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
|
|
141
|
+
{#each items as item, index (item.id)}
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
onclick={() => goToIndex(index)}
|
|
145
|
+
class="w-2 h-2 rounded-full transition-all {index === currentIndex ? 'accent-glow bg-accent w-8' : 'bg-text-muted'}"
|
|
146
|
+
aria-label="Go to item {index + 1}"
|
|
147
|
+
>
|
|
148
|
+
</button>
|
|
149
|
+
{/each}
|
|
150
|
+
</div>
|
|
151
|
+
{/if}
|
|
152
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface CarouselItem {
|
|
3
|
+
id: string;
|
|
4
|
+
content: Snippet | string;
|
|
5
|
+
}
|
|
6
|
+
interface Props {
|
|
7
|
+
items: CarouselItem[];
|
|
8
|
+
currentIndex?: number;
|
|
9
|
+
autoplay?: boolean;
|
|
10
|
+
autoplayInterval?: number;
|
|
11
|
+
showDots?: boolean;
|
|
12
|
+
showArrows?: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare const Carousel: import("svelte").Component<Props, {}, "currentIndex">;
|
|
15
|
+
type Carousel = ReturnType<typeof Carousel>;
|
|
16
|
+
export default Carousel;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { createId } from '../../lib/internal/id.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
open: boolean;
|
|
7
|
+
title: string;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
onToggle?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { open, title, children, onToggle }: Props = $props();
|
|
13
|
+
|
|
14
|
+
let contentElement = $state<HTMLDivElement>();
|
|
15
|
+
let openLocal = $state(open);
|
|
16
|
+
|
|
17
|
+
const headerId = createId('collapse-header');
|
|
18
|
+
const contentId = createId('collapse-content');
|
|
19
|
+
|
|
20
|
+
function toggleLocal() {
|
|
21
|
+
openLocal = !openLocal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let effectiveOpen = $derived(onToggle ? open : openLocal);
|
|
25
|
+
let maxHeight = $derived(effectiveOpen && contentElement ? `${contentElement.scrollHeight}px` : '0px');
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<div class="border border-border rounded-md overflow-hidden {effectiveOpen ? 'glass-panel' : ''}">
|
|
29
|
+
<button
|
|
30
|
+
id={headerId}
|
|
31
|
+
type="button"
|
|
32
|
+
onclick={onToggle ?? toggleLocal}
|
|
33
|
+
class="w-full flex items-center justify-between p-4 text-left text-text font-medium hover:bg-base-3 transition-colors"
|
|
34
|
+
aria-expanded={effectiveOpen}
|
|
35
|
+
aria-controls={contentId}
|
|
36
|
+
>
|
|
37
|
+
<span>{title}</span>
|
|
38
|
+
<svg
|
|
39
|
+
class="w-5 h-5 transition-transform duration-300 ease-luxe {effectiveOpen ? 'rotate-180' : ''}"
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
viewBox="0 0 24 24"
|
|
43
|
+
>
|
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
45
|
+
</svg>
|
|
46
|
+
</button>
|
|
47
|
+
|
|
48
|
+
<div
|
|
49
|
+
bind:this={contentElement}
|
|
50
|
+
id={contentId}
|
|
51
|
+
class="overflow-hidden transition-[max-height] duration-300 ease-luxe"
|
|
52
|
+
style="max-height: {maxHeight};"
|
|
53
|
+
role="region"
|
|
54
|
+
aria-labelledby={headerId}
|
|
55
|
+
>
|
|
56
|
+
<div class="p-4 text-text-soft border-t border-border">
|
|
57
|
+
{@render children?.()}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
open: boolean;
|
|
4
|
+
title: string;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
onToggle?: () => void;
|
|
7
|
+
}
|
|
8
|
+
declare const Collapse: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Collapse = ReturnType<typeof Collapse>;
|
|
10
|
+
export default Collapse;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Portal component - teleports content to a different DOM location.
|
|
6
|
+
*
|
|
7
|
+
* **Focus Management**: This component handles DOM manipulation only.
|
|
8
|
+
* For focus-sensitive content (modals, drawers, toasts), the parent component
|
|
9
|
+
* should manage focus using the patterns from Modal.svelte and Drawer.svelte:
|
|
10
|
+
* - Store previousFocus before opening
|
|
11
|
+
* - Focus appropriate element after mount
|
|
12
|
+
* - Restore focus on cleanup
|
|
13
|
+
*
|
|
14
|
+
* **Stacking Coordination**: Multiple portals can coexist. For z-index stacking,
|
|
15
|
+
* manage via CSS classes on the portal children or implement a portal manager.
|
|
16
|
+
*/
|
|
17
|
+
interface Props {
|
|
18
|
+
target?: HTMLElement | string;
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { target = 'body', children }: Props = $props();
|
|
23
|
+
|
|
24
|
+
let mountElement = $state<HTMLElement | null>(null);
|
|
25
|
+
|
|
26
|
+
function portal(node: HTMLElement, currentTarget: HTMLElement) {
|
|
27
|
+
currentTarget.appendChild(node);
|
|
28
|
+
return {
|
|
29
|
+
update(newTarget: HTMLElement) {
|
|
30
|
+
// Handle target prop changes by moving node to new parent
|
|
31
|
+
if (newTarget !== currentTarget) {
|
|
32
|
+
newTarget.appendChild(node);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
destroy() {
|
|
36
|
+
if (node.parentNode) {
|
|
37
|
+
node.parentNode.removeChild(node);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$effect(() => {
|
|
44
|
+
if (typeof document === 'undefined') return;
|
|
45
|
+
|
|
46
|
+
// Resolve target element
|
|
47
|
+
const targetElement = typeof target === 'string'
|
|
48
|
+
? document.querySelector<HTMLElement>(target)
|
|
49
|
+
: target;
|
|
50
|
+
|
|
51
|
+
if (!targetElement) return;
|
|
52
|
+
|
|
53
|
+
// Create container div
|
|
54
|
+
const container = document.createElement('div');
|
|
55
|
+
targetElement.appendChild(container);
|
|
56
|
+
mountElement = container;
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
// Cleanup: remove container from DOM
|
|
60
|
+
container.remove();
|
|
61
|
+
mountElement = null;
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
{#if mountElement}
|
|
67
|
+
{#if children}
|
|
68
|
+
<div use:portal={mountElement}>
|
|
69
|
+
{@render children()}
|
|
70
|
+
</div>
|
|
71
|
+
{/if}
|
|
72
|
+
{/if}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* Portal component - teleports content to a different DOM location.
|
|
4
|
+
*
|
|
5
|
+
* **Focus Management**: This component handles DOM manipulation only.
|
|
6
|
+
* For focus-sensitive content (modals, drawers, toasts), the parent component
|
|
7
|
+
* should manage focus using the patterns from Modal.svelte and Drawer.svelte:
|
|
8
|
+
* - Store previousFocus before opening
|
|
9
|
+
* - Focus appropriate element after mount
|
|
10
|
+
* - Restore focus on cleanup
|
|
11
|
+
*
|
|
12
|
+
* **Stacking Coordination**: Multiple portals can coexist. For z-index stacking,
|
|
13
|
+
* manage via CSS classes on the portal children or implement a portal manager.
|
|
14
|
+
*/
|
|
15
|
+
interface Props {
|
|
16
|
+
target?: HTMLElement | string;
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
}
|
|
19
|
+
declare const Portal: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type Portal = ReturnType<typeof Portal>;
|
|
21
|
+
export default Portal;
|