@rokkit/ui 1.0.0-next.99 → 1.0.1

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 (81) hide show
  1. package/README.md +159 -1
  2. package/package.json +52 -53
  3. package/src/MarkdownRenderer.svelte +106 -0
  4. package/src/components/AlertList.svelte +59 -0
  5. package/src/components/Avatar.svelte +68 -0
  6. package/src/components/Badge.svelte +57 -0
  7. package/src/components/BreadCrumbs.svelte +94 -0
  8. package/src/components/Button.svelte +91 -0
  9. package/src/components/ButtonGroup.svelte +9 -0
  10. package/src/components/Card.svelte +71 -0
  11. package/src/components/Carousel.svelte +169 -0
  12. package/src/components/Code.svelte +189 -0
  13. package/src/components/Connector.svelte +46 -0
  14. package/src/components/Divider.svelte +24 -0
  15. package/src/components/Dropdown.svelte +202 -0
  16. package/src/components/FloatingAction.svelte +367 -0
  17. package/src/components/FloatingNavigation.svelte +256 -0
  18. package/src/components/Grid.svelte +128 -0
  19. package/src/components/ItemContent.svelte +25 -0
  20. package/src/components/LazyTree.svelte +171 -0
  21. package/src/components/List.svelte +224 -0
  22. package/src/components/Menu.svelte +270 -0
  23. package/src/components/Message.svelte +75 -0
  24. package/src/components/MultiSelect.svelte +367 -0
  25. package/src/components/PaletteManager.svelte +360 -0
  26. package/src/components/Pill.svelte +83 -0
  27. package/src/components/ProgressBar.svelte +31 -0
  28. package/src/components/Range.svelte +346 -0
  29. package/src/components/Rating.svelte +115 -0
  30. package/src/components/Reveal.svelte +46 -0
  31. package/src/components/SearchFilter.svelte +88 -0
  32. package/src/components/Select.svelte +424 -0
  33. package/src/components/Shine.svelte +87 -0
  34. package/src/components/Stack.svelte +38 -0
  35. package/src/components/StatusList.svelte +18 -0
  36. package/src/components/Stepper.svelte +174 -0
  37. package/src/components/Swatch.svelte +102 -0
  38. package/src/components/Switch.svelte +71 -0
  39. package/src/components/Table.svelte +245 -0
  40. package/src/components/Tabs.svelte +202 -0
  41. package/src/components/Tilt.svelte +68 -0
  42. package/src/components/Timeline.svelte +61 -0
  43. package/src/components/Toggle.svelte +93 -0
  44. package/src/components/Toolbar.svelte +306 -0
  45. package/src/components/ToolbarGroup.svelte +17 -0
  46. package/src/components/Tooltip.svelte +121 -0
  47. package/src/components/Tree.svelte +143 -0
  48. package/src/components/UploadFileStatus.svelte +102 -0
  49. package/src/components/UploadProgress.svelte +127 -0
  50. package/src/components/UploadTarget.svelte +126 -0
  51. package/src/components/index.ts +48 -0
  52. package/src/index.ts +59 -0
  53. package/src/markdown-plugin.ts +12 -0
  54. package/src/types/button.ts +86 -0
  55. package/src/types/code.ts +46 -0
  56. package/src/types/floating-action.ts +123 -0
  57. package/src/types/floating-navigation.ts +80 -0
  58. package/src/types/index.ts +58 -0
  59. package/src/types/list.ts +200 -0
  60. package/src/types/menu.ts +95 -0
  61. package/src/types/palette.ts +160 -0
  62. package/src/types/range.ts +51 -0
  63. package/src/types/search-filter.ts +67 -0
  64. package/src/types/select.ts +176 -0
  65. package/src/types/switch.ts +68 -0
  66. package/src/types/table.ts +219 -0
  67. package/src/types/tabs.ts +103 -0
  68. package/src/types/timeline.ts +53 -0
  69. package/src/types/toggle.ts +68 -0
  70. package/src/types/toolbar.ts +167 -0
  71. package/src/types/tooltip.ts +21 -0
  72. package/src/types/tree.ts +250 -0
  73. package/src/types/upload-file-status.ts +45 -0
  74. package/src/types/upload-progress.ts +111 -0
  75. package/src/types/upload-target.ts +71 -0
  76. package/src/utils/palette.ts +618 -0
  77. package/src/utils/shiki.ts +114 -0
  78. package/src/utils/upload.js +145 -0
  79. package/LICENSE +0 -21
  80. package/src/index.js +0 -8
  81. package/src/wrappers.js +0 -2
