@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.
Files changed (133) hide show
  1. package/dist/components/buttons/Button.svelte +33 -0
  2. package/dist/components/buttons/Button.svelte.d.ts +11 -0
  3. package/dist/components/buttons/ButtonGroup.svelte +30 -0
  4. package/dist/components/buttons/ButtonGroup.svelte.d.ts +8 -0
  5. package/dist/components/buttons/FloatingActionButton.svelte +44 -0
  6. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +11 -0
  7. package/dist/components/buttons/IconButton.svelte +53 -0
  8. package/dist/components/buttons/IconButton.svelte.d.ts +13 -0
  9. package/dist/components/buttons/LinkButton.svelte +37 -0
  10. package/dist/components/buttons/LinkButton.svelte.d.ts +12 -0
  11. package/dist/components/buttons/buttonClasses.d.ts +10 -0
  12. package/dist/components/buttons/buttonClasses.js +10 -0
  13. package/dist/components/buttons/index.d.ts +5 -0
  14. package/dist/components/buttons/index.js +5 -0
  15. package/dist/components/cards/Card.svelte +46 -0
  16. package/dist/components/cards/Card.svelte.d.ts +11 -0
  17. package/dist/components/cards/Container.svelte +33 -0
  18. package/dist/components/cards/Container.svelte.d.ts +10 -0
  19. package/dist/components/cards/Divider.svelte +52 -0
  20. package/dist/components/cards/Divider.svelte.d.ts +9 -0
  21. package/dist/components/cards/Grid.svelte +44 -0
  22. package/dist/components/cards/Grid.svelte.d.ts +10 -0
  23. package/dist/components/cards/Panel.svelte +32 -0
  24. package/dist/components/cards/Panel.svelte.d.ts +10 -0
  25. package/dist/components/cards/SectionHeader.svelte +38 -0
  26. package/dist/components/cards/SectionHeader.svelte.d.ts +11 -0
  27. package/dist/components/cards/index.d.ts +6 -0
  28. package/dist/components/cards/index.js +6 -0
  29. package/dist/components/data/Avatar.svelte +67 -0
  30. package/dist/components/data/Avatar.svelte.d.ts +10 -0
  31. package/dist/components/data/Badge.svelte +32 -0
  32. package/dist/components/data/Badge.svelte.d.ts +8 -0
  33. package/dist/components/data/CodeBlock.svelte +121 -0
  34. package/dist/components/data/CodeBlock.svelte.d.ts +32 -0
  35. package/dist/components/data/List.svelte +64 -0
  36. package/dist/components/data/List.svelte.d.ts +8 -0
  37. package/dist/components/data/Pagination.svelte +123 -0
  38. package/dist/components/data/Pagination.svelte.d.ts +9 -0
  39. package/dist/components/data/Stat.svelte +103 -0
  40. package/dist/components/data/Stat.svelte.d.ts +11 -0
  41. package/dist/components/data/Table.svelte +76 -0
  42. package/dist/components/data/Table.svelte.d.ts +9 -0
  43. package/dist/components/data/Tag.svelte +53 -0
  44. package/dist/components/data/Tag.svelte.d.ts +9 -0
  45. package/dist/components/data/index.d.ts +8 -0
  46. package/dist/components/data/index.js +8 -0
  47. package/dist/components/forms/Checkbox.svelte +51 -0
  48. package/dist/components/forms/Checkbox.svelte.d.ts +10 -0
  49. package/dist/components/forms/FileUpload.svelte +164 -0
  50. package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
  51. package/dist/components/forms/Input.svelte +57 -0
  52. package/dist/components/forms/Input.svelte.d.ts +13 -0
  53. package/dist/components/forms/InputGroup.svelte +7 -0
  54. package/dist/components/forms/InputGroup.svelte.d.ts +20 -0
  55. package/dist/components/forms/RadioGroup.svelte +87 -0
  56. package/dist/components/forms/RadioGroup.svelte.d.ts +15 -0
  57. package/dist/components/forms/RangeSlider.svelte +116 -0
  58. package/dist/components/forms/RangeSlider.svelte.d.ts +14 -0
  59. package/dist/components/forms/Select.svelte +71 -0
  60. package/dist/components/forms/Select.svelte.d.ts +16 -0
  61. package/dist/components/forms/Switch.svelte +56 -0
  62. package/dist/components/forms/Switch.svelte.d.ts +10 -0
  63. package/dist/components/forms/Textarea.svelte +57 -0
  64. package/dist/components/forms/Textarea.svelte.d.ts +13 -0
  65. package/dist/components/forms/index.d.ts +9 -0
  66. package/dist/components/forms/index.js +9 -0
  67. package/dist/components/navigation/Breadcrumbs.svelte +59 -0
  68. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +14 -0
  69. package/dist/components/navigation/ContextMenu.svelte +83 -0
  70. package/dist/components/navigation/ContextMenu.svelte.d.ts +11 -0
  71. package/dist/components/navigation/DropdownMenu.svelte +80 -0
  72. package/dist/components/navigation/DropdownMenu.svelte.d.ts +10 -0
  73. package/dist/components/navigation/Menu.svelte +48 -0
  74. package/dist/components/navigation/Menu.svelte.d.ts +15 -0
  75. package/dist/components/navigation/Navbar.svelte +32 -0
  76. package/dist/components/navigation/Navbar.svelte.d.ts +9 -0
  77. package/dist/components/navigation/Sidebar.svelte +35 -0
  78. package/dist/components/navigation/Sidebar.svelte.d.ts +10 -0
  79. package/dist/components/navigation/Tabs.svelte +54 -0
  80. package/dist/components/navigation/Tabs.svelte.d.ts +15 -0
  81. package/dist/components/navigation/index.d.ts +7 -0
  82. package/dist/components/navigation/index.js +7 -0
  83. package/dist/components/overlays/Alert.svelte +99 -0
  84. package/dist/components/overlays/Alert.svelte.d.ts +11 -0
  85. package/dist/components/overlays/CommandPalette.svelte +217 -0
  86. package/dist/components/overlays/CommandPalette.svelte.d.ts +16 -0
  87. package/dist/components/overlays/Drawer.svelte +167 -0
  88. package/dist/components/overlays/Drawer.svelte.d.ts +14 -0
  89. package/dist/components/overlays/Dropdown.svelte +30 -0
  90. package/dist/components/overlays/Dropdown.svelte.d.ts +9 -0
  91. package/dist/components/overlays/Modal.svelte +130 -0
  92. package/dist/components/overlays/Modal.svelte.d.ts +13 -0
  93. package/dist/components/overlays/Popover.svelte +131 -0
  94. package/dist/components/overlays/Popover.svelte.d.ts +11 -0
  95. package/dist/components/overlays/ProgressBar.svelte +45 -0
  96. package/dist/components/overlays/ProgressBar.svelte.d.ts +10 -0
  97. package/dist/components/overlays/SkeletonLoader.svelte +82 -0
  98. package/dist/components/overlays/SkeletonLoader.svelte.d.ts +9 -0
  99. package/dist/components/overlays/Spinner.svelte +43 -0
  100. package/dist/components/overlays/Spinner.svelte.d.ts +7 -0
  101. package/dist/components/overlays/Toast.svelte +140 -0
  102. package/dist/components/overlays/Toast.svelte.d.ts +13 -0
  103. package/dist/components/overlays/Tooltip.svelte +115 -0
  104. package/dist/components/overlays/Tooltip.svelte.d.ts +10 -0
  105. package/dist/components/overlays/index.d.ts +11 -0
  106. package/dist/components/overlays/index.js +11 -0
  107. package/dist/components/typography/Code.svelte +14 -0
  108. package/dist/components/typography/Code.svelte.d.ts +6 -0
  109. package/dist/components/typography/Heading.svelte +22 -0
  110. package/dist/components/typography/Heading.svelte.d.ts +9 -0
  111. package/dist/components/typography/Text.svelte +24 -0
  112. package/dist/components/typography/Text.svelte.d.ts +9 -0
  113. package/dist/components/typography/index.d.ts +3 -0
  114. package/dist/components/typography/index.js +3 -0
  115. package/dist/components/utilities/Accordion.svelte +67 -0
  116. package/dist/components/utilities/Accordion.svelte.d.ts +14 -0
  117. package/dist/components/utilities/Carousel.svelte +152 -0
  118. package/dist/components/utilities/Carousel.svelte.d.ts +16 -0
  119. package/dist/components/utilities/Collapse.svelte +60 -0
  120. package/dist/components/utilities/Collapse.svelte.d.ts +10 -0
  121. package/dist/components/utilities/Portal.svelte +72 -0
  122. package/dist/components/utilities/Portal.svelte.d.ts +21 -0
  123. package/dist/components/utilities/ScrollArea.svelte +41 -0
  124. package/dist/components/utilities/ScrollArea.svelte.d.ts +8 -0
  125. package/dist/components/utilities/index.d.ts +5 -0
  126. package/dist/components/utilities/index.js +5 -0
  127. package/dist/index.d.ts +15 -175
  128. package/dist/index.js +24 -4560
  129. package/dist/lib/internal/id.d.ts +12 -0
  130. package/dist/lib/internal/id.js +15 -0
  131. package/dist/theme.css +218 -0
  132. package/package.json +14 -7
  133. 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,6 @@
1
+ interface Props {
2
+ children?: import('svelte').Snippet;
3
+ }
4
+ declare const Code: import("svelte").Component<Props, {}, "">;
5
+ type Code = ReturnType<typeof Code>;
6
+ export default 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,3 @@
1
+ export { default as Heading } from './Heading.svelte';
2
+ export { default as Text } from './Text.svelte';
3
+ export { default as Code } from './Code.svelte';
@@ -0,0 +1,3 @@
1
+ export { default as Heading } from './Heading.svelte';
2
+ export { default as Text } from './Text.svelte';
3
+ export { default as Code } from './Code.svelte';
@@ -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;