yaxa-svelte 1.5.2 → 1.6.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 (41) hide show
  1. package/README.md +5 -2
  2. package/dist/components/elements/EmptyState.svelte +99 -0
  3. package/dist/components/elements/EmptyState.svelte.d.ts +45 -0
  4. package/dist/components/elements/EmptyState.test.d.ts +1 -0
  5. package/dist/components/elements/EmptyState.test.js +16 -0
  6. package/dist/components/elements/Icon.svelte +1 -0
  7. package/dist/components/forms/Combobox.svelte +243 -0
  8. package/dist/components/forms/Combobox.svelte.d.ts +56 -0
  9. package/dist/components/forms/Combobox.test.d.ts +1 -0
  10. package/dist/components/forms/Combobox.test.js +16 -0
  11. package/dist/components/forms/MultiSelect.svelte +238 -0
  12. package/dist/components/forms/MultiSelect.svelte.d.ts +54 -0
  13. package/dist/components/forms/MultiSelect.test.d.ts +1 -0
  14. package/dist/components/forms/MultiSelect.test.js +16 -0
  15. package/dist/components/forms/NumberInput.svelte +141 -0
  16. package/dist/components/forms/NumberInput.svelte.d.ts +47 -0
  17. package/dist/components/forms/NumberInput.test.d.ts +1 -0
  18. package/dist/components/forms/NumberInput.test.js +16 -0
  19. package/dist/components/layout/Footer.svelte +11 -0
  20. package/dist/components/layout/Header.svelte +14 -0
  21. package/dist/components/navigation/Stepper.svelte +139 -0
  22. package/dist/components/navigation/Stepper.svelte.d.ts +36 -0
  23. package/dist/components/navigation/Stepper.test.d.ts +1 -0
  24. package/dist/components/navigation/Stepper.test.js +14 -0
  25. package/dist/components/overlays/Drawer.svelte +105 -0
  26. package/dist/components/overlays/Drawer.svelte.d.ts +32 -0
  27. package/dist/components/overlays/Drawer.test.d.ts +1 -0
  28. package/dist/components/overlays/Drawer.test.js +14 -0
  29. package/dist/index.d.ts +14 -0
  30. package/dist/index.js +8 -0
  31. package/dist/site/config.d.ts +1 -0
  32. package/dist/site/schema.js +2 -0
  33. package/dist/testing/index.d.ts +24 -0
  34. package/dist/testing/index.js +41 -0
  35. package/dist/testing/index.test.d.ts +1 -0
  36. package/dist/testing/index.test.js +27 -0
  37. package/dist/vite/index.d.ts +18 -3
  38. package/dist/vite/index.js +38 -8
  39. package/dist/vite/index.test.d.ts +1 -0
  40. package/dist/vite/index.test.js +63 -0
  41. package/package.json +5 -1
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
  **The Intuitive Svelte UI & Solo SaaS Library**
6
6
  _Nuxt UI v4 & Nuxt UI Pro Equivalent for SvelteKit 2.7+ & Svelte 5 with Built-in SEO & SaaS Parity._
7
7
 