@@ -0,0 +1,360 @@
1
+ <script lang="ts">
2
+ import type { PaletteManagerProps, PaletteConfig, PaletteManagerIcons } from '../types/palette.js'
3
+ import { defaultRoles, defaultPresets, defaultPaletteConfig } from '../types/palette.js'
4
+ import { DEFAULT_STATE_ICONS } from '@rokkit/core'
5
+ import type { ColorRole, Shades } from '../utils/palette.js'
6
+ import {
7
+ getShades,
8
+ applyPalette,
9
+ savePalette,
10
+ loadPalette,
11
+ isHexColor,
12
+ getTailwindColorNames
13
+ } from '../utils/palette.js'
14
+
15
+ let {
16
+ value = $bindable<PaletteConfig>(structuredClone(defaultPaletteConfig)),
17
+ presets = defaultPresets,
18
+ custom = $bindable<PaletteConfig[]>([]),
19
+ storageKey,
20
+ roles = defaultRoles,
21
+ icons: userIcons = {} as PaletteManagerIcons,
22
+ autoApply = true,
23
+ showPresets = true,
24
+ showSave = true,
25
+ compact = false,
26
+ class: className = '',
27
+ onchange,
28
+ onsave,
29
+ onapply,
30
+ roleRow: roleRowSnippet
31
+ }: PaletteManagerProps = $props()
32
+
33
+ const icons = $derived({
34
+ save: DEFAULT_STATE_ICONS.action.save,
35
+ check: DEFAULT_STATE_ICONS.action.check,
36
+ presets: DEFAULT_STATE_ICONS.palette.presets,
37
+ hex: DEFAULT_STATE_ICONS.palette.hex,
38
+ ...userIcons
39
+ })
40
+
41
+ // Available Tailwind colors for the select dropdown
42
+ const tailwindColorNames = getTailwindColorNames()
43
+
44
+ // Track hex input values for each role (for custom hex entry)
45
+ let hexInputs = $state<Record<string, string>>({})
46
+
47
+ // Track which role has hex input visible
48
+ let showHexInput = $state<Record<string, boolean>>({})
49
+
50
+ // Computed shades for each role
51
+ const roleShades = $derived.by(() => {
52
+ const shades: Record<string, Shades> = {}
53
+ for (const role of roles) {
54
+ const color = value?.mapping[role]
55
+ if (color) {
56
+ try {
57
+ shades[role] = getShades(color)
58
+ } catch {
59
+ // Invalid color, use a fallback
60
+ shades[role] = getShades('gray')
61
+ }
62
+ }
63
+ }
64
+ return shades
65
+ })
66
+
67
+ // Load saved palette on mount
68
+ $effect(() => {
69
+ if (storageKey) {
70
+ const saved = loadPalette(storageKey)
71
+ if (saved) {
72
+ value = { name: 'Saved', mapping: saved }
73
+ if (autoApply) {
74
+ applyPalette(saved)
75
+ }
76
+ }
77
+ }
78
+ })
79
+
80
+ /**
81
+ * Update a role's color
82
+ */
83
+ function updateRoleColor(role: ColorRole, color: string) {
84
+ if (!value) {
85
+ value = structuredClone(defaultPaletteConfig)
86
+ }
87
+ value = {
88
+ ...value,
89
+ mapping: {
90
+ ...value.mapping,
91
+ [role]: color
92
+ }
93
+ }
94
+ onchange?.(value)
95
+ if (autoApply) {
96
+ applyPalette(value.mapping)
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Handle select change for a role
102
+ */
103
+ function handleSelectChange(role: ColorRole, event: Event) {
104
+ const select = event.target as HTMLSelectElement
105
+ const color = select.value
106
+ if (color) {
107
+ updateRoleColor(role, color)
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Handle hex input change
113
+ */
114
+ function handleHexInput(role: ColorRole, event: Event) {
115
+ const input = event.target as HTMLInputElement
116
+ let hex = input.value.trim()
117
+
118
+ // Add # if missing
119
+ if (hex && !hex.startsWith('#')) {
120
+ hex = `#${hex}`
121
+ }
122
+
123
+ hexInputs[role] = hex
124
+
125
+ // Only update if valid hex
126
+ if (isHexColor(hex)) {
127
+ updateRoleColor(role, hex)
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Toggle hex input visibility for a role
133
+ */
134
+ function toggleHexInput(role: ColorRole) {
135
+ showHexInput[role] = !showHexInput[role]
136
+ if (showHexInput[role]) {
137
+ // Initialize hex input with current color if it's a hex
138
+ const current = value?.mapping[role]
139
+ if (current && isHexColor(current)) {
140
+ hexInputs[role] = current
141
+ } else {
142
+ hexInputs[role] = ''
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Apply the current palette
149
+ */
150
+ function handleApply() {
151
+ if (value) {
152
+ applyPalette(value.mapping)
153
+ if (storageKey) {
154
+ savePalette(storageKey, value.mapping)
155
+ }
156
+ onapply?.(value)
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Save current palette as custom
162
+ */
163
+ function handleSave() {
164
+ if (!value) return
165
+
166
+ const name = prompt('Enter a name for this palette:', value.name || 'Custom Palette')
167
+ if (!name) return
168
+
169
+ const newPalette: PaletteConfig = {
170
+ name,
171
+ mapping: { ...value.mapping }
172
+ }
173
+
174
+ custom = [...custom, newPalette]
175
+ onsave?.(newPalette)
176
+ }
177
+
178
+ /**
179
+ * Load a preset palette
180
+ */
181
+ function loadPreset(preset: { name: string; color: string }) {
182
+ value = {
183
+ name: preset.name,
184
+ mapping: {
185
+ ...value?.mapping,
186
+ primary: preset.color
187
+ }
188
+ }
189
+ onchange?.(value)
190
+ if (autoApply) {
191
+ applyPalette(value.mapping)
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Load a custom palette
197
+ */
198
+ function loadCustomPalette(palette: PaletteConfig) {
199
+ value = structuredClone(palette)
200
+ onchange?.(value)
201
+ if (autoApply) {
202
+ applyPalette(value.mapping)
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Get the current color for a role
208
+ */
209
+ function getRoleColor(role: ColorRole): string {
210
+ return value?.mapping[role] || ''
211
+ }
212
+
213
+ /**
214
+ * Check if current color is a Tailwind preset
215
+ */
216
+ function isTailwindColor(color: string): boolean {
217
+ return tailwindColorNames.includes(color.toLowerCase())
218
+ }
219
+ </script>
220
+
221
+ {#snippet shadePreview(shades: Shades)}
222
+ <div data-palette-shades>
223
+ {#each Object.entries(shades) as [shade, hex] (shade)}
224
+ <div
225
+ data-palette-shade
226
+ data-shade={shade}
227
+ style="background-color: {hex}"
228
+ title="{shade}: {hex}"
229
+ ></div>
230
+ {/each}
231
+ </div>
232
+ {/snippet}
233
+
234
+ {#snippet defaultRoleRow(role: ColorRole, currentColor: string, shades: Shades)}
235
+ <div data-palette-role data-role={role}>
236
+ <label data-palette-role-label for="palette-{role}">
237
+ {role.charAt(0).toUpperCase() + role.slice(1)}
238
+ </label>
239
+
240
+ <div data-palette-role-controls>
241
+ {#if !showHexInput[role]}
242
+ <select
243
+ id="palette-{role}"
244
+ data-palette-color-select
245
+ value={isTailwindColor(currentColor) ? currentColor : ''}
246
+ onchange={(e) => handleSelectChange(role, e)}
247
+ >
248
+ <option value="" disabled>Select color...</option>
249
+ {#each tailwindColorNames as color (color)}
250
+ <option value={color}>{color.charAt(0).toUpperCase() + color.slice(1)}</option>
251
+ {/each}
252
+ </select>
253
+ {:else}
254
+ <input
255
+ type="text"
256
+ data-palette-color-input
257
+ placeholder="#000000"
258
+ value={hexInputs[role] || ''}
259
+ oninput={(e) => handleHexInput(role, e)}
260
+ />
261
+ {/if}
262
+
263
+ <button
264
+ type="button"
265
+ data-palette-hex-toggle
266
+ onclick={() => toggleHexInput(role)}
267
+ title={showHexInput[role] ? 'Select from presets' : 'Enter custom hex'}
268
+ >
269
+ {#if showHexInput[role]}
270
+ <span data-palette-presets-icon class={icons.presets} aria-hidden="true"></span>
271
+ {:else}
272
+ <span data-palette-hex-icon class={icons.hex} aria-hidden="true"></span>
273
+ {/if}
274
+ </button>
275
+ </div>
276
+
277
+ {@render shadePreview(shades)}
278
+ </div>
279
+ {/snippet}
280
+
281
+ <div data-palette-manager data-compact={compact || undefined} class={className || undefined}>
282
+ {#if showPresets && presets.length > 0}
283
+ <div data-palette-presets>
284
+ <span data-palette-presets-label>Presets</span>
285
+ <div data-palette-presets-list>
286
+ {#each presets as preset (preset.color)}
287
+ <button
288
+ type="button"
289
+ data-palette-preset
290
+ data-active={value?.mapping.primary === preset.color || undefined}
291
+ style="--preset-color: {preset.preview}"
292
+ onclick={() => loadPreset(preset)}
293
+ title={preset.name}
294
+ >
295
+ <span data-palette-preset-swatch></span>
296
+ {#if !compact}
297
+ <span data-palette-preset-name>{preset.name}</span>
298
+ {/if}
299
+ </button>
300
+ {/each}
301
+ </div>
302
+ </div>
303
+ {/if}
304
+
305
+ {#if custom.length > 0}
306
+ <div data-palette-custom>
307
+ <span data-palette-custom-label>Custom Palettes</span>
308
+ <div data-palette-custom-list>
309
+ {#each custom as palette (palette.name)}
310
+ <button
311
+ type="button"
312
+ data-palette-custom-item
313
+ data-active={value?.name === palette.name || undefined}
314
+ onclick={() => loadCustomPalette(palette)}
315
+ >
316
+ {palette.name}
317
+ </button>
318
+ {/each}
319
+ </div>
320
+ </div>
321
+ {/if}
322
+
323
+ <div data-palette-editor>
324
+ {#each roles as role (role)}
325
+ {@const currentColor = getRoleColor(role)}
326
+ {@const shades = roleShades[role]}
327
+ {#if shades}
328
+ {#if roleRowSnippet}
329
+ {@render roleRowSnippet(role, currentColor, shades, {
330
+ onColorChange: (color) => updateRoleColor(role, color),
331
+ onHexInput: (hex) => {
332
+ hexInputs[role] = hex
333
+ if (isHexColor(hex)) {
334
+ updateRoleColor(role, hex)
335
+ }
336
+ }
337
+ })}
338
+ {:else}
339
+ {@render defaultRoleRow(role, currentColor, shades)}
340
+ {/if}
341
+ {/if}
342
+ {/each}
343
+ </div>
344
+
345
+ <div data-palette-actions>
346
+ {#if showSave}
347
+ <button type="button" data-palette-save onclick={handleSave}>
348
+ <span data-palette-save-icon class={icons.save} aria-hidden="true"></span>
349
+ Save as Custom
350
+ </button>
351
+ {/if}
352
+
353
+ {#if !autoApply}
354
+ <button type="button" data-palette-apply onclick={handleApply}>
355
+ <span data-palette-check-icon class={icons.check} aria-hidden="true"></span>
356
+ Apply
357
+ </button>
358
+ {/if}
359
+ </div>
360
+ </div>
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import { ProxyItem } from '@rokkit/states'
4
+ import { keyboard } from '@rokkit/actions'
5
+ import { DEFAULT_STATE_ICONS } from '@rokkit/core'
6
+
7
+ interface PillIcons {
8
+ remove?: string
9
+ }
10
+
11
+ interface PillProps {
12
+ /** Item data (string or object) */
13
+ value: unknown
14
+ /** Custom field mappings */
15
+ fields?: Record<string, string>
16
+ /** Show remove button */
17
+ removable?: boolean
18
+ /** Disabled state */
19
+ disabled?: boolean
20
+ /** Custom icons */
21
+ icons?: PillIcons
22
+ /** Called when remove is triggered (click or Delete/Backspace key) */
23
+ onremove?: (value: unknown) => void
24
+ /** Custom content snippet */
25
+ content?: Snippet<[ProxyItem]>
26
+ /** Additional CSS class */
27
+ class?: string
28
+ }
29
+
30
+ const {
31
+ value,
32
+ fields,
33
+ removable = false,
34
+ disabled = false,
35
+ icons: userIcons = {} as PillIcons,
36
+ onremove,
37
+ content,
38
+ class: className = ''
39
+ }: PillProps = $props()
40
+
41
+ const icons = $derived({ remove: DEFAULT_STATE_ICONS.action.remove, ...userIcons })
42
+
43
+ const proxy = $derived(new ProxyItem(value, fields))
44
+
45
+ const keyMap = $derived(removable && !disabled ? { remove: ['Delete', 'Backspace'] } : {})
46
+
47
+ function handleRemove() {
48
+ if (!disabled) {
49
+ onremove?.(value)
50
+ }
51
+ }
52
+ </script>
53
+
54
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
55
+ <span
56
+ data-pill
57
+ data-pill-disabled={disabled || undefined}
58
+ class={className || undefined}
59
+ use:keyboard={keyMap}
60
+ onremove={handleRemove}
61
+ tabindex={removable && !disabled ? 0 : undefined}
62
+ >
63
+ {#if content}
64
+ {@render content(proxy)}
65
+ {:else}
66
+ {#if proxy.get('icon')}
67
+ <span data-pill-icon class={proxy.get('icon')} aria-hidden="true"></span>
68
+ {/if}
69
+ <span data-pill-text>{proxy.label}</span>
70
+ {/if}
71
+
72
+ {#if removable}
73
+ <button
74
+ data-pill-remove
75
+ type="button"
76
+ aria-label="Remove {proxy.label}"
77
+ {disabled}
78
+ onclick={handleRemove}
79
+ >
80
+ <span data-pill-remove-icon class={icons.remove} aria-hidden="true"></span>
81
+ </button>
82
+ {/if}
83
+ </span>
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ interface ProgressBarProps {
3
+ /** Current progress value (null = indeterminate) */
4
+ value?: number | null
5
+ /** Maximum value (default: 100) */
6
+ max?: number
7
+ /** Additional CSS class */
8
+ class?: string
9
+ }
10
+
11
+ const { value = null, max = 100, class: className = '' }: ProgressBarProps = $props()
12
+
13
+ const indeterminate = $derived(
14
+ value === null || value === undefined || max === null || max === undefined
15
+ )
16
+ const percentage = $derived(
17
+ indeterminate ? 100 : Math.min(100, Math.max(0, (value! / max!) * 100))
18
+ )
19
+ </script>
20
+
21
+ <div
22
+ data-progress
23
+ data-indeterminate={indeterminate || undefined}
24
+ class={className || undefined}
25
+ role="progressbar"
26
+ aria-valuenow={indeterminate ? undefined : value}
27
+ aria-valuemin={0}
28
+ aria-valuemax={max}
29
+ >
30
+ <div data-progress-bar style:width="{percentage}%"></div>
31
+ </div>