@mrintel/villain-ui 0.3.0 → 0.6.3

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 (128) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3490 -1296
  3. package/dist/components/buttons/Button.svelte +27 -33
  4. package/dist/components/buttons/Button.svelte.d.ts +4 -1
  5. package/dist/components/buttons/ButtonGroup.svelte +17 -30
  6. package/dist/components/buttons/FloatingActionButton.svelte +20 -44
  7. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
  8. package/dist/components/buttons/IconButton.svelte +23 -53
  9. package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
  10. package/dist/components/buttons/LinkButton.svelte +24 -37
  11. package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
  12. package/dist/components/buttons/buttonClasses.d.ts +5 -0
  13. package/dist/components/buttons/buttonClasses.js +8 -3
  14. package/dist/components/cards/Card.svelte +60 -46
  15. package/dist/components/cards/Card.svelte.d.ts +6 -2
  16. package/dist/components/cards/Container.svelte +17 -33
  17. package/dist/components/cards/Divider.svelte +36 -52
  18. package/dist/components/cards/Divider.svelte.d.ts +2 -0
  19. package/dist/components/cards/Grid.svelte +55 -44
  20. package/dist/components/cards/Panel.svelte +18 -32
  21. package/dist/components/cards/Panel.svelte.d.ts +2 -1
  22. package/dist/components/cards/SectionHeader.svelte +24 -38
  23. package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
  24. package/dist/components/data/Avatar.svelte +48 -67
  25. package/dist/components/data/Badge.svelte +45 -32
  26. package/dist/components/data/Badge.svelte.d.ts +7 -1
  27. package/dist/components/data/CalendarGrid.svelte +433 -0
  28. package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
  29. package/dist/components/data/CalendarGrid.types.d.ts +7 -0
  30. package/dist/components/data/CalendarGrid.types.js +1 -0
  31. package/dist/components/data/CodeBlock.svelte +119 -121
  32. package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
  33. package/dist/components/data/List.svelte +87 -64
  34. package/dist/components/data/List.svelte.d.ts +7 -0
  35. package/dist/components/data/Pagination.svelte +121 -123
  36. package/dist/components/data/Pagination.svelte.d.ts +5 -0
  37. package/dist/components/data/Sparkline.svelte +117 -0
  38. package/dist/components/data/Sparkline.svelte.d.ts +43 -0
  39. package/dist/components/data/Stat.svelte +92 -103
  40. package/dist/components/data/Table.svelte +443 -76
  41. package/dist/components/data/Table.svelte.d.ts +23 -2
  42. package/dist/components/data/Table.types.d.ts +14 -0
  43. package/dist/components/data/Table.types.js +1 -0
  44. package/dist/components/data/Tag.svelte +51 -53
  45. package/dist/components/data/Tag.svelte.d.ts +5 -1
  46. package/dist/components/data/index.d.ts +4 -0
  47. package/dist/components/data/index.js +2 -0
  48. package/dist/components/forms/Checkbox.svelte +39 -51
  49. package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
  50. package/dist/components/forms/DatePicker.svelte +61 -0
  51. package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
  52. package/dist/components/forms/DateTimePicker.svelte +63 -0
  53. package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
  54. package/dist/components/forms/FileUpload.svelte +136 -164
  55. package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
  56. package/dist/components/forms/Input.svelte +282 -57
  57. package/dist/components/forms/Input.svelte.d.ts +9 -3
  58. package/dist/components/forms/InputGroup.svelte +7 -7
  59. package/dist/components/forms/RadioGroup.svelte +77 -87
  60. package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
  61. package/dist/components/forms/RangeSlider.svelte +90 -116
  62. package/dist/components/forms/Select.svelte +106 -71
  63. package/dist/components/forms/Select.svelte.d.ts +3 -1
  64. package/dist/components/forms/Switch.svelte +44 -56
  65. package/dist/components/forms/Switch.svelte.d.ts +3 -1
  66. package/dist/components/forms/Textarea.svelte +52 -57
  67. package/dist/components/forms/Textarea.svelte.d.ts +3 -1
  68. package/dist/components/forms/TimePicker.svelte +63 -0
  69. package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
  70. package/dist/components/forms/formClasses.d.ts +3 -0
  71. package/dist/components/forms/formClasses.js +3 -0
  72. package/dist/components/forms/index.d.ts +3 -0
  73. package/dist/components/forms/index.js +3 -0
  74. package/dist/components/navigation/Breadcrumbs.svelte +56 -59
  75. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
  76. package/dist/components/navigation/ContextMenu.svelte +133 -83
  77. package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
  78. package/dist/components/navigation/DropdownMenu.svelte +139 -80
  79. package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
  80. package/dist/components/navigation/Menu.svelte +72 -48
  81. package/dist/components/navigation/Navbar.svelte +111 -32
  82. package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
  83. package/dist/components/navigation/Sidebar.svelte +236 -35
  84. package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
  85. package/dist/components/navigation/Tabs.svelte +86 -54
  86. package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
  87. package/dist/components/overlays/Alert.svelte +81 -99
  88. package/dist/components/overlays/Alert.svelte.d.ts +5 -1
  89. package/dist/components/overlays/CommandPalette.svelte +182 -217
  90. package/dist/components/overlays/Drawer.svelte +158 -167
  91. package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
  92. package/dist/components/overlays/Dropdown.svelte +62 -30
  93. package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
  94. package/dist/components/overlays/Modal.svelte +125 -130
  95. package/dist/components/overlays/Modal.svelte.d.ts +3 -1
  96. package/dist/components/overlays/Popover.svelte +106 -131
  97. package/dist/components/overlays/ProgressBar.svelte +29 -45
  98. package/dist/components/overlays/SkeletonLoader.svelte +66 -82
  99. package/dist/components/overlays/Spinner.svelte +33 -43
  100. package/dist/components/overlays/Toast.svelte +111 -140
  101. package/dist/components/overlays/Toast.svelte.d.ts +3 -0
  102. package/dist/components/overlays/Tooltip.svelte +94 -115
  103. package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
  104. package/dist/components/typography/Code.svelte +10 -14
  105. package/dist/components/typography/Heading.svelte +15 -22
  106. package/dist/components/typography/Heading.svelte.d.ts +1 -0
  107. package/dist/components/typography/Text.svelte +21 -24
  108. package/dist/components/typography/Text.svelte.d.ts +2 -1
  109. package/dist/components/utilities/Accordion.svelte +54 -67
  110. package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
  111. package/dist/components/utilities/Carousel.svelte +124 -152
  112. package/dist/components/utilities/Collapse.svelte +46 -60
  113. package/dist/components/utilities/Hero.svelte +42 -0
  114. package/dist/components/utilities/Hero.svelte.d.ts +10 -0
  115. package/dist/components/utilities/Portal.svelte +47 -72
  116. package/dist/components/utilities/ScrollArea.svelte +33 -41
  117. package/dist/components/utilities/SystemConsole.svelte +310 -0
  118. package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
  119. package/dist/components/utilities/SystemInterface.svelte +726 -0
  120. package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
  121. package/dist/components/utilities/index.d.ts +4 -0
  122. package/dist/components/utilities/index.js +3 -0
  123. package/dist/components/utilities/utilities.types.d.ts +46 -0
  124. package/dist/components/utilities/utilities.types.js +4 -0
  125. package/dist/index.d.ts +49 -4
  126. package/dist/index.js +4 -4
  127. package/dist/theme.css +2821 -218
  128. package/package.json +83 -76
