@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,256 @@
1
+ <!--
2
+ @component NuiCodeBlock
3
+ Code snippet block with optional title, language syntax classes, and clipboard copy button.
4
+ -->
5
+ <script module lang="ts">
6
+ export type NuiCodeLanguage =
7
+ | 'csharp'
8
+ | 'javascript'
9
+ | 'typescript'
10
+ | 'lua'
11
+ | 'sql'
12
+ | 'json'
13
+ | 'xml'
14
+ | 'bash'
15
+ | 'css'
16
+ | 'markdown'
17
+ | (string & {});
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import { onDestroy } from 'svelte';
22
+
23
+ interface Props {
24
+ /** Code snippet to display. */
25
+ code?: string;
26
+ /**
27
+ * Programming language for syntax highlighting classes.
28
+ * Integrates with external highlighters like highlight.js or Prism.
29
+ * @defaultValue 'csharp'
30
+ */
31
+ language?: NuiCodeLanguage;
32
+ /** Optional title or filename displayed in the header. */
33
+ title?: string;
34
+ /**
35
+ * Whether to show the copy-to-clipboard button.
36
+ * @defaultValue true
37
+ */
38
+ showCopyButton?: boolean;
39
+ /**
40
+ * Rounded container borders.
41
+ * @defaultValue true
42
+ */
43
+ rounded?: boolean;
44
+ /**
45
+ * Label for copy button in idle state.
46
+ * @defaultValue 'Copy'
47
+ */
48
+ copyLabel?: string;
49
+ /**
50
+ * Label for copy button in copied state.
51
+ * @defaultValue 'Copied!'
52
+ */
53
+ copiedLabel?: string;
54
+ /**
55
+ * Label for copy button in error state.
56
+ * @defaultValue 'Failed'
57
+ */
58
+ errorLabel?: string;
59
+ /** Callback executed on successful clipboard copy. */
60
+ onCopy?: (code: string) => void;
61
+ /** Callback executed when clipboard copy fails. */
62
+ onCopyError?: (error: unknown) => void;
63
+ }
64
+
65
+ let {
66
+ code = '',
67
+ language = 'csharp',
68
+ title,
69
+ showCopyButton = true,
70
+ rounded = true,
71
+ copyLabel = 'Copy',
72
+ copiedLabel = 'Copied!',
73
+ errorLabel = 'Failed',
74
+ onCopy,
75
+ onCopyError
76
+ }: Props = $props();
77
+
78
+ let copying = $state(false);
79
+ let copied = $state(false);
80
+ let copyFailed = $state(false);
81
+ let copyTimeout: ReturnType<typeof setTimeout> | undefined;
82
+
83
+ onDestroy(() => {
84
+ if (copyTimeout !== undefined) {
85
+ clearTimeout(copyTimeout);
86
+ copyTimeout = undefined;
87
+ }
88
+ });
89
+
90
+ async function copyToClipboard() {
91
+ if (copying) return;
92
+
93
+ if (copyTimeout !== undefined) {
94
+ clearTimeout(copyTimeout);
95
+ copyTimeout = undefined;
96
+ }
97
+
98
+ copying = true;
99
+ copied = false;
100
+ copyFailed = false;
101
+
102
+ let writeSuccess = false;
103
+ let writeError: unknown;
104
+
105
+ try {
106
+ if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {
107
+ throw new Error('Clipboard API not available');
108
+ }
109
+ await navigator.clipboard.writeText(code);
110
+ writeSuccess = true;
111
+ } catch (e) {
112
+ writeError = e;
113
+ console.warn('[fivem-nui-components] Clipboard copy failed:', e);
114
+ }
115
+
116
+ copying = false;
117
+
118
+ if (writeSuccess) {
119
+ copied = true;
120
+ try {
121
+ onCopy?.(code);
122
+ } catch (cbErr) {
123
+ console.error('[fivem-nui-components] onCopy callback error:', cbErr);
124
+ }
125
+ } else {
126
+ copyFailed = true;
127
+ try {
128
+ onCopyError?.(writeError);
129
+ } catch (cbErr) {
130
+ console.error('[fivem-nui-components] onCopyError callback error:', cbErr);
131
+ }
132
+ }
133
+
134
+ copyTimeout = setTimeout(() => {
135
+ copied = false;
136
+ copyFailed = false;
137
+ copyTimeout = undefined;
138
+ }, 2000);
139
+ }
140
+ </script>
141
+
142
+ <div class="nui-code-block" class:nui-code-block--rounded={rounded}>
143
+ {#if title || showCopyButton}
144
+ <div class="nui-code-block__header">
145
+ {#if title}<span class="nui-code-block__title">{title}</span>{/if}
146
+ {#if showCopyButton}
147
+ <button
148
+ type="button"
149
+ class="nui-code-block__copy"
150
+ class:nui-code-block__copy--copied={copied}
151
+ class:nui-code-block__copy--error={copyFailed}
152
+ disabled={copying}
153
+ onclick={copyToClipboard}
154
+ aria-label={copied ? copiedLabel : copyFailed ? errorLabel : copyLabel}
155
+ >
156
+ <span aria-live="polite" aria-atomic="true">
157
+ {copied ? copiedLabel : copyFailed ? errorLabel : copyLabel}
158
+ </span>
159
+ </button>
160
+ {/if}
161
+ </div>
162
+ {/if}
163
+
164
+ <pre class="nui-code-block__pre"><code class="language-{language}">{code}</code></pre>
165
+ </div>
166
+
167
+ <style>
168
+ .nui-code-block {
169
+ width: 100%;
170
+ font-family: 'Consolas', 'Monaco', monospace;
171
+ }
172
+
173
+ .nui-code-block__header {
174
+ display: flex;
175
+ justify-content: space-between;
176
+ align-items: center;
177
+ padding: 0.5rem 1rem;
178
+ background: var(--nui-surface-alt, #101012);
179
+ border: 1px solid var(--nui-border, #27272a);
180
+ border-bottom: none;
181
+ }
182
+
183
+ .nui-code-block__title {
184
+ font-size: 0.8125rem;
185
+ color: var(--nui-fg-secondary, #8b8b99);
186
+ font-weight: 500;
187
+ }
188
+
189
+ .nui-code-block__copy {
190
+ background: transparent;
191
+ border: 1px solid var(--nui-border, #3f3f46);
192
+ border-radius: var(--nui-radius, 0.25rem);
193
+ padding: 0.25rem 0.5rem;
194
+ font-size: 0.75rem;
195
+ color: var(--nui-fg, #fafafa);
196
+ cursor: pointer;
197
+ transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
198
+ }
199
+
200
+ .nui-code-block__copy:hover:not(:disabled) {
201
+ background: var(--nui-hover, rgb(255 255 255 / 0.06));
202
+ }
203
+
204
+ .nui-code-block__copy:disabled {
205
+ opacity: 0.6;
206
+ cursor: default;
207
+ }
208
+
209
+ .nui-code-block__copy:focus-visible {
210
+ outline: 2px solid var(--nui-primary, #3b82f6);
211
+ outline-offset: 2px;
212
+ }
213
+
214
+ .nui-code-block__copy--copied {
215
+ border-color: var(--nui-success, #22c55e);
216
+ color: var(--nui-success, #22c55e);
217
+ }
218
+
219
+ .nui-code-block__copy--error {
220
+ border-color: var(--nui-danger, #ef4444);
221
+ color: var(--nui-danger, #ef4444);
222
+ }
223
+
224
+ .nui-code-block__pre {
225
+ margin: 0;
226
+ padding: 1rem;
227
+ background: var(--nui-surface, #18181b);
228
+ border: 1px solid var(--nui-border, #27272a);
229
+ overflow-x: auto;
230
+ color: var(--nui-fg, #fafafa);
231
+ }
232
+
233
+ .nui-code-block--rounded .nui-code-block__header {
234
+ border-radius: var(--nui-radius, 0.5rem) var(--nui-radius, 0.5rem) 0 0;
235
+ }
236
+
237
+ .nui-code-block--rounded .nui-code-block__pre {
238
+ border-radius: var(--nui-radius, 0.5rem);
239
+ }
240
+
241
+ .nui-code-block--rounded .nui-code-block__header + .nui-code-block__pre {
242
+ border-radius: 0 0 var(--nui-radius, 0.5rem) var(--nui-radius, 0.5rem);
243
+ }
244
+
245
+ .nui-code-block__pre code {
246
+ font-size: 0.8125rem;
247
+ line-height: 1.5;
248
+ }
249
+
250
+ /* Scoped syntax highlighting tokens (when external highlighter is applied) */
251
+ .nui-code-block__pre :global(.string) { color: #facc15; }
252
+ .nui-code-block__pre :global(.keyword) { color: #60a5fa; font-weight: 600; }
253
+ .nui-code-block__pre :global(.function) { color: #a7f3d0; }
254
+ .nui-code-block__pre :global(.comment) { color: #6b7280; opacity: 0.8; font-style: italic; }
255
+ .nui-code-block__pre :global(.number) { color: #f472b6; }
256
+ </style>
@@ -0,0 +1,341 @@
1
+ <!--
2
+ @component NuiList
3
+ Interactive list component supporting actions, icons, descriptions, and toggle switches.
4
+
5
+ Controlled Component:
6
+ Switch items follow a controlled state pattern where `switchValue` reflects the authoritative state
7
+ from backend/parent data. Toggling emits `switchEvent` and `onSwitchToggle` to request a state change.
8
+ -->
9
+ <script module lang="ts">
10
+ /**
11
+ * Represents an item in the {@link NuiList} component.
12
+ */
13
+ export interface NuiListItem {
14
+ /** Unique item identifier. Highly recommended for dynamic or reorderable lists. */
15
+ id?: string;
16
+ /** Primary display label. */
17
+ label: string;
18
+ /** Associated value (should be JSON-serializable if transmitted via NUI). */
19
+ value?: unknown;
20
+ /** Icon string, emoji, or icon character. */
21
+ icon?: string;
22
+ /** Secondary description text. */
23
+ description?: string;
24
+ /** Whether to render a toggle switch instead of an action button. */
25
+ switch?: boolean;
26
+ /** Current state of the toggle switch. */
27
+ switchValue?: boolean;
28
+ /** NUI event sent to game client when switch state is toggled. */
29
+ switchEvent?: string;
30
+ /** Item-specific NUI event sent on item click. */
31
+ eventName?: string;
32
+ /** Whether the item is disabled. */
33
+ disabled?: boolean;
34
+ }
35
+ </script>
36
+
37
+ <script lang="ts">
38
+ import { nui } from '@fivem-nui/svelte';
39
+ import type { SvelteNuiClient } from '@fivem-nui/svelte';
40
+
41
+ type MaybePromise<T> = T | Promise<T>;
42
+
43
+ interface Props {
44
+ /** Array of list items to display. */
45
+ items?: NuiListItem[];
46
+ /** SvelteNuiClient instance for event transport. */
47
+ client?: SvelteNuiClient;
48
+ /** Vertical density sizing. */
49
+ itemSize?: 'sm' | 'md' | 'lg';
50
+ /** Global NUI event name sent with `{ id, value }` when any row is activated. */
51
+ selectEvent?: string;
52
+ /** Local click callback executed when an item is activated (supports sync or async). */
53
+ onItemClick?: (item: NuiListItem) => MaybePromise<void>;
54
+ /** Local change callback executed when a switch item is toggled (supports sync or async). */
55
+ onSwitchToggle?: (item: NuiListItem, value: boolean) => MaybePromise<void>;
56
+ }
57
+
58
+ let {
59
+ items = [],
60
+ client = nui,
61
+ itemSize = 'md',
62
+ selectEvent = '',
63
+ onItemClick,
64
+ onSwitchToggle
65
+ }: Props = $props();
66
+
67
+ function safeCall(fn: (() => unknown) | undefined, errorPrefix: string): void {
68
+ if (!fn) return;
69
+ try {
70
+ const result = fn();
71
+ if (result && typeof (result as Promise<unknown>).catch === 'function') {
72
+ (result as Promise<unknown>).catch((err) => {
73
+ console.error(errorPrefix, err);
74
+ });
75
+ }
76
+ } catch (err) {
77
+ console.error(errorPrefix, err);
78
+ }
79
+ }
80
+
81
+ function handleItemClick(item: NuiListItem) {
82
+ if (item.disabled) return;
83
+ if (item.eventName) {
84
+ try {
85
+ client.send(item.eventName, { value: item.value, id: item.id });
86
+ } catch (err) {
87
+ console.error(`[fivem-nui-components] Failed to send event '${item.eventName}':`, err);
88
+ }
89
+ }
90
+ if (selectEvent) {
91
+ try {
92
+ client.send(selectEvent, { id: item.id, value: item.value });
93
+ } catch (err) {
94
+ console.error(`[fivem-nui-components] Failed to send selectEvent '${selectEvent}':`, err);
95
+ }
96
+ }
97
+ safeCall(
98
+ () => onItemClick?.(item),
99
+ '[fivem-nui-components] onItemClick callback error:'
100
+ );
101
+ }
102
+
103
+ function handleSwitch(item: NuiListItem) {
104
+ if (item.disabled) return;
105
+ const nextVal = !(item.switchValue ?? false);
106
+ if (item.switchEvent) {
107
+ try {
108
+ client.send(item.switchEvent, { id: item.id, value: nextVal });
109
+ } catch (err) {
110
+ console.error(`[fivem-nui-components] Failed to send switchEvent '${item.switchEvent}':`, err);
111
+ }
112
+ }
113
+ safeCall(
114
+ () => onSwitchToggle?.(item, nextVal),
115
+ '[fivem-nui-components] onSwitchToggle callback error:'
116
+ );
117
+ }
118
+ </script>
119
+
120
+ <ul class="nui-list nui-list--{itemSize}">
121
+ {#each items as item, index (item.id ?? `${item.label}-${index}`)}
122
+ <li class="nui-list-item" class:nui-list-item--switch={item.switch}>
123
+ {#if item.switch}
124
+ <label class="nui-list-item-label" class:disabled={item.disabled}>
125
+ {#if item.icon}
126
+ <span class="nui-list-item-icon" aria-hidden="true">{item.icon}</span>
127
+ {/if}
128
+ <span class="nui-list-item-text">
129
+ <span class="nui-list-item-title">{item.label}</span>
130
+ {#if item.description}
131
+ <span class="nui-list-item-description">{item.description}</span>
132
+ {/if}
133
+ </span>
134
+ <span class="nui-switch-wrapper">
135
+ <input
136
+ type="checkbox"
137
+ class="nui-switch__input"
138
+ checked={item.switchValue ?? false}
139
+ disabled={item.disabled}
140
+ onchange={() => handleSwitch(item)}
141
+ />
142
+ <span class="nui-switch" class:checked={item.switchValue ?? false} aria-hidden="true">
143
+ <span class="nui-switch__thumb"></span>
144
+ </span>
145
+ </span>
146
+ </label>
147
+ {:else}
148
+ <button
149
+ type="button"
150
+ class="nui-list-item-btn"
151
+ disabled={item.disabled}
152
+ onclick={() => handleItemClick(item)}
153
+ >
154
+ {#if item.icon}
155
+ <span class="nui-list-item-icon" aria-hidden="true">{item.icon}</span>
156
+ {/if}
157
+ <span class="nui-list-item-text">
158
+ <span class="nui-list-item-title">{item.label}</span>
159
+ {#if item.description}
160
+ <span class="nui-list-item-description">{item.description}</span>
161
+ {/if}
162
+ </span>
163
+ </button>
164
+ {/if}
165
+ </li>
166
+ {/each}
167
+ </ul>
168
+
169
+ <style>
170
+ .nui-list {
171
+ list-style: none;
172
+ padding: 0;
173
+ margin: 0;
174
+ display: flex;
175
+ flex-direction: column;
176
+ font-family: inherit;
177
+ width: 100%;
178
+ }
179
+
180
+ .nui-list-item {
181
+ display: flex;
182
+ align-items: center;
183
+ border-bottom: 1px solid var(--nui-border, #27272a);
184
+ }
185
+
186
+ .nui-list-item:last-child {
187
+ border-bottom: none;
188
+ }
189
+
190
+ .nui-list-item-btn,
191
+ .nui-list-item-label {
192
+ box-sizing: border-box;
193
+ }
194
+
195
+ .nui-list--sm .nui-list-item-btn,
196
+ .nui-list--sm .nui-list-item-label {
197
+ padding: 0.4rem 0.625rem;
198
+ }
199
+ .nui-list--md .nui-list-item-btn,
200
+ .nui-list--md .nui-list-item-label {
201
+ padding: 0.6rem 0.875rem;
202
+ }
203
+ .nui-list--lg .nui-list-item-btn,
204
+ .nui-list--lg .nui-list-item-label {
205
+ padding: 0.85rem 1.125rem;
206
+ }
207
+
208
+ .nui-list-item-btn {
209
+ appearance: none;
210
+ -webkit-appearance: none;
211
+ border: none;
212
+ background: transparent;
213
+ padding: 0;
214
+ margin: 0;
215
+ font: inherit;
216
+ color: inherit;
217
+ text-align: left;
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 0.75rem;
221
+ width: 100%;
222
+ cursor: pointer;
223
+ border-radius: var(--nui-radius, 0.375rem);
224
+ transition: background 0.15s ease;
225
+ }
226
+
227
+ .nui-list-item-btn:hover:not(:disabled) {
228
+ background: var(--nui-hover, rgb(255 255 255 / 0.04));
229
+ }
230
+
231
+ .nui-list-item-btn:disabled {
232
+ opacity: 0.45;
233
+ cursor: not-allowed;
234
+ }
235
+
236
+ .nui-list-item-btn:focus-visible {
237
+ outline: 2px solid var(--nui-primary, #3b82f6);
238
+ outline-offset: 2px;
239
+ }
240
+
241
+ .nui-list-item-label {
242
+ display: flex;
243
+ align-items: center;
244
+ justify-content: space-between;
245
+ gap: 0.75rem;
246
+ width: 100%;
247
+ font-size: 0.875rem;
248
+ cursor: pointer;
249
+ border-radius: var(--nui-radius, 0.375rem);
250
+ transition: background 0.15s ease;
251
+ }
252
+
253
+ .nui-list-item-label:hover:not(.disabled) {
254
+ background: var(--nui-hover, rgb(255 255 255 / 0.04));
255
+ }
256
+
257
+ .nui-list-item-label.disabled {
258
+ opacity: 0.45;
259
+ cursor: not-allowed;
260
+ }
261
+
262
+ .nui-list-item-icon {
263
+ font-size: 1.15rem;
264
+ line-height: 1;
265
+ flex-shrink: 0;
266
+ }
267
+
268
+ .nui-list-item-text {
269
+ display: flex;
270
+ flex-direction: column;
271
+ line-height: 1.3;
272
+ flex: 1;
273
+ min-width: 0;
274
+ }
275
+
276
+ .nui-list-item-title {
277
+ font-weight: 500;
278
+ font-size: 0.875rem;
279
+ color: var(--nui-fg, #fafafa);
280
+ overflow-wrap: anywhere;
281
+ }
282
+
283
+ .nui-list-item-description {
284
+ font-size: 0.75rem;
285
+ opacity: 0.65;
286
+ overflow-wrap: anywhere;
287
+ }
288
+
289
+ .nui-switch-wrapper {
290
+ position: relative;
291
+ display: inline-flex;
292
+ align-items: center;
293
+ flex-shrink: 0;
294
+ }
295
+
296
+ .nui-switch__input {
297
+ position: absolute;
298
+ opacity: 0;
299
+ width: 0;
300
+ height: 0;
301
+ margin: 0;
302
+ pointer-events: none;
303
+ }
304
+
305
+ .nui-switch {
306
+ flex: none;
307
+ width: 2.25rem;
308
+ height: 1.25rem;
309
+ padding: 0.125rem;
310
+ border-radius: 999px;
311
+ background: var(--nui-surface-alt, #3f3f46);
312
+ border: 1px solid var(--nui-border, #52525b);
313
+ transition: background 0.2s ease, border-color 0.2s ease;
314
+ box-sizing: border-box;
315
+ display: inline-flex;
316
+ align-items: center;
317
+ }
318
+
319
+ .nui-switch.checked {
320
+ background: var(--nui-primary, #3b82f6);
321
+ border-color: var(--nui-primary, #3b82f6);
322
+ }
323
+
324
+ .nui-switch__input:focus-visible + .nui-switch {
325
+ outline: 2px solid var(--nui-primary, #3b82f6);
326
+ outline-offset: 3px;
327
+ }
328
+
329
+ .nui-switch__thumb {
330
+ display: block;
331
+ width: 0.95rem;
332
+ height: 0.95rem;
333
+ border-radius: 50%;
334
+ background: #fff;
335
+ transition: transform 0.2s ease;
336
+ }
337
+
338
+ .nui-switch.checked .nui-switch__thumb {
339
+ transform: translateX(1rem);
340
+ }
341
+ </style>