8
+ [![npm version](https://img.shields.io/npm/v/yaxa-svelte.svg?color=CB3837&logo=npm)](https://www.npmjs.com/package/yaxa-svelte)
9
+ [![npm downloads](https://img.shields.io/npm/dm/yaxa-svelte.svg?color=blue)](https://www.npmjs.com/package/yaxa-svelte)
8
10
  [![Svelte 5](https://img.shields.io/badge/Svelte-5.0+-FF3E00?style=flat&logo=svelte&logoColor=white)](https://svelte.dev)
9
11
  [![SvelteKit](https://img.shields.io/badge/SvelteKit-2.7+-FF3E00?style=flat&logo=svelte&logoColor=white)](https://kit.svelte.dev)
10
12
  [![Tailwind CSS v4](https://img.shields.io/badge/Tailwind_CSS-v4.0-38B2AC?style=flat&logo=tailwind-css&logoColor=white)](https://tailwindcss.com)
@@ -51,16 +53,17 @@ _(Optional full-stack SaaS features: `pnpm add better-auth drizzle-orm @polar-sh
51
53
 
52
54
  ### 2. Configure `vite.config.ts`
53
55
 
54
- Add Tailwind CSS to your Vite plugins:
56
+ Add Tailwind CSS and the `yaxa()` plugin to your Vite plugins:
55
57
 
56
58
  ```ts
57
59
  // vite.config.ts
58
60
  import { sveltekit } from '@sveltejs/kit/vite';
59
61
  import tailwindcss from '@tailwindcss/vite';
62
+ import { yaxa } from 'yaxa-svelte/vite';
60
63
  import { defineConfig } from 'vite';
61
64
 
62
65
  export default defineConfig({
63
- plugins: [tailwindcss(), sveltekit()]
66
+ plugins: [tailwindcss(), sveltekit(), yaxa()]
64
67
  });
65
68
  ```
66
69
 
@@ -0,0 +1,99 @@
1
+ <script module lang="ts">
2
+ import { tv, type VariantProps } from '../../utils/cn';
3
+ import type { Snippet } from 'svelte';
4
+ import type { IconSource } from './Icon.svelte';
5
+
6
+ export const emptyStateVariants = tv({
7
+ base: 'flex flex-col items-center justify-center text-center p-8 rounded-2xl bg-white dark:bg-neutral-900/50',
8
+ variants: {
9
+ size: {
10
+ sm: 'py-6 px-4 gap-2',
11
+ md: 'py-10 px-6 gap-3',
12
+ lg: 'py-16 px-8 gap-4'
13
+ },
14
+ bordered: {
15
+ true: 'border border-dashed border-neutral-300 dark:border-neutral-800',
16
+ false: 'border-0'
17
+ }
18
+ },
19
+ defaultVariants: {
20
+ size: 'md',
21
+ bordered: true
22
+ }
23
+ });
24
+
25
+ export type EmptyStateProps = VariantProps<typeof emptyStateVariants> & {
26
+ title?: string;
27
+ description?: string;
28
+ icon?: IconSource;
29
+ actions?: Snippet;
30
+ children?: Snippet;
31
+ class?: string;
32
+ };
33
+ </script>
34
+
35
+ <script lang="ts">
36
+ import Icon from './Icon.svelte';
37
+
38
+ let {
39
+ title,
40
+ description,
41
+ icon = 'sparkles',
42
+ size = 'md',
43
+ bordered = true,
44
+ actions,
45
+ children,
46
+ class: className = ''
47
+ }: EmptyStateProps = $props();
48
+ </script>
49
+
50
+ <div class={emptyStateVariants({ size, bordered, class: className })}>
51
+ {#if icon}
52
+ <div
53
+ class="flex items-center justify-center rounded-2xl bg-neutral-100 p-4 text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400 {size ===
54
+ 'sm'
55
+ ? 'h-10 w-10 p-2'
56
+ : size === 'lg'
57
+ ? 'h-16 w-16 p-5'
58
+ : 'h-12 w-12 p-3'}"
59
+ >
60
+ <Icon name={icon} size={size === 'sm' ? 'sm' : size === 'lg' ? 'lg' : 'md'} />
61
+ </div>
62
+ {/if}
63
+
64
+ {#if title}
65
+ <h3
66
+ class="font-bold text-neutral-900 dark:text-white {size === 'sm'
67
+ ? 'text-sm'
68
+ : size === 'lg'
69
+ ? 'text-xl'
70
+ : 'text-base'}"
71
+ >
72
+ {title}
73
+ </h3>
74
+ {/if}
75
+
76
+ {#if description}
77
+ <p
78
+ class="max-w-md text-neutral-500 dark:text-neutral-400 {size === 'sm'
79
+ ? 'text-xs'
80
+ : size === 'lg'
81
+ ? 'text-base'
82
+ : 'text-sm'}"
83
+ >
84
+ {description}
85
+ </p>
86
+ {/if}
87
+
88
+ {#if children}
89
+ <div class="mt-1">
90
+ {@render children()}
91
+ </div>
92
+ {/if}
93
+
94
+ {#if actions}
95
+ <div class="mt-4 flex flex-wrap items-center justify-center gap-3">
96
+ {@render actions()}
97
+ </div>
98
+ {/if}
99
+ </div>
@@ -0,0 +1,45 @@
1
+ import { type VariantProps } from '../../utils/cn';
2
+ import type { Snippet } from 'svelte';
3
+ import type { IconSource } from './Icon.svelte';
4
+ export declare const emptyStateVariants: import("tailwind-variants").TVReturnType<{
5
+ size: {
6
+ sm: "py-6 px-4 gap-2";
7
+ md: "py-10 px-6 gap-3";
8
+ lg: "py-16 px-8 gap-4";
9
+ };
10
+ bordered: {
11
+ true: "border border-dashed border-neutral-300 dark:border-neutral-800";
12
+ false: "border-0";
13
+ };
14
+ }, undefined, "flex flex-col items-center justify-center text-center p-8 rounded-2xl bg-white dark:bg-neutral-900/50", {
15
+ size: {
16
+ sm: "py-6 px-4 gap-2";
17
+ md: "py-10 px-6 gap-3";
18
+ lg: "py-16 px-8 gap-4";
19
+ };
20
+ bordered: {
21
+ true: "border border-dashed border-neutral-300 dark:border-neutral-800";
22
+ false: "border-0";
23
+ };
24
+ }, undefined, import("tailwind-variants").TVReturnTypeLike<{
25
+ size: {
26
+ sm: "py-6 px-4 gap-2";
27
+ md: "py-10 px-6 gap-3";
28
+ lg: "py-16 px-8 gap-4";
29
+ };
30
+ bordered: {
31
+ true: "border border-dashed border-neutral-300 dark:border-neutral-800";
32
+ false: "border-0";
33
+ };
34
+ }, undefined>>;
35
+ export type EmptyStateProps = VariantProps<typeof emptyStateVariants> & {
36
+ title?: string;
37
+ description?: string;
38
+ icon?: IconSource;
39
+ actions?: Snippet;
40
+ children?: Snippet;
41
+ class?: string;
42
+ };
43
+ declare const EmptyState: import("svelte").Component<EmptyStateProps, {}, "">;
44
+ type EmptyState = ReturnType<typeof EmptyState>;
45
+ export default EmptyState;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { emptyStateVariants } from './EmptyState.svelte';
3
+ describe('EmptyState Component', () => {
4
+ it('generates default variant classes', () => {
5
+ const classes = emptyStateVariants();
6
+ expect(classes).toContain('border-dashed');
7
+ expect(classes).toContain('py-10');
8
+ });
9
+ it('supports size and bordered variants', () => {
10
+ const smClasses = emptyStateVariants({ size: 'sm', bordered: false });
11
+ expect(smClasses).toContain('py-6');
12
+ expect(smClasses).toContain('border-0');
13
+ const lgClasses = emptyStateVariants({ size: 'lg' });
14
+ expect(lgClasses).toContain('py-16');
15
+ });
16
+ });
@@ -98,6 +98,7 @@
98
98
  '<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0020 4.77 5.07 5.07 0 0019.91 1S18.73.65 16 2.48a13.38 13.38 0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 005 4.77a5.44 5.44 0 00-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 009 18.13V22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
99
99
  twitter:
100
100
  '<path d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
101
+ npm: '<path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z" fill="currentColor"/>',
101
102
  lock: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2" stroke="currentColor" stroke-width="2" fill="none"/><path d="M7 11V7a5 5 0 0110 0v4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
102
103
  trash:
103
104
  '<path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2M10 11v6M14 11v6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
@@ -0,0 +1,243 @@
1
+ <script module lang="ts">
2
+ import { tv, type VariantProps } from '../../utils/cn';
3
+ import type { Snippet } from 'svelte';
4
+ import type { IconSource } from '../elements/Icon.svelte';
5
+
6
+ export interface ComboboxOption {
7
+ value: string;
8
+ label: string;
9
+ icon?: IconSource;
10
+ description?: string;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ export const comboboxVariants = tv({
15
+ base: 'flex w-full items-center justify-between rounded-lg border bg-white text-neutral-900 transition-all duration-150 focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-neutral-900 dark:text-neutral-100',
16
+ variants: {
17
+ size: {
18
+ sm: 'h-9 px-3 text-xs gap-2',
19
+ md: 'h-10 px-3.5 text-sm gap-2.5',
20
+ lg: 'h-12 px-4 text-base gap-3'
21
+ },
22
+ status: {
23
+ default:
24
+ 'border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20 dark:border-neutral-700',
25
+ error:
26
+ 'border-rose-500 text-rose-900 focus:border-rose-500 focus:ring-rose-500/20 dark:text-rose-100'
27
+ }
28
+ },
29
+ defaultVariants: {
30
+ size: 'md',
31
+ status: 'default'
32
+ }
33
+ });
34
+
35
+ export type ComboboxProps = VariantProps<typeof comboboxVariants> & {
36
+ value?: string;
37
+ options?: ComboboxOption[];
38
+ placeholder?: string;
39
+ searchPlaceholder?: string;
40
+ icon?: IconSource;
41
+ disabled?: boolean;
42
+ clearable?: boolean;
43
+ name?: string;
44
+ class?: string;
45
+ itemSnippet?: Snippet<[ComboboxOption, boolean]>;
46
+ };
47
+ </script>
48
+
49
+ <script lang="ts">
50
+ import { Popover } from 'bits-ui';
51
+ import Icon from '../elements/Icon.svelte';
52
+
53
+ let {
54
+ value = $bindable(''),
55
+ options = [],
56
+ placeholder = 'Select an option...',
57
+ searchPlaceholder = 'Search options...',
58
+ icon,
59
+ disabled = false,
60
+ clearable = true,
61
+ name,
62
+ size = 'md',
63
+ status = 'default',
64
+ class: className = '',
65
+ itemSnippet
66
+ }: ComboboxProps = $props();
67
+
68
+ let open = $state(false);
69
+ let query = $state('');
70
+ let highlightedIndex = $state(0);
71
+
72
+ let selectedOption = $derived(options.find((opt) => opt.value === value));
73
+
74
+ let filteredOptions = $derived.by(() => {
75
+ if (!query.trim()) return options;
76
+ const q = query.toLowerCase();
77
+ return options.filter(
78
+ (opt) =>
79
+ opt.label.toLowerCase().includes(q) ||
80
+ opt.value.toLowerCase().includes(q) ||
81
+ opt.description?.toLowerCase().includes(q)
82
+ );
83
+ });
84
+
85
+ function handleSelect(option: ComboboxOption) {
86
+ if (option.disabled) return;
87
+ value = option.value;
88
+ open = false;
89
+ query = '';
90
+ }
91
+
92
+ function handleClear(e: MouseEvent) {
93
+ e.stopPropagation();
94
+ value = '';
95
+ query = '';
96
+ }
97
+
98
+ function handleKeydown(e: KeyboardEvent) {
99
+ if (!open) {
100
+ if (e.key === 'ArrowDown' || e.key === 'Enter') {
101
+ e.preventDefault();
102
+ open = true;
103
+ }
104
+ return;
105
+ }
106
+
107
+ if (e.key === 'ArrowDown') {
108
+ e.preventDefault();
109
+ highlightedIndex = (highlightedIndex + 1) % Math.max(1, filteredOptions.length);
110
+ } else if (e.key === 'ArrowUp') {
111
+ e.preventDefault();
112
+ highlightedIndex =
113
+ (highlightedIndex - 1 + filteredOptions.length) % Math.max(1, filteredOptions.length);
114
+ } else if (e.key === 'Enter') {
115
+ e.preventDefault();
116
+ const opt = filteredOptions[highlightedIndex];
117
+ if (opt) handleSelect(opt);
118
+ } else if (e.key === 'Escape') {
119
+ open = false;
120
+ }
121
+ }
122
+ </script>
123
+
124
+ {#if name}
125
+ <input type="hidden" {name} {value} />
126
+ {/if}
127
+
128
+ <Popover.Root bind:open>
129
+ <Popover.Trigger
130
+ {disabled}
131
+ class={comboboxVariants({ size, status, class: className })}
132
+ aria-haspopup="listbox"
133
+ aria-expanded={open}
134
+ >
135
+ <div class="flex flex-1 items-center gap-2 overflow-hidden text-left">
136
+ {#if icon}
137
+ <Icon name={icon} size={size === 'sm' ? 'xs' : 'sm'} class="shrink-0 text-neutral-400" />
138
+ {:else if selectedOption?.icon}
139
+ <Icon
140
+ name={selectedOption.icon}
141
+ size={size === 'sm' ? 'xs' : 'sm'}
142
+ class="shrink-0 text-neutral-400"
143
+ />
144
+ {/if}
145
+
146
+ {#if selectedOption}
147
+ <span class="truncate font-medium">{selectedOption.label}</span>
148
+ {:else}
149
+ <span class="truncate text-neutral-400 dark:text-neutral-500">{placeholder}</span>
150
+ {/if}
151
+ </div>
152
+
153
+ <div class="flex shrink-0 items-center gap-1">
154
+ {#if clearable && value && !disabled}
155
+ <button
156
+ type="button"
157
+ onclick={handleClear}
158
+ class="rounded p-0.5 text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200"
159
+ aria-label="Clear selection"
160
+ >
161
+ <Icon name="cross" size="xs" />
162
+ </button>
163
+ {/if}
164
+ <Icon
165
+ name="chevron-down"
166
+ size="xs"
167
+ class="text-neutral-400 transition-transform duration-200 {open ? 'rotate-180' : ''}"
168
+ />
169
+ </div>
170
+ </Popover.Trigger>
171
+
172
+ <Popover.Portal>
173
+ <Popover.Content
174
+ sideOffset={6}
175
+ class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-50 w-(--bits-popover-anchor-width) min-w-[200px] overflow-hidden rounded-xl border border-neutral-200 bg-white p-1.5 shadow-xl dark:border-neutral-800 dark:bg-neutral-900"
176
+ >
177
+ <!-- Search Input -->
178
+ <div
179
+ class="flex items-center border-b border-neutral-100 px-2.5 pb-1.5 dark:border-neutral-800"
180
+ >
181
+ <Icon name="search" size="xs" class="mr-2 shrink-0 text-neutral-400" />
182
+ <input
183
+ type="text"
184
+ bind:value={query}
185
+ placeholder={searchPlaceholder}
186
+ onkeydown={handleKeydown}
187
+ class="h-8 w-full bg-transparent text-xs text-neutral-900 placeholder:text-neutral-400 focus:outline-none dark:text-white"
188
+ />
189
+ </div>
190
+
191
+ <!-- Options List -->
192
+ <div class="mt-1 max-h-56 overflow-y-auto" role="listbox">
193
+ {#if filteredOptions.length === 0}
194
+ <div class="py-6 text-center text-xs text-neutral-400 dark:text-neutral-500">
195
+ No options match "{query}".
196
+ </div>
197
+ {:else}
198
+ {#each filteredOptions as option, index}
199
+ {@const isSelected = option.value === value}
200
+ {@const isHighlighted = index === highlightedIndex}
201
+
202
+ <button
203
+ type="button"
204
+ role="option"
205
+ aria-selected={isSelected}
206
+ disabled={option.disabled}
207
+ onclick={() => handleSelect(option)}
208
+ class="flex w-full items-center justify-between rounded-lg px-2.5 py-1.5 text-left text-xs transition-colors {isSelected
209
+ ? 'bg-primary-50 font-semibold text-primary-900 dark:bg-primary-950/60 dark:text-primary-100'
210
+ : isHighlighted
211
+ ? 'bg-neutral-100 text-neutral-900 dark:bg-neutral-800/60 dark:text-white'
212
+ : 'text-neutral-700 hover:bg-neutral-50 dark:text-neutral-300 dark:hover:bg-neutral-800/40'} {option.disabled
213
+ ? 'cursor-not-allowed opacity-40'
214
+ : ''}"
215
+ >
216
+ {#if itemSnippet}
217
+ {@render itemSnippet(option, isSelected)}
218
+ {:else}
219
+ <div class="flex items-center gap-2 overflow-hidden">
220
+ {#if option.icon}
221
+ <Icon name={option.icon} size="xs" class="shrink-0 text-neutral-400" />
222
+ {/if}
223
+ <div>
224
+ <div>{option.label}</div>
225
+ {#if option.description}
226
+ <div class="text-[10px] text-neutral-400 dark:text-neutral-500">
227
+ {option.description}
228
+ </div>
229
+ {/if}
230
+ </div>
231
+ </div>
232
+
233
+ {#if isSelected}
234
+ <Icon name="check" size="xs" class="text-primary-600 dark:text-primary-400" />
235
+ {/if}
236
+ {/if}
237
+ </button>
238
+ {/each}
239
+ {/if}
240
+ </div>
241
+ </Popover.Content>
242
+ </Popover.Portal>
243
+ </Popover.Root>
@@ -0,0 +1,56 @@
1
+ import { type VariantProps } from '../../utils/cn';
2
+ import type { Snippet } from 'svelte';
3
+ import type { IconSource } from '../elements/Icon.svelte';
4
+ export interface ComboboxOption {
5
+ value: string;
6
+ label: string;
7
+ icon?: IconSource;
8
+ description?: string;
9
+ disabled?: boolean;
10
+ }
11
+ export declare const comboboxVariants: import("tailwind-variants").TVReturnType<{
12
+ size: {
13
+ sm: "h-9 px-3 text-xs gap-2";
14
+ md: "h-10 px-3.5 text-sm gap-2.5";
15
+ lg: "h-12 px-4 text-base gap-3";
16
+ };
17
+ status: {
18
+ default: "border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20 dark:border-neutral-700";
19
+ error: "border-rose-500 text-rose-900 focus:border-rose-500 focus:ring-rose-500/20 dark:text-rose-100";
20
+ };
21
+ }, undefined, "flex w-full items-center justify-between rounded-lg border bg-white text-neutral-900 transition-all duration-150 focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-neutral-900 dark:text-neutral-100", {
22
+ size: {
23
+ sm: "h-9 px-3 text-xs gap-2";
24
+ md: "h-10 px-3.5 text-sm gap-2.5";
25
+ lg: "h-12 px-4 text-base gap-3";
26
+ };
27
+ status: {
28
+ default: "border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20 dark:border-neutral-700";
29
+ error: "border-rose-500 text-rose-900 focus:border-rose-500 focus:ring-rose-500/20 dark:text-rose-100";
30
+ };
31
+ }, undefined, import("tailwind-variants").TVReturnTypeLike<{
32
+ size: {
33
+ sm: "h-9 px-3 text-xs gap-2";
34
+ md: "h-10 px-3.5 text-sm gap-2.5";
35
+ lg: "h-12 px-4 text-base gap-3";
36
+ };
37
+ status: {
38
+ default: "border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20 dark:border-neutral-700";
39
+ error: "border-rose-500 text-rose-900 focus:border-rose-500 focus:ring-rose-500/20 dark:text-rose-100";
40
+ };
41
+ }, undefined>>;
42
+ export type ComboboxProps = VariantProps<typeof comboboxVariants> & {
43
+ value?: string;
44
+ options?: ComboboxOption[];
45
+ placeholder?: string;
46
+ searchPlaceholder?: string;
47
+ icon?: IconSource;
48
+ disabled?: boolean;
49
+ clearable?: boolean;
50
+ name?: string;
51
+ class?: string;
52
+ itemSnippet?: Snippet<[ComboboxOption, boolean]>;
53
+ };
54
+ declare const Combobox: import("svelte").Component<ComboboxProps, {}, "value">;
55
+ type Combobox = ReturnType<typeof Combobox>;
56
+ export default Combobox;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { comboboxVariants } from './Combobox.svelte';
3
+ describe('Combobox Component', () => {
4
+ it('generates default variant classes', () => {
5
+ const classes = comboboxVariants();
6
+ expect(classes).toContain('h-10');
7
+ expect(classes).toContain('border-neutral-300');
8
+ });
9
+ it('supports different sizes and error status', () => {
10
+ const smClasses = comboboxVariants({ size: 'sm', status: 'error' });
11
+ expect(smClasses).toContain('h-9');
12
+ expect(smClasses).toContain('border-rose-500');
13
+ const lgClasses = comboboxVariants({ size: 'lg' });
14
+ expect(lgClasses).toContain('h-12');
15
+ });
16
+ });