@@ -1,115 +1,94 @@
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>
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ let { content, placement = 'top', delay = 200, trigger, children, class: className = '' } = $props();
3
+ // Use children as fallback if trigger not provided
4
+ const renderedTrigger = $derived(trigger ?? children);
5
+ let visible = $state(false);
6
+ let actualPlacement = $state(placement);
7
+ let tooltipElement = $state();
8
+ let timeoutId;
9
+ const tooltipId = createId('tooltip');
10
+ const placementClasses = {
11
+ top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
12
+ bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
13
+ left: 'right-full top-1/2 -translate-y-1/2 mr-2',
14
+ right: 'left-full top-1/2 -translate-y-1/2 ml-2'
15
+ };
16
+ const oppositePlacement = {
17
+ top: 'bottom',
18
+ bottom: 'top',
19
+ left: 'right',
20
+ right: 'left'
21
+ };
22
+ function handleMouseEnter() {
23
+ if (typeof window === 'undefined')
24
+ return;
25
+ timeoutId = window.setTimeout(() => {
26
+ visible = true;
27
+ }, delay);
28
+ }
29
+ function handleMouseLeave() {
30
+ if (timeoutId) {
31
+ clearTimeout(timeoutId);
32
+ }
33
+ visible = false;
34
+ }
35
+ function handleFocus() {
36
+ handleMouseEnter();
37
+ }
38
+ function handleBlur() {
39
+ handleMouseLeave();
40
+ }
41
+ // Reset actualPlacement when visibility changes
42
+ $effect(() => {
43
+ if (!visible) {
44
+ actualPlacement = placement;
45
+ }
46
+ });
47
+ // Check viewport bounds and flip placement if needed
48
+ $effect(() => {
49
+ if (typeof window === 'undefined')
50
+ return;
51
+ if (visible && tooltipElement) {
52
+ const rect = tooltipElement.getBoundingClientRect();
53
+ const viewportWidth = window.innerWidth;
54
+ const viewportHeight = window.innerHeight;
55
+ // Determine if current placement overflows and flip if needed
56
+ if (actualPlacement === 'top' && rect.top < 0 && actualPlacement !== oppositePlacement[placement]) {
57
+ actualPlacement = 'bottom';
58
+ }
59
+ else if (actualPlacement === 'bottom' && rect.bottom > viewportHeight && actualPlacement !== oppositePlacement[placement]) {
60
+ actualPlacement = 'top';
61
+ }
62
+ else if (actualPlacement === 'left' && rect.left < 0 && actualPlacement !== oppositePlacement[placement]) {
63
+ actualPlacement = 'right';
64
+ }
65
+ else if (actualPlacement === 'right' && rect.right > viewportWidth && actualPlacement !== oppositePlacement[placement]) {
66
+ actualPlacement = 'left';
67
+ }
68
+ }
69
+ });
70
+ </script>
71
+
72
+ <div class="relative inline-block">
73
+ <div
74
+ aria-describedby={tooltipId}
75
+ onmouseenter={handleMouseEnter}
76
+ onmouseleave={handleMouseLeave}
77
+ onfocus={handleFocus}
78
+ onblur={handleBlur}
79
+ role="presentation"
80
+ class="contents"
81
+ >
82
+ {@render renderedTrigger?.()}
83
+ </div>
84
+
85
+ <div
86
+ bind:this={tooltipElement}
87
+ id={tooltipId}
88
+ role="tooltip"
89
+ aria-hidden={visible ? 'false' : 'true'}
90
+ class="absolute {placementClasses[actualPlacement]} z-[var(--z-50)] panel-floating rounded-[var(--radius-md)] px-4 py-2.5 text-sm text-text whitespace-nowrap pointer-events-none {className} {visible ? 'animate-[fade-in_0.15s_var(--ease-luxe)] opacity-100' : 'opacity-0 invisible'}"
91
+ >
92
+ {content}
93
+ </div>
94
+ </div>
@@ -1,9 +1,11 @@
1
1
  import type { Snippet } from 'svelte';
