@fivem-nui/svelte-components 0.0.0-stage → 1.0.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 (143) hide show
  1. package/README.md +101 -2
  2. package/dist/components/actions/NuiButton.svelte +292 -0
  3. package/dist/components/actions/NuiButton.svelte.d.ts +60 -0
  4. package/dist/components/data/NuiBadge.svelte +277 -0
  5. package/dist/components/data/NuiBadge.svelte.d.ts +74 -0
  6. package/dist/components/data/NuiCodeBlock.svelte +256 -0
  7. package/dist/components/data/NuiCodeBlock.svelte.d.ts +49 -0
  8. package/dist/components/data/NuiList.svelte +341 -0
  9. package/dist/components/data/NuiList.svelte.d.ts +52 -0
  10. package/dist/components/data/NuiProgressBar.svelte +264 -0
  11. package/dist/components/data/NuiProgressBar.svelte.d.ts +51 -0
  12. package/dist/components/data/NuiRichText.svelte +260 -0
  13. package/dist/components/data/NuiRichText.svelte.d.ts +33 -0
  14. package/dist/components/data/NuiTable.svelte +470 -0
  15. package/dist/components/data/NuiTable.svelte.d.ts +53 -0
  16. package/dist/components/data/chart/NuiChart.svelte +384 -0
  17. package/dist/components/data/chart/NuiChart.svelte.d.ts +49 -0
  18. package/dist/components/data/chart/color.d.ts +9 -0
  19. package/dist/components/data/chart/color.js +78 -0
  20. package/dist/components/data/chart/index.d.ts +10 -0
  21. package/dist/components/data/chart/index.js +10 -0
  22. package/dist/components/data/chart/registry.d.ts +14 -0
  23. package/dist/components/data/chart/registry.js +53 -0
  24. package/dist/components/data/chart/renderers/bar.d.ts +8 -0
  25. package/dist/components/data/chart/renderers/bar.js +79 -0
  26. package/dist/components/data/chart/renderers/base.d.ts +18 -0
  27. package/dist/components/data/chart/renderers/base.js +10 -0
  28. package/dist/components/data/chart/renderers/line.d.ts +8 -0
  29. package/dist/components/data/chart/renderers/line.js +89 -0
  30. package/dist/components/data/chart/renderers/pie.d.ts +10 -0
  31. package/dist/components/data/chart/renderers/pie.js +55 -0
  32. package/dist/components/data/chart/scale.d.ts +22 -0
  33. package/dist/components/data/chart/scale.js +101 -0
  34. package/dist/components/data/chart/types.d.ts +84 -0
  35. package/dist/components/data/chart/types.js +1 -0
  36. package/dist/components/data/chart/validator.d.ts +10 -0
  37. package/dist/components/data/chart/validator.js +82 -0
  38. package/dist/components/forms/NuiCheckbox.svelte +218 -0
  39. package/dist/components/forms/NuiCheckbox.svelte.d.ts +37 -0
  40. package/dist/components/forms/NuiDatepicker.svelte +755 -0
  41. package/dist/components/forms/NuiDatepicker.svelte.d.ts +34 -0
  42. package/dist/components/forms/NuiFileInput.svelte +1047 -0
  43. package/dist/components/forms/NuiFileInput.svelte.d.ts +93 -0
  44. package/dist/components/forms/NuiFileInput.types.d.ts +28 -0
  45. package/dist/components/forms/NuiFileInput.types.js +1 -0
  46. package/dist/components/forms/NuiForm.svelte +391 -0
  47. package/dist/components/forms/NuiForm.svelte.d.ts +69 -0
  48. package/dist/components/forms/NuiInput.svelte +296 -0
  49. package/dist/components/forms/NuiInput.svelte.d.ts +62 -0
  50. package/dist/components/forms/NuiRadioGroup.svelte +238 -0
  51. package/dist/components/forms/NuiRadioGroup.svelte.d.ts +47 -0
  52. package/dist/components/forms/NuiSelect.svelte +819 -0
  53. package/dist/components/forms/NuiSelect.svelte.d.ts +49 -0
  54. package/dist/components/layout/NuiCard.svelte +314 -0
  55. package/dist/components/layout/NuiCard.svelte.d.ts +48 -0
  56. package/dist/components/layout/NuiCardGrid.svelte +98 -0
  57. package/dist/components/layout/NuiCardGrid.svelte.d.ts +22 -0
  58. package/dist/components/layout/NuiFlex.svelte +121 -0
  59. package/dist/components/layout/NuiFlex.svelte.d.ts +23 -0
  60. package/dist/components/layout/NuiGrid.svelte +66 -0
  61. package/dist/components/layout/NuiGrid.svelte.d.ts +14 -0
  62. package/dist/components/layout/NuiStack.svelte +138 -0
  63. package/dist/components/layout/NuiStack.svelte.d.ts +26 -0
  64. package/dist/components/navigation/NuiAccordion.svelte +323 -0
  65. package/dist/components/navigation/NuiAccordion.svelte.d.ts +38 -0
  66. package/dist/components/navigation/NuiBreadcrumbs.svelte +133 -0
  67. package/dist/components/navigation/NuiBreadcrumbs.svelte.d.ts +19 -0
  68. package/dist/components/navigation/NuiMenu.svelte +596 -0
  69. package/dist/components/navigation/NuiMenu.svelte.d.ts +28 -0
  70. package/dist/components/navigation/NuiPagination.svelte +306 -0
  71. package/dist/components/navigation/NuiPagination.svelte.d.ts +40 -0
  72. package/dist/components/navigation/NuiSidebar.svelte +186 -0
  73. package/dist/components/navigation/NuiSidebar.svelte.d.ts +26 -0
  74. package/dist/components/navigation/NuiTabs.svelte +249 -0
  75. package/dist/components/navigation/NuiTabs.svelte.d.ts +31 -0
  76. package/dist/components/overlay/NuiDialog.svelte +118 -0
  77. package/dist/components/overlay/NuiDialog.svelte.d.ts +34 -0
  78. package/dist/components/overlay/NuiModal.svelte +992 -0
  79. package/dist/components/overlay/NuiModal.svelte.d.ts +72 -0
  80. package/dist/components/overlay/NuiTooltip.svelte +159 -0
  81. package/dist/components/overlay/NuiTooltip.svelte.d.ts +13 -0
  82. package/dist/composables/useClickOutside.d.ts +43 -0
  83. package/dist/composables/useClickOutside.js +73 -0
  84. package/dist/composables/useFormValidation.d.ts +23 -0
  85. package/dist/composables/useFormValidation.js +72 -0
  86. package/dist/composables/useIdle.svelte.d.ts +18 -0
  87. package/dist/composables/useIdle.svelte.js +144 -0
  88. package/dist/composables/useKeyboardShortcut.svelte.d.ts +114 -0
  89. package/dist/composables/useKeyboardShortcut.svelte.js +153 -0
  90. package/dist/composables/useLocalStorage.svelte.d.ts +22 -0
  91. package/dist/composables/useLocalStorage.svelte.js +105 -0
  92. package/dist/composables/useMediaQuery.svelte.d.ts +51 -0
  93. package/dist/composables/useMediaQuery.svelte.js +80 -0
  94. package/dist/index.d.ts +46 -0
  95. package/dist/index.js +42 -0
  96. package/package.json +58 -3
  97. package/src/components/actions/NuiButton.svelte +292 -0
  98. package/src/components/data/NuiBadge.svelte +277 -0
  99. package/src/components/data/NuiCodeBlock.svelte +256 -0
  100. package/src/components/data/NuiList.svelte +341 -0
  101. package/src/components/data/NuiProgressBar.svelte +264 -0
  102. package/src/components/data/NuiRichText.svelte +260 -0
  103. package/src/components/data/NuiTable.svelte +470 -0
  104. package/src/components/data/chart/NuiChart.svelte +384 -0
  105. package/src/components/data/chart/color.ts +83 -0
  106. package/src/components/data/chart/index.ts +10 -0
  107. package/src/components/data/chart/registry.ts +71 -0
  108. package/src/components/data/chart/renderers/bar.ts +90 -0
  109. package/src/components/data/chart/renderers/base.ts +32 -0
  110. package/src/components/data/chart/renderers/line.ts +99 -0
  111. package/src/components/data/chart/renderers/pie.ts +66 -0
  112. package/src/components/data/chart/scale.ts +139 -0
  113. package/src/components/data/chart/types.ts +98 -0
  114. package/src/components/data/chart/validator.ts +98 -0
  115. package/src/components/forms/NuiCheckbox.svelte +218 -0
  116. package/src/components/forms/NuiDatepicker.svelte +755 -0
  117. package/src/components/forms/NuiFileInput.svelte +1047 -0
  118. package/src/components/forms/NuiFileInput.types.ts +29 -0
  119. package/src/components/forms/NuiForm.svelte +391 -0
  120. package/src/components/forms/NuiInput.svelte +296 -0
  121. package/src/components/forms/NuiRadioGroup.svelte +238 -0
  122. package/src/components/forms/NuiSelect.svelte +819 -0
  123. package/src/components/layout/NuiCard.svelte +314 -0
  124. package/src/components/layout/NuiCardGrid.svelte +98 -0
  125. package/src/components/layout/NuiFlex.svelte +121 -0
  126. package/src/components/layout/NuiGrid.svelte +66 -0
  127. package/src/components/layout/NuiStack.svelte +138 -0
  128. package/src/components/navigation/NuiAccordion.svelte +323 -0
  129. package/src/components/navigation/NuiBreadcrumbs.svelte +133 -0
  130. package/src/components/navigation/NuiMenu.svelte +596 -0
  131. package/src/components/navigation/NuiPagination.svelte +306 -0
  132. package/src/components/navigation/NuiSidebar.svelte +186 -0
  133. package/src/components/navigation/NuiTabs.svelte +249 -0
  134. package/src/components/overlay/NuiDialog.svelte +118 -0
  135. package/src/components/overlay/NuiModal.svelte +992 -0
  136. package/src/components/overlay/NuiTooltip.svelte +159 -0
  137. package/src/composables/useClickOutside.ts +132 -0
  138. package/src/composables/useFormValidation.ts +105 -0
  139. package/src/composables/useIdle.svelte.ts +191 -0
  140. package/src/composables/useKeyboardShortcut.svelte.ts +278 -0
  141. package/src/composables/useLocalStorage.svelte.ts +137 -0
  142. package/src/composables/useMediaQuery.svelte.ts +102 -0
  143. package/src/index.ts +66 -0
