@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,277 @@
1
+ <!--
2
+ @component NuiBadge
3
+ Status badge and notification counter component.
4
+ -->
5
+ <script lang="ts">
6
+ interface Props {
7
+ /** Text or snippet content rendered inside the badge. */
8
+ children?: import("svelte").Snippet;
9
+ /**
10
+ * Numerical counter value (e.g. notification count).
11
+ * Negative, non-numeric, or infinite values are normalized to 0.
12
+ * @defaultValue 0
13
+ */
14
+ count?: number;
15
+ /**
16
+ * Whether to display the counter when count is 0.
17
+ * When false (default), a count of 0 falls back to children or a dot indicator.
18
+ * @defaultValue false
19
+ */
20
+ showZero?: boolean;
21
+ /**
22
+ * Maximum displayed counter value (e.g. 99 displays as "99+").
23
+ * @defaultValue 99
24
+ */
25
+ maxCount?: number;
26
+ /**
27
+ * Color variant.
28
+ * @defaultValue 'primary'
29
+ */
30
+ variant?:
31
+ | "primary"
32
+ | "secondary"
33
+ | "success"
34
+ | "warning"
35
+ | "danger"
36
+ | "info";
37
+ /**
38
+ * Size of the badge.
39
+ * @defaultValue 'md'
40
+ */
41
+ size?: "xs" | "sm" | "md" | "lg";
42
+ /**
43
+ * Outline style with transparent background and colored border.
44
+ * @defaultValue false
45
+ */
46
+ outline?: boolean;
47
+ /**
48
+ * Pill-shaped rounded borders.
49
+ * @defaultValue true
50
+ */
51
+ rounded?: boolean;
52
+ /**
53
+ * Visibility of the badge.
54
+ * @defaultValue true
55
+ */
56
+ visible?: boolean;
57
+ /**
58
+ * Whether the badge is interactive (automatically true when onclick is provided).
59
+ * @defaultValue false
60
+ */
61
+ clickable?: boolean;
62
+ /**
63
+ * Whether the clickable badge is disabled.
64
+ * @defaultValue false
65
+ */
66
+ disabled?: boolean;
67
+ /**
68
+ * Accessible description for screen readers.
69
+ * Recommended for interactive badges or badges displaying only symbols/numbers.
70
+ */
71
+ ariaLabel?: string;
72
+ /**
73
+ * Optional role (defaults to "status" when ariaLabel is provided on a non-interactive <span>).
74
+ */
75
+ role?: string;
76
+ /** Click event handler. */
77
+ onclick?: (event: MouseEvent) => void;
78
+ }
79
+
80
+ let {
81
+ children,
82
+ count = 0,
83
+ showZero = false,
84
+ maxCount = 99,
85
+ variant = "primary",
86
+ size = "md",
87
+ outline = false,
88
+ rounded = true,
89
+ visible = true,
90
+ clickable = false,
91
+ disabled = false,
92
+ ariaLabel,
93
+ role,
94
+ onclick,
95
+ }: Props = $props();
96
+
97
+ const normalizedCount = $derived(
98
+ Number.isFinite(count) ? Math.max(0, Math.floor(count)) : 0,
99
+ );
100
+ const normalizedMaxCount = $derived(
101
+ Number.isFinite(maxCount) ? Math.max(1, Math.floor(maxCount)) : 99,
102
+ );
103
+ const showCount = $derived(
104
+ normalizedCount > 0 || (showZero && count !== undefined),
105
+ );
106
+ const displayCount = $derived(
107
+ normalizedCount > normalizedMaxCount
108
+ ? `${normalizedMaxCount}+`
109
+ : normalizedCount,
110
+ );
111
+ const isClickable = $derived(clickable || typeof onclick === "function");
112
+ const resolvedRole = $derived(
113
+ role ?? (ariaLabel && !isClickable ? "status" : undefined),
114
+ );
115
+ </script>
116
+
117
+ {#if visible}
118
+ {#if isClickable}
119
+ <button
120
+ type="button"
121
+ class="nui-badge nui-badge--{variant} nui-badge--{size} nui-badge--clickable"
122
+ class:nui-badge--outline={outline}
123
+ class:nui-badge--rounded={rounded}
124
+ class:nui-badge--square={!rounded}
125
+ aria-label={ariaLabel}
126
+ {disabled}
127
+ {onclick}
128
+ >
129
+ {#if showCount}
130
+ {displayCount}
131
+ {:else if children}
132
+ {@render children()}
133
+ {:else}
134
+ <span aria-hidden="true">●</span>
135
+ {/if}
136
+ </button>
137
+ {:else}
138
+ <span
139
+ class="nui-badge nui-badge--{variant} nui-badge--{size}"
140
+ class:nui-badge--outline={outline}
141
+ class:nui-badge--rounded={rounded}
142
+ class:nui-badge--square={!rounded}
143
+ aria-label={ariaLabel}
144
+ role={resolvedRole}
145
+ >
146
+ {#if showCount}
147
+ {displayCount}
148
+ {:else if children}
149
+ {@render children()}
150
+ {:else}
151
+ <span aria-hidden="true">●</span>
152
+ {/if}
153
+ </span>
154
+ {/if}
155
+ {/if}
156
+
157
+ <style>
158
+ .nui-badge {
159
+ display: inline-flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ font: inherit;
163
+ font-weight: 600;
164
+ line-height: 1;
165
+ text-align: center;
166
+ border: 1px solid transparent;
167
+ transition:
168
+ opacity 0.15s ease,
169
+ background 0.15s ease,
170
+ border-color 0.15s ease;
171
+ box-sizing: border-box;
172
+ vertical-align: middle;
173
+ }
174
+
175
+ button.nui-badge {
176
+ appearance: none;
177
+ -webkit-appearance: none;
178
+ color: inherit;
179
+ padding: 0;
180
+ cursor: pointer;
181
+ }
182
+
183
+ button.nui-badge:focus-visible {
184
+ outline: 2px solid currentColor;
185
+ outline-offset: 2px;
186
+ }
187
+
188
+ .nui-badge--clickable {
189
+ cursor: pointer;
190
+ }
191
+
192
+ .nui-badge--clickable:hover:not(:disabled) {
193
+ opacity: 0.85;
194
+ }
195
+
196
+ .nui-badge--clickable:disabled {
197
+ opacity: 0.5;
198
+ cursor: not-allowed;
199
+ }
200
+
201
+ .nui-badge--primary {
202
+ background: var(--nui-primary, #3b82f6);
203
+ color: #fff;
204
+ }
205
+ .nui-badge--secondary {
206
+ background: var(--nui-surface-alt, #3f3f46);
207
+ color: var(--nui-fg, #fafafa);
208
+ }
209
+ .nui-badge--success {
210
+ background: var(--nui-success, #22c55e);
211
+ color: #fff;
212
+ }
213
+ .nui-badge--warning {
214
+ background: var(--nui-warning, #f59e0b);
215
+ color: #18181b;
216
+ }
217
+ .nui-badge--danger {
218
+ background: var(--nui-danger, #ef4444);
219
+ color: #fff;
220
+ }
221
+ .nui-badge--info {
222
+ background: var(--nui-info, #06b6d4);
223
+ color: #fff;
224
+ }
225
+
226
+ .nui-badge.nui-badge--outline {
227
+ background: transparent;
228
+ }
229
+ .nui-badge.nui-badge--outline.nui-badge--primary {
230
+ border-color: var(--nui-primary, #3b82f6);
231
+ color: var(--nui-primary, #3b82f6);
232
+ }
233
+ .nui-badge.nui-badge--outline.nui-badge--secondary {
234
+ border-color: var(--nui-border, #3f3f46);
235
+ color: var(--nui-fg-secondary, #8b8b99);
236
+ }
237
+ .nui-badge.nui-badge--outline.nui-badge--success {
238
+ border-color: var(--nui-success, #22c55e);
239
+ color: var(--nui-success, #22c55e);
240
+ }
241
+ .nui-badge.nui-badge--outline.nui-badge--warning {
242
+ border-color: var(--nui-warning, #f59e0b);
243
+ color: var(--nui-warning, #f59e0b);
244
+ }
245
+ .nui-badge.nui-badge--outline.nui-badge--danger {
246
+ border-color: var(--nui-danger, #ef4444);
247
+ color: var(--nui-danger, #ef4444);
248
+ }
249
+ .nui-badge.nui-badge--outline.nui-badge--info {
250
+ border-color: var(--nui-info, #06b6d4);
251
+ color: var(--nui-info, #06b6d4);
252
+ }
253
+
254
+ .nui-badge--xs {
255
+ padding: 0.125rem 0.35rem;
256
+ font-size: 0.625rem;
257
+ }
258
+ .nui-badge--sm {
259
+ padding: 0.2rem 0.5rem;
260
+ font-size: 0.75rem;
261
+ }
262
+ .nui-badge--md {
263
+ padding: 0.3rem 0.65rem;
264
+ font-size: 0.8125rem;
265
+ }
266
+ .nui-badge--lg {
267
+ padding: 0.4rem 0.85rem;
268
+ font-size: 0.9375rem;
269
+ }
270
+
271
+ .nui-badge--rounded {
272
+ border-radius: 9999px;
273
+ }
274
+ .nui-badge--square {
275
+ border-radius: var(--nui-radius, 0.25rem);
276
+ }
277
+ </style>
@@ -0,0 +1,74 @@
1
+ interface Props {
2
+ /** Text or snippet content rendered inside the badge. */
3
+ children?: import("svelte").Snippet;
4
+ /**
5
+ * Numerical counter value (e.g. notification count).
6
+ * Negative, non-numeric, or infinite values are normalized to 0.
7
+ * @defaultValue 0
8
+ */
9
+ count?: number;
10
+ /**
11
+ * Whether to display the counter when count is 0.
12
+ * When false (default), a count of 0 falls back to children or a dot indicator.
13
+ * @defaultValue false
14
+ */
15
+ showZero?: boolean;
16
+ /**
17
+ * Maximum displayed counter value (e.g. 99 displays as "99+").
18
+ * @defaultValue 99
19
+ */
20
+ maxCount?: number;
21
+ /**
22
+ * Color variant.
23
+ * @defaultValue 'primary'
24
+ */
25
+ variant?: "primary" | "secondary" | "success" | "warning" | "danger" | "info";
26
+ /**
27
+ * Size of the badge.
28
+ * @defaultValue 'md'
29
+ */
30
+ size?: "xs" | "sm" | "md" | "lg";
31
+ /**
32
+ * Outline style with transparent background and colored border.
33
+ * @defaultValue false
34
+ */
35
+ outline?: boolean;
36
+ /**
37
+ * Pill-shaped rounded borders.
38
+ * @defaultValue true
39
+ */
40
+ rounded?: boolean;
41
+ /**
42
+ * Visibility of the badge.
43
+ * @defaultValue true
44
+ */
45
+ visible?: boolean;
46
+ /**
47
+ * Whether the badge is interactive (automatically true when onclick is provided).
48
+ * @defaultValue false
49
+ */
50
+ clickable?: boolean;
51
+ /**
52
+ * Whether the clickable badge is disabled.
53
+ * @defaultValue false
54
+ */
55
+ disabled?: boolean;
56
+ /**
57
+ * Accessible description for screen readers.
58
+ * Recommended for interactive badges or badges displaying only symbols/numbers.
59
+ */
60
+ ariaLabel?: string;
61
+ /**
62
+ * Optional role (defaults to "status" when ariaLabel is provided on a non-interactive <span>).
63
+ */
64
+ role?: string;
65
+ /** Click event handler. */
66
+ onclick?: (event: MouseEvent) => void;
67
+ }
68
+ /**
69
+ * NuiBadge
70
+ * Status badge and notification counter component.
71
+ */
72
+ declare const NuiBadge: import("svelte").Component<Props, {}, "">;
73
+ type NuiBadge = ReturnType<typeof NuiBadge>;
74
+ export default NuiBadge;
@@ -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,49 @@
1
+ export type NuiCodeLanguage = 'csharp' | 'javascript' | 'typescript' | 'lua' | 'sql' | 'json' | 'xml' | 'bash' | 'css' | 'markdown' | (string & {});
2
+ interface Props {
3
+ /** Code snippet to display. */
4
+ code?: string;
5
+ /**
6
+ * Programming language for syntax highlighting classes.
7
+ * Integrates with external highlighters like highlight.js or Prism.
8
+ * @defaultValue 'csharp'
9
+ */
10
+ language?: NuiCodeLanguage;
11
+ /** Optional title or filename displayed in the header. */
12
+ title?: string;
13
+ /**
14
+ * Whether to show the copy-to-clipboard button.
15
+ * @defaultValue true
16
+ */
17
+ showCopyButton?: boolean;
18
+ /**
19
+ * Rounded container borders.
20
+ * @defaultValue true
21
+ */
22
+ rounded?: boolean;
23
+ /**
24
+ * Label for copy button in idle state.
25
+ * @defaultValue 'Copy'
26
+ */
27
+ copyLabel?: string;
28
+ /**
29
+ * Label for copy button in copied state.
30
+ * @defaultValue 'Copied!'
31
+ */
32
+ copiedLabel?: string;
33
+ /**
34
+ * Label for copy button in error state.
35
+ * @defaultValue 'Failed'
36
+ */
37
+ errorLabel?: string;
38
+ /** Callback executed on successful clipboard copy. */
39
+ onCopy?: (code: string) => void;
40
+ /** Callback executed when clipboard copy fails. */
41
+ onCopyError?: (error: unknown) => void;
42
+ }
43
+ /**
44
+ * NuiCodeBlock
45
+ * Code snippet block with optional title, language syntax classes, and clipboard copy button.
46
+ */
47
+ declare const NuiCodeBlock: import("svelte").Component<Props, {}, "">;
48
+ type NuiCodeBlock = ReturnType<typeof NuiCodeBlock>;
49
+ export default NuiCodeBlock;