@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,202 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Tabs — Wrapper + Navigator + ProxyItem implementation.
4
+ *
5
+ * Architecture:
6
+ * Wrapper — owns focusedKey $state + flatView $derived
7
+ * Navigator — attaches DOM event handlers, calls wrapper[action](path)
8
+ * owns focus + scrollIntoView after every keyboard action
9
+ * flatView loop — single flat {#each} for tab triggers
10
+ *
11
+ * Snippet customization:
12
+ * itemContent — replaces inner content of <button> for tab triggers
13
+ * tabPanel — replaces panel content
14
+ * [named] — per-item override via item.snippet = 'name'; falls back to itemContent
15
+ * empty — rendered when no options
16
+ *
17
+ * Tab panels are rendered separately from triggers. Only the active panel
18
+ * receives data-panel-active. Navigator ignores panels (no data-path on them).
19
+ */
20
+ // @ts-nocheck
21
+ import type { TabsProps } from '../types/tabs.js'
22
+ import type { ProxyItem } from '@rokkit/states'
23
+ import { Wrapper, ProxyTree, messages } from '@rokkit/states'
24
+ import { Navigator } from '@rokkit/actions'
25
+ import { resolveSnippet, ITEM_SNIPPET, DEFAULT_STATE_ICONS } from '@rokkit/core'
26
+
27
+ let {
28
+ options = [],
29
+ fields: userFields = {},
30
+ value = $bindable(),
31
+ orientation = 'horizontal',
32
+ position = 'before',
33
+ align = 'start',
34
+ name = 'tabs',
35
+ editable = false,
36
+ placeholder = messages.tabs.placeholder,
37
+ disabled = false,
38
+ labels: userLabels = {},
39
+ class: className = '',
40
+ onchange,
41
+ onselect,
42
+ onadd,
43
+ onremove,
44
+ ...snippets
45
+ }: TabsProps & { labels?: Record<string, string>; [key: string]: unknown } = $props()
46
+
47
+ const labels = $derived({ ...messages.tabs, ...userLabels })
48
+
49
+ // ─── Wrapper ──────────────────────────────────────────────────────────────
50
+
51
+ const proxyTree = $derived(new ProxyTree(options, userFields))
52
+ const wrapper = $derived(
53
+ new Wrapper(proxyTree, {
54
+ onchange,
55
+ onselect: (v, proxy) => {
56
+ value = v
57
+ onselect?.(v, proxy)
58
+ }
59
+ })
60
+ )
61
+
62
+ // ─── Navigator ────────────────────────────────────────────────────────────
63
+
64
+ let containerRef = $state<HTMLElement | null>(null)
65
+
66
+ $effect(() => {
67
+ if (!containerRef) return
68
+ const nav = new Navigator(containerRef, wrapper, { orientation })
69
+ return () => nav.destroy()
70
+ })
71
+
72
+ // ─── Sync external value → focused key ────────────────────────────────────
73
+
74
+ $effect(() => {
75
+ wrapper.moveToValue(value)
76
+ })
77
+
78
+ // ─── Editable handlers ────────────────────────────────────────────────────
79
+
80
+ function handleAdd() {
81
+ onadd?.()
82
+ }
83
+
84
+ function handleRemove(proxy: ProxyItem) {
85
+ onremove?.(proxy.value)
86
+ }
87
+ </script>
88
+
89
+ {#snippet defaultTabContent(proxy: ProxyItem)}
90
+ {#if proxy.get('icon')}
91
+ <span data-tabs-icon class={proxy.get('icon')} aria-hidden="true"></span>
92
+ {/if}
93
+ <span data-tabs-label>{proxy.label}</span>
94
+ {#if editable}
95
+ <span
96
+ data-tabs-remove
97
+ role="button"
98
+ tabindex="-1"
99
+ aria-label={labels.remove}
100
+ onclick={(e) => {
101
+ e.stopPropagation()
102
+ handleRemove(proxy)
103
+ }}
104
+ onkeydown={(e) => {
105
+ if (e.key === 'Enter' || e.key === ' ') {
106
+ e.stopPropagation()
107
+ e.preventDefault()
108
+ handleRemove(proxy)
109
+ }
110
+ }}
111
+ >
112
+ <span class={DEFAULT_STATE_ICONS.action.close} aria-hidden="true"></span>
113
+ </span>
114
+ {/if}
115
+ {/snippet}
116
+
117
+ {#snippet defaultPanel(proxy: ProxyItem)}
118
+ <div data-tabs-content>
119
+ {proxy.get('content')}
120
+ </div>
121
+ {/snippet}
122
+
123
+ {#snippet defaultEmpty()}
124
+ No tabs available.
125
+ {/snippet}
126
+
127
+ <div
128
+ bind:this={containerRef}
129
+ data-tabs
130
+ data-orientation={orientation}
131
+ data-position={position}
132
+ data-align={align}
133
+ data-disabled={disabled || undefined}
134
+ class={className || undefined}
135
+ aria-label={name}
136
+ >
137
+ {#if options.length === 0}
138
+ <div data-tabs-empty>
139
+ {#if snippets.empty}
140
+ {@render snippets.empty()}
141
+ {:else}
142
+ {@render defaultEmpty()}
143
+ {/if}
144
+ </div>
145
+ {:else}
146
+ <div data-tabs-list role="tablist" aria-orientation={orientation}>
147
+ {#each wrapper.flatView as node (node.key)}
148
+ {@const proxy = node.proxy}
149
+ {@const sel = proxy.value === value}
150
+ {@const content = resolveSnippet(snippets, proxy, ITEM_SNIPPET)}
151
+
152
+ <button
153
+ type="button"
154
+ data-tabs-trigger
155
+ data-path={node.key}
156
+ data-selected={sel || undefined}
157
+ data-disabled={proxy.disabled || undefined}
158
+ role="tab"
159
+ aria-selected={sel}
160
+ aria-label={proxy.get('label') || proxy.label}
161
+ disabled={proxy.disabled || disabled}
162
+ >
163
+ {#if content}
164
+ {@render content(proxy, sel)}
165
+ {:else}
166
+ {@render defaultTabContent(proxy)}
167
+ {/if}
168
+ </button>
169
+ {/each}
170
+ {#if editable}
171
+ <button type="button" data-tabs-add aria-label={labels.add} onclick={handleAdd}>
172
+ <span class="i-lucide:plus" aria-hidden="true"></span>
173
+ </button>
174
+ {/if}
175
+ </div>
176
+
177
+ {#each wrapper.flatView as node (node.key)}
178
+ {@const proxy = node.proxy}
179
+ {@const active = proxy.value === value}
180
+
181
+ <div
182
+ data-tabs-panel
183
+ data-panel-active={active || undefined}
184
+ role="tabpanel"
185
+ id="tab-panel-{node.key}"
186
+ aria-labelledby="tab-{node.key}"
187
+ >
188
+ {#if snippets.tabPanel}
189
+ {@render snippets.tabPanel(proxy)}
190
+ {:else}
191
+ {@render defaultPanel(proxy)}
192
+ {/if}
193
+ </div>
194
+ {/each}
195
+
196
+ {#if value === undefined}
197
+ <div data-tabs-placeholder>
198
+ {placeholder}
199
+ </div>
200
+ {/if}
201
+ {/if}
202
+ </div>
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface TiltProps {
5
+ /** Maximum rotation angle in degrees (default: 10) */
6
+ maxRotation?: number
7
+ /** Whether to adjust brightness based on mouse Y position */
8
+ setBrightness?: boolean
9
+ /** CSS perspective value in pixels (default: 600) */
10
+ perspective?: number
11
+ /** Additional CSS class */
12
+ class?: string
13
+ children?: Snippet
14
+ }
15
+
16
+ const {
17
+ maxRotation = 10,
18
+ setBrightness = false,
19
+ perspective = 600,
20
+ class: className = '',
21
+ children
22
+ }: TiltProps = $props()
23
+
24
+ let width = $state(0)
25
+ let height = $state(0)
26
+
27
+ let rotateX = $state(0)
28
+ let rotateY = $state(0)
29
+ let brightness = $state(1)
30
+
31
+ /** Linear interpolation: maps value from [0, max] to [rangeMin, rangeMax] */
32
+ function lerp(value: number, max: number, rangeMin: number, rangeMax: number): number {
33
+ if (max === 0) return rangeMin
34
+ return rangeMin + (value / max) * (rangeMax - rangeMin)
35
+ }
36
+
37
+ function onMouseMove(e: MouseEvent) {
38
+ rotateY = lerp(e.offsetX, width, maxRotation, -maxRotation)
39
+ rotateX = lerp(e.offsetY, height, -maxRotation, maxRotation)
40
+
41
+ if (setBrightness) {
42
+ brightness = lerp(e.offsetY, height, 2.0, 1.0)
43
+ }
44
+ }
45
+
46
+ function onMouseLeave() {
47
+ rotateX = 0
48
+ rotateY = 0
49
+ brightness = 1
50
+ }
51
+ </script>
52
+
53
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
54
+ <div
55
+ data-tilt
56
+ data-tilt-brightness={setBrightness || undefined}
57
+ class={className || undefined}
58
+ style:--tilt-perspective="{perspective}px"
59
+ style:--tilt-rotate-x="{rotateX}deg"
60
+ style:--tilt-rotate-y="{rotateY}deg"
61
+ style:--tilt-brightness={brightness}
62
+ bind:clientWidth={width}
63
+ bind:clientHeight={height}
64
+ onmousemove={onMouseMove}
65
+ onmouseleave={onMouseLeave}
66
+ >
67
+ {@render children?.()}
68
+ </div>
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ import type { TimelineProps } from '../types/timeline.js'
3
+ import { defaultTimelineFields, defaultTimelineIcons } from '../types/timeline.js'
4
+ import { ProxyItem } from '@rokkit/states'
5
+
6
+ let {
7
+ items = [],
8
+ fields: userFields,
9
+ icons: userIcons,
10
+ class: className = '',
11
+ content
12
+ }: TimelineProps = $props()
13
+
14
+ const fields = $derived({ ...defaultTimelineFields, ...userFields })
15
+ const icons = $derived({ ...defaultTimelineIcons, ...userIcons })
16
+ </script>
17
+
18
+ <div data-timeline class={className || undefined} role="list">
19
+ {#each items as item, index (index)}
20
+ {@const proxy = new ProxyItem(item, fields)}
21
+ {@const text = proxy.label}
22
+ {@const icon = proxy.get('icon')}
23
+ {@const description = proxy.get('subtext')}
24
+ {@const completed = Boolean(item.completed)}
25
+ {@const active = Boolean(item.active)}
26
+
27
+ <div
28
+ data-timeline-item
29
+ data-completed={completed || undefined}
30
+ data-active={active || undefined}
31
+ role="listitem"
32
+ >
33
+ <div data-timeline-marker aria-hidden="true">
34
+ <div data-timeline-circle>
35
+ {#if completed}
36
+ <span class={icons.completed}></span>
37
+ {:else if icon}
38
+ <span class={icon}></span>
39
+ {:else}
40
+ {index + 1}
41
+ {/if}
42
+ </div>
43
+ {#if index < items.length - 1}
44
+ <div data-timeline-connector></div>
45
+ {/if}
46
+ </div>
47
+
48
+ <div data-timeline-body>
49
+ {#if text}
50
+ <div data-timeline-title>{text}</div>
51
+ {/if}
52
+ {#if description}
53
+ <div data-timeline-description>{description}</div>
54
+ {/if}
55
+ {#if content}
56
+ {@render content(item, index)}
57
+ {/if}
58
+ </div>
59
+ </div>
60
+ {/each}
61
+ </div>
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ // @ts-nocheck
3
+ import type { ToggleProps } from '../types/toggle.js'
4
+ import { Wrapper, ProxyTree, messages } from '@rokkit/states'
5
+ import { Navigator } from '@rokkit/actions'
6
+ import { resolveSnippet } from '@rokkit/core'
7
+
8
+ let {
9
+ options = [],
10
+ fields: userFields = {},
11
+ value = $bindable(),
12
+ onchange,
13
+ showLabels = true,
14
+ size = 'md',
15
+ disabled = false,
16
+ label = messages.toggle.label,
17
+ class: className = '',
18
+ ...snippets
19
+ }: ToggleProps & { [key: string]: unknown } = $props()
20
+
21
+ // ─── Wrapper ──────────────────────────────────────────────────────────────
22
+
23
+ const proxyTree = $derived(new ProxyTree(options, userFields))
24
+ const wrapper = $derived(new Wrapper(proxyTree, { onselect: handleSelect }))
25
+
26
+ // ─── Navigator (horizontal, always attached) ───────────────────────────────
27
+
28
+ let containerRef = $state<HTMLElement | null>(null)
29
+
30
+ $effect(() => {
31
+ if (!containerRef) return
32
+ const dir = getComputedStyle(containerRef).direction
33
+ const nav = new Navigator(containerRef, wrapper, { orientation: 'horizontal', dir })
34
+ return () => nav.destroy()
35
+ })
36
+
37
+ // ─── Sync external value → focused key ────────────────────────────────────
38
+
39
+ $effect(() => {
40
+ wrapper.moveToValue(value)
41
+ })
42
+
43
+ // ─── Selection handler ─────────────────────────────────────────────────────
44
+
45
+ function handleSelect(extractedValue, proxy) {
46
+ if (proxy.disabled || disabled) return
47
+ if (extractedValue !== value) {
48
+ value = extractedValue
49
+ onchange?.(extractedValue, proxy.original)
50
+ }
51
+ }
52
+ </script>
53
+
54
+ <div
55
+ bind:this={containerRef}
56
+ data-toggle
57
+ data-toggle-size={size}
58
+ data-toggle-disabled={disabled || undefined}
59
+ data-toggle-labels={showLabels || undefined}
60
+ class={className || undefined}
61
+ role="radiogroup"
62
+ aria-label={label}
63
+ aria-disabled={disabled || undefined}
64
+ >
65
+ {#each wrapper.flatView as node (node.key)}
66
+ {@const proxy = node.proxy}
67
+ {@const sel = proxy.value === value}
68
+ {@const content = resolveSnippet(snippets, proxy)}
69
+ <button
70
+ type="button"
71
+ data-toggle-option
72
+ data-path={node.key}
73
+ data-selected={sel || undefined}
74
+ data-disabled={proxy.disabled || undefined}
75
+ role="radio"
76
+ aria-checked={sel}
77
+ aria-label={proxy.label}
78
+ title={proxy.get('description') || proxy.label}
79
+ disabled={proxy.disabled || disabled}
80
+ >
81
+ {#if content}
82
+ {@render content(proxy, sel)}
83
+ {:else}
84
+ {#if proxy.get('icon')}
85
+ <span data-toggle-icon class={proxy.get('icon')} aria-hidden="true"></span>
86
+ {/if}
87
+ {#if showLabels && proxy.label}
88
+ <span data-toggle-label>{proxy.label}</span>
89
+ {/if}
90
+ {/if}
91
+ </button>
92
+ {/each}
93
+ </div>