2
- interface Props {
2
+ export interface Props {
3
3
  content: string;
4
4
  placement?: 'top' | 'bottom' | 'left' | 'right';
5
5
  delay?: number;
6
6
  trigger?: Snippet;
7
+ children?: Snippet;
8
+ class?: string;
7
9
  }
8
10
  declare const Tooltip: import("svelte").Component<Props, {}, "">;
9
11
  type Tooltip = ReturnType<typeof Tooltip>;
@@ -1,14 +1,10 @@
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>
1
+ <script lang="ts">"use strict";
2
+ let { children } = $props();
3
+ </script>
4
+
5
+ <code
6
+ class="inline-flex items-center transition-all duration-300 hover:shadow-[0_0_12px_var(--color-accent-overlay-30)]"
7
+ style="background: var(--color-accent-overlay-10); 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;"
8
+ >
9
+ {@render children?.()}
10
+ </code>
@@ -1,22 +1,15 @@
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>
1
+ <script lang="ts">"use strict";
2
+ let { level = 1, glow = false, variant = 'default', as, children } = $props();
3
+ const element = $derived(as ?? `h${level}`);
4
+ const variantClasses = {
5
+ default: '',
6
+ accent: 'text-accent',
7
+ gradient: 'text-gradient'
8
+ };
9
+ const baseClasses = $derived(`transition-all duration-300 ${glow ? 'text-glow' : ''} ${variantClasses[variant]}`);
10
+ const styles = $derived(`font-size: var(--text-h${level}-size); line-height: var(--text-h${level}-line-height); font-weight: var(--text-h${level}-weight); letter-spacing: var(--text-h${level}-letter-spacing); font-family: var(--font-heading); color: var(--color-text);`);
11
+ </script>
12
+
13
+ <svelte:element this={element} class={baseClasses} style={styles}>
14
+ {@render children?.()}
15
+ </svelte:element>
@@ -1,6 +1,7 @@
1
1
  interface Props {
2
2
  level?: 1 | 2 | 3 | 4 | 5 | 6;
3
3
  glow?: boolean;
4
+ variant?: 'default' | 'accent' | 'gradient';
4
5
  as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
5
6
  children?: import('svelte').Snippet;
6
7
  }
@@ -1,24 +1,21 @@
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>
1
+ <script lang="ts">"use strict";
2
+ let { variant = 'body', color = 'default', weight = 'normal', as = 'p', children } = $props();
3
+ const colorMap = {
4
+ default: 'var(--color-text)',
5
+ soft: 'var(--color-text-soft)',
6
+ muted: 'var(--color-text-muted)',
7
+ success: 'var(--color-success)',
8
+ warning: 'var(--color-warning)',
9
+ error: 'var(--color-error)',
10
+ };
11
+ const weightMap = {
12
+ normal: '400',
13
+ semibold: '600',
14
+ bold: '700',
15
+ };
16
+ const styles = $derived(`font-size: var(--text-${variant}-size); line-height: var(--text-${variant}-line-height); font-weight: ${weightMap[weight] || '400'}; letter-spacing: var(--text-${variant}-letter-spacing); font-family: var(--font-body); color: ${colorMap[color]};`);
17
+ </script>
18
+
19
+ <svelte:element this={as} class="transition-colors duration-300" style={styles}>
20
+ {@render children?.()}
21
+ </svelte:element>
@@ -1,6 +1,7 @@
1
1
  interface Props {
2
2
  variant?: 'body' | 'caption';
3
- color?: 'default' | 'soft' | 'muted';
3
+ color?: 'default' | 'soft' | 'muted' | 'success' | 'warning' | 'error';
4
+ weight?: 'normal' | 'bold' | 'semibold';
4
5
  as?: 'p' | 'span' | 'div';
5
6
  children?: import('svelte').Snippet;
6
7
  }
@@ -1,67 +1,54 @@
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>
1
+ <script lang="ts">import Collapse from './Collapse.svelte';
2
+ let { items, openItems = $bindable([]), mode = 'single', onToggle, ontoggle } = $props();
3
+ const onToggleCallback = $derived(onToggle ?? ontoggle);
4
+ // Normalize openItems to array
5
+ let openItemsArray = $derived(mode === 'single'
6
+ ? typeof openItems === 'string'
7
+ ? [openItems]
8
+ : []
9
+ : Array.isArray(openItems)
10
+ ? openItems
11
+ : []);
12
+ function isItemOpen(id) {
13
+ return openItemsArray.includes(id);
14
+ }
15
+ function toggleItem(id) {
16
+ const wasOpen = isItemOpen(id);
17
+ if (mode === 'single') {
18
+ // Single mode: only one item open at a time
19
+ if (wasOpen) {
20
+ openItems = '';
21
+ }
22
+ else {
23
+ openItems = id;
24
+ }
25
+ }
26
+ else {
27
+ // Multiple mode: toggle independently
28
+ if (wasOpen) {
29
+ openItems = openItemsArray.filter(itemId => itemId !== id);
30
+ }
31
+ else {
32
+ openItems = [...openItemsArray, id];
33
+ }
34
+ }
35
+ // Call onToggle callback if provided
36
+ onToggleCallback?.(id, !wasOpen);
37
+ }
38
+ </script>
39
+
40
+ <div class="space-y-2">
41
+ {#each items as item (item.id)}
42
+ <Collapse
43
+ title={item.title}
44
+ open={isItemOpen(item.id)}
45
+ onToggle={() => toggleItem(item.id)}
46
+ >
47
+ {#if typeof item.content === 'string'}
48
+ {item.content}
49
+ {:else}
50
+ {@render item.content()}
51
+ {/if}
52
+ </Collapse>
53
+ {/each}
54
+ </div>
@@ -4,10 +4,13 @@ interface AccordionItem {
4
4
  title: string;
5
5
  content: string | Snippet;
6
6
  }
7
- interface Props {
7
+ export interface Props {
8
8
  items: AccordionItem[];
9
9
  openItems?: string | string[];
10
10
  mode?: 'single' | 'multiple';
11
+ onToggle?: (itemId: string, isOpen: boolean) => void;
12
+ /** @deprecated Use onToggle */
13
+ ontoggle?: (itemId: string, isOpen: boolean) => void;
11
14
  }
12
15
  declare const Accordion: import("svelte").Component<Props, {}, "openItems">;
13
16
  type Accordion = ReturnType<typeof Accordion>;