@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.
- package/README.md +159 -1
- package/package.json +52 -53
- package/src/MarkdownRenderer.svelte +106 -0
- package/src/components/AlertList.svelte +59 -0
- package/src/components/Avatar.svelte +68 -0
- package/src/components/Badge.svelte +57 -0
- package/src/components/BreadCrumbs.svelte +94 -0
- package/src/components/Button.svelte +91 -0
- package/src/components/ButtonGroup.svelte +9 -0
- package/src/components/Card.svelte +71 -0
- package/src/components/Carousel.svelte +169 -0
- package/src/components/Code.svelte +189 -0
- package/src/components/Connector.svelte +46 -0
- package/src/components/Divider.svelte +24 -0
- package/src/components/Dropdown.svelte +202 -0
- package/src/components/FloatingAction.svelte +367 -0
- package/src/components/FloatingNavigation.svelte +256 -0
- package/src/components/Grid.svelte +128 -0
- package/src/components/ItemContent.svelte +25 -0
- package/src/components/LazyTree.svelte +171 -0
- package/src/components/List.svelte +224 -0
- package/src/components/Menu.svelte +270 -0
- package/src/components/Message.svelte +75 -0
- package/src/components/MultiSelect.svelte +367 -0
- package/src/components/PaletteManager.svelte +360 -0
- package/src/components/Pill.svelte +83 -0
- package/src/components/ProgressBar.svelte +31 -0
- package/src/components/Range.svelte +346 -0
- package/src/components/Rating.svelte +115 -0
- package/src/components/Reveal.svelte +46 -0
- package/src/components/SearchFilter.svelte +88 -0
- package/src/components/Select.svelte +424 -0
- package/src/components/Shine.svelte +87 -0
- package/src/components/Stack.svelte +38 -0
- package/src/components/StatusList.svelte +18 -0
- package/src/components/Stepper.svelte +174 -0
- package/src/components/Swatch.svelte +102 -0
- package/src/components/Switch.svelte +71 -0
- package/src/components/Table.svelte +245 -0
- package/src/components/Tabs.svelte +202 -0
- package/src/components/Tilt.svelte +68 -0
- package/src/components/Timeline.svelte +61 -0
- package/src/components/Toggle.svelte +93 -0
- package/src/components/Toolbar.svelte +306 -0
- package/src/components/ToolbarGroup.svelte +17 -0
- package/src/components/Tooltip.svelte +121 -0
- package/src/components/Tree.svelte +143 -0
- package/src/components/UploadFileStatus.svelte +102 -0
- package/src/components/UploadProgress.svelte +127 -0
- package/src/components/UploadTarget.svelte +126 -0
- package/src/components/index.ts +48 -0
- package/src/index.ts +59 -0
- package/src/markdown-plugin.ts +12 -0
- package/src/types/button.ts +86 -0
- package/src/types/code.ts +46 -0
- package/src/types/floating-action.ts +123 -0
- package/src/types/floating-navigation.ts +80 -0
- package/src/types/index.ts +58 -0
- package/src/types/list.ts +200 -0
- package/src/types/menu.ts +95 -0
- package/src/types/palette.ts +160 -0
- package/src/types/range.ts +51 -0
- package/src/types/search-filter.ts +67 -0
- package/src/types/select.ts +176 -0
- package/src/types/switch.ts +68 -0
- package/src/types/table.ts +219 -0
- package/src/types/tabs.ts +103 -0
- package/src/types/timeline.ts +53 -0
- package/src/types/toggle.ts +68 -0
- package/src/types/toolbar.ts +167 -0
- package/src/types/tooltip.ts +21 -0
- package/src/types/tree.ts +250 -0
- package/src/types/upload-file-status.ts +45 -0
- package/src/types/upload-progress.ts +111 -0
- package/src/types/upload-target.ts +71 -0
- package/src/utils/palette.ts +618 -0
- package/src/utils/shiki.ts +114 -0
- package/src/utils/upload.js +145 -0
- package/LICENSE +0 -21
- package/src/index.js +0 -8
- 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>
|