@@ -0,0 +1,49 @@
1
+ export interface NuiSelectOption {
2
+ /** Unique value identifying this option. */
3
+ value: string;
4
+ /** Human-readable text label. */
5
+ label: string;
6
+ /** Whether this individual option is disabled. */
7
+ disabled?: boolean;
8
+ }
9
+ import type { SvelteNuiClient } from "@fivem-nui/svelte";
10
+ interface Props {
11
+ /** NUI event name emitted when selection changes or is cleared. */
12
+ eventName?: string;
13
+ /** SvelteNuiClient instance used to dispatch NUI events. */
14
+ client?: SvelteNuiClient;
15
+ /**
16
+ * Current selection value (two-way bindable):
17
+ * - In single-select mode (`multiple={false}`): a plain string containing the selected option's `value` (e.g. `'item_1'`), or `''` if empty.
18
+ * - In multi-select mode (`multiple={true}`): a serialized JSON array string of selected option values (e.g. `'["item_1", "item_2"]'`), or `'[]'` / `''` if empty.
19
+ *
20
+ * Note: In multi-select mode, the underlying `<input type="hidden" {name} {value} />` submits this JSON string.
21
+ * When emitting the NUI event (`eventName`), a parsed array (`value: string[]`) is provided in the event payload.
22
+ */
23
+ value?: string;
24
+ /** Text label displayed above the select control. */
25
+ label?: string;
26
+ /** Placeholder text shown when no option is selected. Defaults to 'Select...'. */
27
+ placeholder?: string;
28
+ /** Available select options. Each option's `value` must be unique. */
29
+ options?: NuiSelectOption[];
30
+ /** Whether the select control is disabled. */
31
+ disabled?: boolean;
32
+ /**
33
+ * Whether multiple options can be selected simultaneously.
34
+ * When enabled, `value` binds to a serialized JSON array of selected strings (e.g. `'["opt1","opt2"]'`).
35
+ */
36
+ multiple?: boolean;
37
+ /** Whether the search input is enabled in the dropdown panel. Defaults to true. */
38
+ searchable?: boolean;
39
+ /** Maximum CSS height for the dropdown panel (e.g. '200px', '15rem'). Defaults to '200px'. */
40
+ maxHeight?: string;
41
+ /**
42
+ * Form input name attribute rendered on an underlying `<input type="hidden" {name} {value} />`.
43
+ * In single mode, submits the selected string value. In multi-select mode, submits the JSON array string.
44
+ */
45
+ name?: string;
46
+ }
47
+ declare const NuiSelect: import("svelte").Component<Props, {}, "value">;
48
+ type NuiSelect = ReturnType<typeof NuiSelect>;
49
+ export default NuiSelect;
@@ -0,0 +1,314 @@
1
+ <script lang="ts" module>
2
+ let warnedNoAccessibleName = false;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { DEV } from 'esm-env';
7
+ import type { Snippet } from 'svelte';
8
+ import type { HTMLAttributes } from 'svelte/elements';
9
+
10
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children'> {
11
+ /** Card header title */
12
+ title?: string;
13
+ /** Optional icon displayed before the title */
14
+ icon?: string;
15
+ /** Card elevation level (1 to 5) */
16
+ elevation?: 1 | 2 | 3 | 4 | 5 | '1' | '2' | '3' | '4' | '5';
17
+ /**
18
+ * Whether the card can be toggled/selected.
19
+ * When true and not `controlled`, clicking toggles `selected` (supports `bind:selected`).
20
+ * For single-select card groups (radio behavior), set `controlled={true}` and update `selected` via `onSelect`.
21
+ */
22
+ selectable?: boolean;
23
+ /**
24
+ * Whether the card is currently selected.
25
+ * Supports two-way binding via `bind:selected` when `controlled` is false.
26
+ */
27
+ selected?: boolean;
28
+ /**
29
+ * When true, the parent owns the `selected` state; the card will not mutate `selected` locally upon click.
30
+ * Do not use `bind:selected` together with `controlled={true}`.
31
+ * @default false
32
+ */
33
+ controlled?: boolean;
34
+ /** Whether the card behaves as a clickable button */
35
+ clickable?: boolean;
36
+ /**
37
+ * Whether the card is disabled.
38
+ * When true, disables the interactive card overlay (if interactive) and marks `actions` and `children` as `inert`.
39
+ * Note: `inert` is applied to `actions` and `children` even on non-interactive cards to suppress descendant interactivity.
40
+ */
41
+ disabled?: boolean;
42
+ /** Accessible name for the interactive card when there is no title */
43
+ label?: string;
44
+ /** Heading level for the card title (defaults to 3 -> <h3>) */
45
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | (number & {});
46
+ /** Callback fired when the card is clicked or activated via keyboard. For `selectable` cards, also receives the intended next selection state. */
47
+ onSelect?: (event: MouseEvent | KeyboardEvent, nextSelected?: boolean) => void;
48
+ /** Card body content snippet */
49
+ children?: Snippet;
50
+ /** Header action buttons or badges snippet */
51
+ actions?: Snippet;
52
+ }
53
+
54
+ const uid = $props.id();
55
+ const titleId = `nui-card-${uid}-title`;
56
+
57
+ let {
58
+ title = '',
59
+ icon = '',
60
+ elevation = 1,
61
+ selectable = false,
62
+ selected = $bindable(false),
63
+ controlled = false,
64
+ clickable = false,
65
+ disabled = false,
66
+ label,
67
+ headingLevel = 3,
68
+ onSelect,
69
+ children,
70
+ actions,
71
+ class: className = '',
72
+ role: roleProp,
73
+ 'aria-label': ariaLabel,
74
+ 'aria-labelledby': ariaLabelledby,
75
+ ...rest
76
+ }: Props = $props();
77
+
78
+ const interactive = $derived(selectable || clickable || Boolean(onSelect));
79
+ const computedAriaLabelledby = $derived(title ? titleId : ariaLabelledby);
80
+ const computedAriaLabel = $derived(
81
+ computedAriaLabelledby ? undefined : (label ?? ariaLabel)
82
+ );
83
+
84
+ const safeHeadingTag = $derived.by(() => {
85
+ const level = Number(headingLevel);
86
+ if (Number.isInteger(level) && level >= 1 && level <= 6) {
87
+ return `h${level}`;
88
+ }
89
+ return 'h3';
90
+ });
91
+
92
+ // Warn once in development mode if an interactive card lacks an accessible name
93
+ $effect(() => {
94
+ if (!DEV || warnedNoAccessibleName) return;
95
+ if (interactive && !computedAriaLabel && !computedAriaLabelledby) {
96
+ warnedNoAccessibleName = true;
97
+ console.warn(
98
+ '[fivem-nui-components] NuiCard: Interactive card must have an accessible name. Provide a `title`, `label`, or `aria-label`.'
99
+ );
100
+ }
101
+ });
102
+
103
+ function handleClick(event: MouseEvent | KeyboardEvent) {
104
+ if (disabled) return;
105
+ if (selectable) {
106
+ const next = !selected;
107
+ if (!controlled) {
108
+ selected = next;
109
+ }
110
+ onSelect?.(event, next);
111
+ } else {
112
+ onSelect?.(event);
113
+ }
114
+ }
115
+
116
+ function handleKeyDown(event: KeyboardEvent) {
117
+ if (disabled) return;
118
+ if (event.key === 'Enter' || event.key === ' ') {
119
+ event.preventDefault();
120
+ handleClick(event);
121
+ }
122
+ }
123
+ </script>
124
+
125
+ <div
126
+ {...rest}
127
+ role={roleProp ?? (!interactive && (ariaLabel || ariaLabelledby) ? 'group' : undefined)}
128
+ aria-label={!interactive ? ariaLabel : undefined}
129
+ aria-labelledby={!interactive ? ariaLabelledby : undefined}
130
+ class={['nui-card', `nui-card--elevation-${elevation}`, className]}
131
+ class:nui-card--interactive={interactive}
132
+ class:nui-card--selected={selectable && selected}
133
+ class:nui-card--disabled={disabled}
134
+ >
135
+ {#if interactive}
136
+ <button
137
+ type="button"
138
+ class="nui-card-hit"
139
+ {disabled}
140
+ aria-pressed={selectable ? selected : undefined}
141
+ aria-labelledby={computedAriaLabelledby}
142
+ aria-label={computedAriaLabel}
143
+ onclick={handleClick}
144
+ onkeydown={handleKeyDown}
145
+ ></button>
146
+ {/if}
147
+
148
+ {#if title || icon || actions}
149
+ <div class="nui-card-header">
150
+ {#if icon}<span class="nui-card-icon" aria-hidden="true">{icon}</span>{/if}
151
+ {#if title}
152
+ <svelte:element this={safeHeadingTag} id={titleId} class="nui-card-title">
153
+ {title}
154
+ </svelte:element>
155
+ {/if}
156
+ {#if actions}
157
+ <div class="nui-card-actions" inert={disabled || undefined}>{@render actions()}</div>
158
+ {/if}
159
+ </div>
160
+ {/if}
161
+
162
+ {#if children}
163
+ <div class="nui-card-body" inert={disabled || undefined}>{@render children()}</div>
164
+ {/if}
165
+ </div>
166
+
167
+ <style>
168
+ .nui-card {
169
+ position: relative;
170
+ background: var(--nui-surface, #18181b);
171
+ color: var(--nui-fg, #fafafa);
172
+ border: 1px solid var(--nui-border, #27272a);
173
+ border-radius: var(--nui-radius, 0.5rem);
174
+ padding: 1rem;
175
+ box-sizing: border-box;
176
+ font-family: inherit;
177
+ box-shadow: var(--nui-card-shadow);
178
+ transition: transform 0.15s ease, border-color 0.15s ease,
179
+ opacity 0.15s ease, box-shadow 0.15s ease;
180
+ }
181
+
182
+ .nui-card--elevation-1 { --nui-card-shadow: 0 1px 3px rgb(0 0 0 / 0.12); }
183
+ .nui-card--elevation-2 { --nui-card-shadow: 0 3px 6px rgb(0 0 0 / 0.16); }
184
+ .nui-card--elevation-3 { --nui-card-shadow: 0 10px 20px rgb(0 0 0 / 0.19); }
185
+ .nui-card--elevation-4 { --nui-card-shadow: 0 20px 40px rgb(0 0 0 / 0.23); }
186
+ .nui-card--elevation-5 { --nui-card-shadow: 0 30px 60px rgb(0 0 0 / 0.3); }
187
+
188
+ .nui-card--interactive:not(.nui-card--disabled):hover {
189
+ transform: translateY(-2px);
190
+ border-color: var(--nui-primary, #3b82f6);
191
+ z-index: 2;
192
+ }
193
+
194
+ .nui-card:focus-within {
195
+ z-index: 2;
196
+ }
197
+
198
+ .nui-card--interactive:not(.nui-card--disabled):active {
199
+ transform: none;
200
+ }
201
+
202
+ .nui-card--selected {
203
+ border-color: var(--nui-primary, #3b82f6);
204
+ box-shadow: inset 0 0 0 1px var(--nui-primary, #3b82f6), var(--nui-card-shadow);
205
+ }
206
+
207
+ .nui-card--disabled {
208
+ opacity: 0.5;
209
+ }
210
+
211
+ /* Fallback for environments lacking native inert support */
212
+ .nui-card--disabled .nui-card-actions,
213
+ .nui-card--disabled .nui-card-body {
214
+ pointer-events: none;
215
+ }
216
+
217
+ .nui-card-hit {
218
+ position: absolute;
219
+ inset: 0;
220
+ width: 100%;
221
+ margin: 0;
222
+ padding: 0;
223
+ border: 0;
224
+ border-radius: inherit;
225
+ background: transparent;
226
+ appearance: none;
227
+ cursor: pointer;
228
+ }
229
+
230
+ /* Overlay expands -2px only during hover to eliminate jitter without overlapping adjacent cards at rest */
231
+ .nui-card--interactive:not(.nui-card--disabled):hover .nui-card-hit {
232
+ bottom: -2px;
233
+ }
234
+
235
+ .nui-card-hit:disabled {
236
+ cursor: not-allowed;
237
+ }
238
+
239
+ .nui-card-hit:focus-visible {
240
+ outline: 2px solid var(--nui-primary, #3b82f6);
241
+ outline-offset: 2px;
242
+ }
243
+
244
+ .nui-card-header {
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 0.5rem;
248
+ margin-bottom: 0.75rem;
249
+ }
250
+
251
+ .nui-card-header:last-child {
252
+ margin-bottom: 0;
253
+ }
254
+
255
+ .nui-card-icon {
256
+ font-size: 1.25rem;
257
+ line-height: 1;
258
+ display: inline-flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ }
262
+
263
+ .nui-card-title {
264
+ margin: 0;
265
+ font-size: 1rem;
266
+ font-weight: 600;
267
+ flex: 1;
268
+ color: inherit;
269
+ }
270
+
271
+ /* Above overlay so actions, interactive content, and user-marked elements receive direct pointer/keyboard focus */
272
+ .nui-card-actions {
273
+ position: relative;
274
+ z-index: 1;
275
+ display: flex;
276
+ gap: 0.375rem;
277
+ }
278
+
279
+ .nui-card-body {
280
+ font-size: 0.875rem;
281
+ color: inherit;
282
+ }
283
+
284
+ .nui-card-body :global(:is(
285
+ a,
286
+ button,
287
+ input,
288
+ select,
289
+ textarea,
290
+ label,
291
+ summary,
292
+ [tabindex],
293
+ [role="button"],
294
+ [role="checkbox"],
295
+ [role="radio"],
296
+ [role="switch"],
297
+ [role="link"],
298
+ [role="textbox"],
299
+ [contenteditable],
300
+ .nui-card-interactive
301
+ )) {
302
+ position: relative;
303
+ z-index: 1;
304
+ }
305
+
306
+ @media (prefers-reduced-motion: reduce) {
307
+ .nui-card {
308
+ transition: none;
309
+ }
310
+ .nui-card--interactive:hover {
311
+ transform: none;
312
+ }
313
+ }
314
+ </style>
@@ -0,0 +1,48 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children'> {
4
+ /** Card header title */
5
+ title?: string;
6
+ /** Optional icon displayed before the title */
7
+ icon?: string;
8
+ /** Card elevation level (1 to 5) */
9
+ elevation?: 1 | 2 | 3 | 4 | 5 | '1' | '2' | '3' | '4' | '5';
10
+ /**
11
+ * Whether the card can be toggled/selected.
12
+ * When true and not `controlled`, clicking toggles `selected` (supports `bind:selected`).
13
+ * For single-select card groups (radio behavior), set `controlled={true}` and update `selected` via `onSelect`.
14
+ */
15
+ selectable?: boolean;
16
+ /**
17
+ * Whether the card is currently selected.
18
+ * Supports two-way binding via `bind:selected` when `controlled` is false.
19
+ */
20
+ selected?: boolean;
21
+ /**
22
+ * When true, the parent owns the `selected` state; the card will not mutate `selected` locally upon click.
23
+ * Do not use `bind:selected` together with `controlled={true}`.
24
+ * @default false
25
+ */
26
+ controlled?: boolean;
27
+ /** Whether the card behaves as a clickable button */
28
+ clickable?: boolean;
29
+ /**
30
+ * Whether the card is disabled.
31
+ * When true, disables the interactive card overlay (if interactive) and marks `actions` and `children` as `inert`.
32
+ * Note: `inert` is applied to `actions` and `children` even on non-interactive cards to suppress descendant interactivity.
33
+ */
34
+ disabled?: boolean;
35
+ /** Accessible name for the interactive card when there is no title */
36
+ label?: string;
37
+ /** Heading level for the card title (defaults to 3 -> <h3>) */
38
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | (number & {});
39
+ /** Callback fired when the card is clicked or activated via keyboard. For `selectable` cards, also receives the intended next selection state. */
40
+ onSelect?: (event: MouseEvent | KeyboardEvent, nextSelected?: boolean) => void;
41
+ /** Card body content snippet */
42
+ children?: Snippet;
43
+ /** Header action buttons or badges snippet */
44
+ actions?: Snippet;
45
+ }
46
+ declare const NuiCard: import("svelte").Component<Props, {}, "selected">;
47
+ type NuiCard = ReturnType<typeof NuiCard>;
48
+ export default NuiCard;
@@ -0,0 +1,98 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ interface Props extends HTMLAttributes<HTMLDivElement> {
6
+ /** Max number of columns (responsive) or exact count (fixed) */
7
+ columns?: number | `${number}` | (string & {});
8
+ /**
9
+ * Gap between cards. Numbers are treated as px (e.g. 16 -> '16px').
10
+ * Fixed units (rem, px) are recommended; percentage units in responsive mode may produce
11
+ * variable track minimums because container-relative calc() evaluation depends on outer context.
12
+ */
13
+ gap?: string | number;
14
+ /** Auto fit columns by `minCardWidth`, capped by `columns` */
15
+ responsive?: boolean;
16
+ /** Minimum card width. Numbers are treated as px */
17
+ minCardWidth?: string | number;
18
+ /** Center cards within their cells */
19
+ center?: boolean;
20
+ children?: Snippet;
21
+ }
22
+
23
+ let {
24
+ columns = 3,
25
+ gap = "1rem",
26
+ responsive = true,
27
+ minCardWidth = "250px",
28
+ center = false,
29
+ children,
30
+ class: className = "",
31
+ style,
32
+ ...rest
33
+ }: Props = $props();
34
+
35
+ const toCss = (v: string | number | undefined | null, fallback: string) => {
36
+ if (typeof v === "number") return `${v}px`;
37
+ if (typeof v === "string" && v.trim().length > 0) return v.trim();
38
+ return fallback;
39
+ };
40
+
41
+ const cols = $derived.by(() => {
42
+ if (columns == null) return 3;
43
+ const num = Number(columns);
44
+ if (!Number.isFinite(num)) return 1;
45
+ return Math.max(1, Math.floor(num));
46
+ });
47
+
48
+ const styleVars = $derived(
49
+ `--nui-card-columns:${cols};--nui-card-gap:${toCss(gap, "1rem")};--nui-card-min-width:${toCss(minCardWidth, "250px")};`,
50
+ );
51
+ /**
52
+ * Concatenate extra inline style. Svelte 5 passes style strings here; non-string values are safely ignored.
53
+ */
54
+ const styleExtra = $derived(typeof style === "string" ? style : "");
55
+ </script>
56
+
57
+ <div
58
+ {...rest}
59
+ class={[
60
+ "nui-card-grid",
61
+ responsive ? "nui-card-grid--responsive" : "nui-card-grid--fixed",
62
+ center && "nui-card-grid--center",
63
+ className,
64
+ ]}
65
+ style={styleVars + styleExtra}
66
+ >
67
+ {@render children?.()}
68
+ </div>
69
+
70
+ <style>
71
+ .nui-card-grid {
72
+ display: grid;
73
+ gap: var(--nui-card-gap, 1rem);
74
+ width: 100%;
75
+ font-family: inherit;
76
+ box-sizing: border-box;
77
+ }
78
+
79
+ .nui-card-grid--responsive {
80
+ --_gap-total: calc((var(--nui-card-columns) - 1) * var(--nui-card-gap));
81
+ --_min: min(
82
+ 100%,
83
+ max(
84
+ var(--nui-card-min-width),
85
+ calc((100% - var(--_gap-total)) / var(--nui-card-columns))
86
+ )
87
+ );
88
+ grid-template-columns: repeat(auto-fit, minmax(var(--_min), 1fr));
89
+ }
90
+
91
+ .nui-card-grid--fixed {
92
+ grid-template-columns: repeat(var(--nui-card-columns), minmax(0, 1fr));
93
+ }
94
+
95
+ .nui-card-grid--center {
96
+ justify-items: center;
97
+ }
98
+ </style>
@@ -0,0 +1,22 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ /** Max number of columns (responsive) or exact count (fixed) */
5
+ columns?: number | `${number}` | (string & {});
6
+ /**
7
+ * Gap between cards. Numbers are treated as px (e.g. 16 -> '16px').
8
+ * Fixed units (rem, px) are recommended; percentage units in responsive mode may produce
9
+ * variable track minimums because container-relative calc() evaluation depends on outer context.
10
+ */
11
+ gap?: string | number;
12
+ /** Auto fit columns by `minCardWidth`, capped by `columns` */
13
+ responsive?: boolean;
14
+ /** Minimum card width. Numbers are treated as px */
15
+ minCardWidth?: string | number;
16
+ /** Center cards within their cells */
17
+ center?: boolean;
18
+ children?: Snippet;
19
+ }
20
+ declare const NuiCardGrid: import("svelte").Component<Props, {}, "">;
21
+ type NuiCardGrid = ReturnType<typeof NuiCardGrid>;
22
+ export default NuiCardGrid;
@@ -0,0 +1,121 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ type Direction = "row" | "column" | "row-reverse" | "column-reverse";
6
+ type Justify = "start" | "center" | "end" | "between" | "around" | "evenly";
7
+ type Align = "start" | "center" | "end" | "stretch" | "baseline";
8
+
9
+ interface Props extends HTMLAttributes<HTMLDivElement> {
10
+ /** Main axis direction. Note: `*-reverse` changes visual order only, not DOM/tab order */
11
+ direction?: Direction;
12
+ /** Alignment along the main axis */
13
+ justify?: Justify;
14
+ /** Alignment along the cross axis */
15
+ align?: Align;
16
+ /** Allow items to wrap onto multiple lines */
17
+ wrap?: boolean;
18
+ /** Gap between items. Numbers are treated as px */
19
+ gap?: string | number;
20
+ /** Render as inline-flex */
21
+ inline?: boolean;
22
+ children?: Snippet;
23
+ }
24
+
25
+ let {
26
+ direction = "row",
27
+ justify = "start",
28
+ align = "stretch",
29
+ wrap = false,
30
+ gap = "1rem",
31
+ inline = false,
32
+ children,
33
+ class: className = "",
34
+ style,
35
+ ...rest
36
+ }: Props = $props();
37
+
38
+ const toCss = (v: string | number) => (typeof v === "number" ? `${v}px` : v);
39
+ const styleVars = $derived(`--nui-flex-gap:${toCss(gap)};`);
40
+ const styleExtra = $derived(typeof style === "string" ? style : "");
41
+ </script>
42
+
43
+ <div
44
+ {...rest}
45
+ class={[
46
+ "nui-flex",
47
+ `nui-flex--${direction}`,
48
+ `nui-flex--justify-${justify}`,
49
+ `nui-flex--align-${align}`,
50
+ wrap && "nui-flex--wrap",
51
+ inline && "nui-flex--inline",
52
+ className,
53
+ ]}
54
+ style={styleVars + styleExtra}
55
+ >
56
+ {@render children?.()}
57
+ </div>
58
+
59
+ <style>
60
+ .nui-flex {
61
+ display: flex;
62
+ gap: var(--nui-flex-gap, 1rem);
63
+ font-family: inherit;
64
+ box-sizing: border-box;
65
+ }
66
+
67
+ .nui-flex--inline {
68
+ display: inline-flex;
69
+ }
70
+ .nui-flex--wrap {
71
+ flex-wrap: wrap;
72
+ }
73
+
74
+ .nui-flex--row {
75
+ flex-direction: row;
76
+ }
77
+ .nui-flex--column {
78
+ flex-direction: column;
79
+ }
80
+ .nui-flex--row-reverse {
81
+ flex-direction: row-reverse;
82
+ }
83
+ .nui-flex--column-reverse {
84
+ flex-direction: column-reverse;
85
+ }
86
+
87
+ .nui-flex--justify-start {
88
+ justify-content: flex-start;
89
+ }
90
+ .nui-flex--justify-center {
91
+ justify-content: center;
92
+ }
93
+ .nui-flex--justify-end {
94
+ justify-content: flex-end;
95
+ }
96
+ .nui-flex--justify-between {
97
+ justify-content: space-between;
98
+ }
99
+ .nui-flex--justify-around {
100
+ justify-content: space-around;
101
+ }
102
+ .nui-flex--justify-evenly {
103
+ justify-content: space-evenly;
104
+ }
105
+
106
+ .nui-flex--align-start {
107
+ align-items: flex-start;
108
+ }
109
+ .nui-flex--align-center {
110
+ align-items: center;
111
+ }
112
+ .nui-flex--align-end {
113
+ align-items: flex-end;
114
+ }
115
+ .nui-flex--align-stretch {
116
+ align-items: stretch;
117
+ }
118
+ .nui-flex--align-baseline {
119
+ align-items: baseline;
120
+ }
121
+ </style>
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ type Direction = "row" | "column" | "row-reverse" | "column-reverse";
4
+ type Justify = "start" | "center" | "end" | "between" | "around" | "evenly";
5
+ type Align = "start" | "center" | "end" | "stretch" | "baseline";
6
+ interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ /** Main axis direction. Note: `*-reverse` changes visual order only, not DOM/tab order */
8
+ direction?: Direction;
9
+ /** Alignment along the main axis */
10
+ justify?: Justify;
11
+ /** Alignment along the cross axis */
12
+ align?: Align;
13
+ /** Allow items to wrap onto multiple lines */
14
+ wrap?: boolean;
15
+ /** Gap between items. Numbers are treated as px */
16
+ gap?: string | number;
17
+ /** Render as inline-flex */
18
+ inline?: boolean;
19
+ children?: Snippet;
20
+ }
21
+ declare const NuiFlex: import("svelte").Component<Props, {}, "">;
22
+ type NuiFlex = ReturnType<typeof NuiFlex>;
23
+ export default NuiFlex;