@rokkit/ui 1.0.0-next.145 → 1.0.0-next.146
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/package.json +1 -1
- package/src/components/AlertList.svelte +60 -0
- package/src/components/Card.svelte +6 -4
- package/src/components/Dropdown.svelte +202 -0
- package/src/components/FloatingNavigation.svelte +4 -1
- package/src/components/List.svelte +9 -0
- package/src/components/Message.svelte +77 -0
- package/src/components/Range.svelte +32 -8
- package/src/components/Select.svelte +4 -5
- package/src/components/StatusList.svelte +18 -0
- package/src/components/Toolbar.svelte +2 -0
- package/src/components/index.ts +4 -0
- package/src/index.ts +5 -1
- package/src/types/toolbar.ts +3 -0
- package/src/utils/palette.ts +24 -3
package/package.json
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
3
|
+
import { alerts } from '@rokkit/states'
|
|
4
|
+
import Message from './Message.svelte'
|
|
5
|
+
|
|
6
|
+
interface AlertListProps {
|
|
7
|
+
/** Screen position for the toast stack */
|
|
8
|
+
position?:
|
|
9
|
+
| 'top-right'
|
|
10
|
+
| 'top-center'
|
|
11
|
+
| 'top-left'
|
|
12
|
+
| 'bottom-right'
|
|
13
|
+
| 'bottom-center'
|
|
14
|
+
| 'bottom-left'
|
|
15
|
+
/** Additional CSS class */
|
|
16
|
+
class?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { position = 'top-right', class: className = '' }: AlertListProps = $props()
|
|
20
|
+
|
|
21
|
+
let el: HTMLElement | undefined = $state()
|
|
22
|
+
const dismissing = new SvelteSet<string>()
|
|
23
|
+
|
|
24
|
+
// Portal to document.body so position:fixed is relative to the viewport,
|
|
25
|
+
// not clipped by any overflow:auto ancestor (e.g. the docs main column).
|
|
26
|
+
$effect(() => {
|
|
27
|
+
if (!el) return
|
|
28
|
+
|
|
29
|
+
document.body.appendChild(el)
|
|
30
|
+
return () => el?.remove()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
function startDismiss(id: string) {
|
|
34
|
+
dismissing.add(id)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onTransitionEnd(id: string, e: TransitionEvent) {
|
|
38
|
+
if (e.propertyName === 'max-height' && dismissing.has(id)) {
|
|
39
|
+
dismissing.delete(id)
|
|
40
|
+
alerts.dismiss(id)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div bind:this={el} data-alert-list data-position={position} class={className || undefined}>
|
|
46
|
+
{#each alerts.current as alert (alert.id)}
|
|
47
|
+
<div
|
|
48
|
+
data-dismissing={dismissing.has(alert.id) || undefined}
|
|
49
|
+
ontransitionend={(e) => onTransitionEnd(alert.id, e)}
|
|
50
|
+
>
|
|
51
|
+
<Message
|
|
52
|
+
type={alert.type as 'error' | 'info' | 'success' | 'warning'}
|
|
53
|
+
text={alert.text}
|
|
54
|
+
dismissible={alert.dismissible}
|
|
55
|
+
actions={alert.actions as any}
|
|
56
|
+
ondismiss={() => startDismiss(alert.id)}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
{/each}
|
|
60
|
+
</div>
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
href?: string
|
|
7
7
|
/** Click handler (only applies when no href) */
|
|
8
8
|
onclick?: () => void
|
|
9
|
+
/** Visual variant */
|
|
10
|
+
variant?: 'default' | 'primary' | 'secondary' | 'tertiary'
|
|
9
11
|
/** Additional CSS class */
|
|
10
12
|
class?: string
|
|
11
13
|
/** Card header snippet */
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
children?: Snippet
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const { href, onclick, class: className = '', header, footer, children }: CardProps = $props()
|
|
21
|
+
const { href, onclick, variant = 'default', class: className = '', header, footer, children }: CardProps = $props()
|
|
20
22
|
</script>
|
|
21
23
|
|
|
22
24
|
{#snippet cardContent()}
|
|
@@ -40,15 +42,15 @@
|
|
|
40
42
|
{/snippet}
|
|
41
43
|
|
|
42
44
|
{#if href}
|
|
43
|
-
<a {href} data-card class={className || undefined}>
|
|
45
|
+
<a {href} data-card data-variant={variant} class={className || undefined}>
|
|
44
46
|
{@render cardContent()}
|
|
45
47
|
</a>
|
|
46
48
|
{:else if onclick}
|
|
47
|
-
<button type="button" data-card data-card-interactive class={className || undefined} {onclick}>
|
|
49
|
+
<button type="button" data-card data-card-interactive data-variant={variant} class={className || undefined} {onclick}>
|
|
48
50
|
{@render cardContent()}
|
|
49
51
|
</button>
|
|
50
52
|
{:else}
|
|
51
|
-
<div data-card class={className || undefined}>
|
|
53
|
+
<div data-card data-variant={variant} class={className || undefined}>
|
|
52
54
|
{@render cardContent()}
|
|
53
55
|
</div>
|
|
54
56
|
{/if}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Dropdown — Trigger that shows the selected value + dropdown panel with options.
|
|
4
|
+
*
|
|
5
|
+
* Similar to Menu but the trigger label reflects the currently selected value
|
|
6
|
+
* instead of a static label prop.
|
|
7
|
+
*
|
|
8
|
+
* Data attributes:
|
|
9
|
+
* data-dropdown — root container
|
|
10
|
+
* data-dropdown-trigger — trigger button
|
|
11
|
+
* data-dropdown-icon — optional icon in trigger
|
|
12
|
+
* data-dropdown-label — selected value text in trigger
|
|
13
|
+
* data-dropdown-arrow — expand/collapse chevron
|
|
14
|
+
* data-dropdown-panel — dropdown options panel
|
|
15
|
+
* data-dropdown-option — each option item
|
|
16
|
+
* data-dropdown-separator — separator between options
|
|
17
|
+
* data-active — marks the currently selected option
|
|
18
|
+
* data-open — present on root when panel is open
|
|
19
|
+
* data-size — size variant (sm | md | lg)
|
|
20
|
+
* data-align — panel alignment (start | end)
|
|
21
|
+
* data-direction — panel direction (down | up)
|
|
22
|
+
*/
|
|
23
|
+
// @ts-nocheck
|
|
24
|
+
import { ProxyTree, Wrapper } from '@rokkit/states'
|
|
25
|
+
import { Navigator, Trigger } from '@rokkit/actions'
|
|
26
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
27
|
+
|
|
28
|
+
interface DropdownIcons {
|
|
29
|
+
opened?: string
|
|
30
|
+
closed?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
items = [],
|
|
35
|
+
fields = {},
|
|
36
|
+
value = $bindable(),
|
|
37
|
+
placeholder = 'Select...',
|
|
38
|
+
icon,
|
|
39
|
+
size = 'md',
|
|
40
|
+
disabled = false,
|
|
41
|
+
showArrow = true,
|
|
42
|
+
align = 'start',
|
|
43
|
+
direction = 'down',
|
|
44
|
+
icons: userIcons = {} as DropdownIcons,
|
|
45
|
+
onchange,
|
|
46
|
+
class: className = ''
|
|
47
|
+
}: {
|
|
48
|
+
items?: unknown[]
|
|
49
|
+
fields?: Record<string, string>
|
|
50
|
+
value?: unknown
|
|
51
|
+
placeholder?: string
|
|
52
|
+
icon?: string
|
|
53
|
+
size?: string
|
|
54
|
+
disabled?: boolean
|
|
55
|
+
showArrow?: boolean
|
|
56
|
+
align?: 'start' | 'end'
|
|
57
|
+
direction?: 'up' | 'down'
|
|
58
|
+
icons?: DropdownIcons
|
|
59
|
+
onchange?: (value: unknown, item: unknown) => void
|
|
60
|
+
class?: string
|
|
61
|
+
} = $props()
|
|
62
|
+
|
|
63
|
+
const icons = $derived({ ...DEFAULT_STATE_ICONS.selector, ...userIcons })
|
|
64
|
+
|
|
65
|
+
// ─── State ────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
let isOpen = $state(false)
|
|
68
|
+
let rootRef = $state<HTMLElement | null>(null)
|
|
69
|
+
let triggerRef = $state<HTMLElement | null>(null)
|
|
70
|
+
let panelRef = $state<HTMLElement | null>(null)
|
|
71
|
+
|
|
72
|
+
// ─── Wrapper ──────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function handleSelect(v: unknown, proxy: unknown) {
|
|
75
|
+
if ((proxy as { disabled?: boolean }).disabled) return
|
|
76
|
+
value = v
|
|
77
|
+
onchange?.(v, (proxy as { original: unknown }).original)
|
|
78
|
+
isOpen = false
|
|
79
|
+
triggerRef?.focus()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const proxyTree = $derived(new ProxyTree(items, fields))
|
|
83
|
+
const wrapper = $derived(new Wrapper(proxyTree, { onselect: handleSelect }))
|
|
84
|
+
|
|
85
|
+
$effect(() => {
|
|
86
|
+
const w = wrapper
|
|
87
|
+
w.cancel = () => {
|
|
88
|
+
isOpen = false
|
|
89
|
+
triggerRef?.focus()
|
|
90
|
+
}
|
|
91
|
+
w.blur = () => {
|
|
92
|
+
isOpen = false
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
$effect(() => {
|
|
97
|
+
const _w = wrapper
|
|
98
|
+
if (isOpen) _w.first(null)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// ─── Trigger action ───────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
$effect(() => {
|
|
104
|
+
if (!triggerRef || !rootRef || disabled) return
|
|
105
|
+
const t = new Trigger(triggerRef, rootRef, {
|
|
106
|
+
isOpen: () => isOpen,
|
|
107
|
+
onopen: () => {
|
|
108
|
+
isOpen = true
|
|
109
|
+
requestAnimationFrame(() => wrapper.first(null))
|
|
110
|
+
},
|
|
111
|
+
onclose: () => {
|
|
112
|
+
isOpen = false
|
|
113
|
+
},
|
|
114
|
+
onlast: () => requestAnimationFrame(() => wrapper.last(null))
|
|
115
|
+
})
|
|
116
|
+
return () => t.destroy()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// ─── Navigator on panel ───────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
$effect(() => {
|
|
122
|
+
if (!isOpen || !panelRef) return
|
|
123
|
+
const nav = new Navigator(panelRef, wrapper, {})
|
|
124
|
+
return () => nav.destroy()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Focus sync — move DOM focus to focusedKey in panel
|
|
128
|
+
$effect(() => {
|
|
129
|
+
const key = wrapper.focusedKey
|
|
130
|
+
if (!isOpen || !panelRef || !key) return
|
|
131
|
+
requestAnimationFrame(() => {
|
|
132
|
+
const target = panelRef?.querySelector(`[data-path="${key}"]`) as HTMLElement | null
|
|
133
|
+
if (target && target !== document.activeElement) target.focus()
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// ─── Selected label ───────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
const selectedLabel = $derived.by(() => {
|
|
140
|
+
if (value === undefined || value === null) return null
|
|
141
|
+
for (const node of wrapper.flatView) {
|
|
142
|
+
if (node.proxy.value === value) return node.proxy.label
|
|
143
|
+
}
|
|
144
|
+
return String(value)
|
|
145
|
+
})
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<div
|
|
149
|
+
bind:this={rootRef}
|
|
150
|
+
data-dropdown
|
|
151
|
+
data-open={isOpen || undefined}
|
|
152
|
+
data-size={size}
|
|
153
|
+
data-disabled={disabled || undefined}
|
|
154
|
+
data-align={align}
|
|
155
|
+
data-direction={direction}
|
|
156
|
+
class={className || undefined}
|
|
157
|
+
>
|
|
158
|
+
<button
|
|
159
|
+
bind:this={triggerRef}
|
|
160
|
+
type="button"
|
|
161
|
+
data-dropdown-trigger
|
|
162
|
+
{disabled}
|
|
163
|
+
aria-haspopup="listbox"
|
|
164
|
+
aria-expanded={isOpen}
|
|
165
|
+
>
|
|
166
|
+
{#if icon}
|
|
167
|
+
<span data-dropdown-icon class={icon} aria-hidden="true"></span>
|
|
168
|
+
{/if}
|
|
169
|
+
<span data-dropdown-label>{selectedLabel ?? placeholder}</span>
|
|
170
|
+
{#if showArrow}
|
|
171
|
+
<span data-dropdown-arrow class={isOpen ? icons.opened : icons.closed} aria-hidden="true"
|
|
172
|
+
></span>
|
|
173
|
+
{/if}
|
|
174
|
+
</button>
|
|
175
|
+
|
|
176
|
+
{#if isOpen}
|
|
177
|
+
<div bind:this={panelRef} data-dropdown-panel role="listbox">
|
|
178
|
+
{#each wrapper.flatView as node (node.key)}
|
|
179
|
+
{@const proxy = node.proxy}
|
|
180
|
+
{@const isActive = proxy.value === value}
|
|
181
|
+
|
|
182
|
+
{#if node.type === 'separator'}
|
|
183
|
+
<hr data-dropdown-separator />
|
|
184
|
+
{:else}
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
data-dropdown-option
|
|
188
|
+
data-path={node.key}
|
|
189
|
+
data-active={isActive || undefined}
|
|
190
|
+
data-disabled={proxy.disabled || undefined}
|
|
191
|
+
disabled={proxy.disabled || disabled}
|
|
192
|
+
role="option"
|
|
193
|
+
aria-selected={isActive}
|
|
194
|
+
tabindex="-1"
|
|
195
|
+
>
|
|
196
|
+
<span data-dropdown-option-label>{proxy.label}</span>
|
|
197
|
+
</button>
|
|
198
|
+
{/if}
|
|
199
|
+
{/each}
|
|
200
|
+
</div>
|
|
201
|
+
{/if}
|
|
202
|
+
</div>
|
|
@@ -141,7 +141,10 @@
|
|
|
141
141
|
focusDomItem(index)
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
function matchesEntryId(
|
|
144
|
+
function matchesEntryId(
|
|
145
|
+
item: { proxy: ProxyItem; original: Record<string, unknown> },
|
|
146
|
+
id: string
|
|
147
|
+
): boolean {
|
|
145
148
|
return resolveTargetId(item) === id
|
|
146
149
|
}
|
|
147
150
|
|
|
@@ -82,6 +82,15 @@
|
|
|
82
82
|
return () => nav.destroy()
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
+
// Expand all groups on mount and when items change.
|
|
86
|
+
// collapsible=false: groups are fixed section headers — must always show children.
|
|
87
|
+
// collapsible=true: groups start expanded; user can collapse individual groups.
|
|
88
|
+
$effect(() => {
|
|
89
|
+
for (const [, proxy] of wrapper.lookup) {
|
|
90
|
+
if (proxy.hasChildren) proxy.expanded = true
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
85
94
|
// ─── Sync external value → focused key ────────────────────────────────────
|
|
86
95
|
|
|
87
96
|
$effect(() => {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
4
|
+
|
|
5
|
+
interface MessageProps {
|
|
6
|
+
/** Alert type — controls color and icon */
|
|
7
|
+
type?: 'error' | 'info' | 'success' | 'warning'
|
|
8
|
+
/** Icon class overrides per type */
|
|
9
|
+
icons?: { error?: string; info?: string; success?: string; warning?: string }
|
|
10
|
+
/** Text content (shorthand; children takes precedence) */
|
|
11
|
+
text?: string
|
|
12
|
+
/** Show dismiss button */
|
|
13
|
+
dismissible?: boolean
|
|
14
|
+
/** Auto-dismiss after N ms. Defaults to 4000 unless dismissible (persistent). Pass 0 to disable. */
|
|
15
|
+
timeout?: number
|
|
16
|
+
/** Optional action buttons snippet */
|
|
17
|
+
actions?: Snippet
|
|
18
|
+
/** Rich content (takes precedence over text) */
|
|
19
|
+
children?: Snippet
|
|
20
|
+
/** Called when dismissed (button click or timeout) */
|
|
21
|
+
ondismiss?: () => void
|
|
22
|
+
/** Additional CSS class */
|
|
23
|
+
class?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
type = 'error',
|
|
28
|
+
icons = DEFAULT_STATE_ICONS.state as Record<string, string>,
|
|
29
|
+
text = undefined,
|
|
30
|
+
dismissible = false,
|
|
31
|
+
timeout = dismissible ? 0 : 4000,
|
|
32
|
+
actions,
|
|
33
|
+
children,
|
|
34
|
+
ondismiss,
|
|
35
|
+
class: className = ''
|
|
36
|
+
}: MessageProps = $props()
|
|
37
|
+
|
|
38
|
+
const role = $derived(type === 'error' || type === 'warning' ? 'alert' : 'status')
|
|
39
|
+
const icon = $derived(icons[type] ?? '')
|
|
40
|
+
|
|
41
|
+
$effect(() => {
|
|
42
|
+
if (timeout > 0) {
|
|
43
|
+
const timer = setTimeout(() => ondismiss?.(), timeout)
|
|
44
|
+
return () => clearTimeout(timer)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<div
|
|
50
|
+
data-message-root
|
|
51
|
+
data-type={type}
|
|
52
|
+
data-dismissible={dismissible}
|
|
53
|
+
{role}
|
|
54
|
+
class={className || undefined}
|
|
55
|
+
>
|
|
56
|
+
<span data-message-icon class={icon} aria-hidden="true"></span>
|
|
57
|
+
|
|
58
|
+
<span data-message-text>
|
|
59
|
+
{#if children}
|
|
60
|
+
{@render children()}
|
|
61
|
+
{:else}
|
|
62
|
+
{text}
|
|
63
|
+
{/if}
|
|
64
|
+
</span>
|
|
65
|
+
|
|
66
|
+
{#if actions}
|
|
67
|
+
<div data-message-actions>
|
|
68
|
+
{@render actions()}
|
|
69
|
+
</div>
|
|
70
|
+
{/if}
|
|
71
|
+
|
|
72
|
+
{#if dismissible}
|
|
73
|
+
<button type="button" data-message-dismiss aria-label="Dismiss" onclick={ondismiss}
|
|
74
|
+
>×</button
|
|
75
|
+
>
|
|
76
|
+
{/if}
|
|
77
|
+
</div>
|
|
@@ -127,10 +127,22 @@
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function applyUpperKey(key: string): boolean {
|
|
130
|
-
if (isIncreaseKey(key)) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
if (isIncreaseKey(key)) {
|
|
131
|
+
nudgeUpper(1)
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
if (isDecreaseKey(key)) {
|
|
135
|
+
nudgeUpper(-1)
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
if (key === 'Home') {
|
|
139
|
+
jumpUpper(false)
|
|
140
|
+
return true
|
|
141
|
+
}
|
|
142
|
+
if (key === 'End') {
|
|
143
|
+
jumpUpper(true)
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
134
146
|
return false
|
|
135
147
|
}
|
|
136
148
|
|
|
@@ -173,10 +185,22 @@
|
|
|
173
185
|
}
|
|
174
186
|
|
|
175
187
|
function applyLowerKey(key: string): boolean {
|
|
176
|
-
if (isIncreaseKey(key)) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
if (isIncreaseKey(key)) {
|
|
189
|
+
nudgeLower(1)
|
|
190
|
+
return true
|
|
191
|
+
}
|
|
192
|
+
if (isDecreaseKey(key)) {
|
|
193
|
+
nudgeLower(-1)
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
if (key === 'Home') {
|
|
197
|
+
lower = min
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
if (key === 'End') {
|
|
201
|
+
lower = upper
|
|
202
|
+
return true
|
|
203
|
+
}
|
|
180
204
|
return false
|
|
181
205
|
}
|
|
182
206
|
|
|
@@ -107,13 +107,12 @@
|
|
|
107
107
|
const childrenField = $derived(fields?.children || 'children')
|
|
108
108
|
|
|
109
109
|
function childMatchesQuery(child: unknown, query: string): boolean {
|
|
110
|
-
return String((child as Record<string, unknown>)[textField] ?? '')
|
|
110
|
+
return String((child as Record<string, unknown>)[textField] ?? '')
|
|
111
|
+
.toLowerCase()
|
|
112
|
+
.includes(query)
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
function filterGroupChildren(
|
|
114
|
-
asRecord: Record<string, unknown>,
|
|
115
|
-
query: string
|
|
116
|
-
): unknown | null {
|
|
115
|
+
function filterGroupChildren(asRecord: Record<string, unknown>, query: string): unknown | null {
|
|
117
116
|
const children = asRecord[childrenField] as unknown[]
|
|
118
117
|
const matching = children.filter((child: unknown) => childMatchesQuery(child, query))
|
|
119
118
|
return matching.length > 0 ? { ...asRecord, [childrenField]: matching } : null
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_ICONS = DEFAULT_STATE_ICONS.badge
|
|
5
|
+
|
|
6
|
+
let { class: className = '', items, icons = {} } = $props()
|
|
7
|
+
|
|
8
|
+
const resolvedIcons = $derived({ ...DEFAULT_ICONS, ...icons })
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div data-status-list class={className} role="status">
|
|
12
|
+
{#each items as { text, status }, index (index)}
|
|
13
|
+
<div data-status-item data-status={status}>
|
|
14
|
+
<span class={resolvedIcons[status]} aria-hidden="true"></span>
|
|
15
|
+
<p>{text}</p>
|
|
16
|
+
</div>
|
|
17
|
+
{/each}
|
|
18
|
+
</div>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
fields: userFields,
|
|
18
18
|
position = 'top',
|
|
19
19
|
size = 'md',
|
|
20
|
+
width = 'full',
|
|
20
21
|
sticky = false,
|
|
21
22
|
compact = false,
|
|
22
23
|
showDividers = true,
|
|
@@ -252,6 +253,7 @@
|
|
|
252
253
|
data-toolbar
|
|
253
254
|
data-toolbar-position={position}
|
|
254
255
|
data-toolbar-size={size}
|
|
256
|
+
data-toolbar-width={width === 'fit' ? 'fit' : undefined}
|
|
255
257
|
data-toolbar-sticky={sticky || undefined}
|
|
256
258
|
data-toolbar-compact={compact || undefined}
|
|
257
259
|
data-toolbar-disabled={disabled || undefined}
|
package/src/components/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { default as Button } from './Button.svelte'
|
|
|
3
3
|
export { default as ButtonGroup } from './ButtonGroup.svelte'
|
|
4
4
|
export { default as Code } from './Code.svelte'
|
|
5
5
|
export { default as Menu } from './Menu.svelte'
|
|
6
|
+
export { default as Dropdown } from './Dropdown.svelte'
|
|
6
7
|
export { default as Select } from './Select.svelte'
|
|
7
8
|
export { default as MultiSelect } from './MultiSelect.svelte'
|
|
8
9
|
export { default as Toolbar } from './Toolbar.svelte'
|
|
@@ -36,3 +37,6 @@ export { default as UploadTarget } from './UploadTarget.svelte'
|
|
|
36
37
|
export { default as UploadFileStatus } from './UploadFileStatus.svelte'
|
|
37
38
|
export { default as UploadProgress } from './UploadProgress.svelte'
|
|
38
39
|
export { default as FloatingNavigation } from './FloatingNavigation.svelte'
|
|
40
|
+
export { default as StatusList } from './StatusList.svelte'
|
|
41
|
+
export { default as Message } from './Message.svelte'
|
|
42
|
+
export { default as AlertList } from './AlertList.svelte'
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
ButtonGroup,
|
|
5
5
|
Code,
|
|
6
6
|
Menu,
|
|
7
|
+
Dropdown,
|
|
7
8
|
Select,
|
|
8
9
|
MultiSelect,
|
|
9
10
|
Toolbar,
|
|
@@ -35,7 +36,10 @@ export {
|
|
|
35
36
|
Grid,
|
|
36
37
|
UploadTarget,
|
|
37
38
|
UploadFileStatus,
|
|
38
|
-
UploadProgress
|
|
39
|
+
UploadProgress,
|
|
40
|
+
StatusList,
|
|
41
|
+
Message,
|
|
42
|
+
AlertList
|
|
39
43
|
} from './components/index.js'
|
|
40
44
|
|
|
41
45
|
// Utilities
|
package/src/types/toolbar.ts
CHANGED
|
@@ -112,6 +112,9 @@ export interface ToolbarProps {
|
|
|
112
112
|
/** Size variant */
|
|
113
113
|
size?: 'sm' | 'md' | 'lg'
|
|
114
114
|
|
|
115
|
+
/** Width behaviour: full stretches to container, fit sizes to content */
|
|
116
|
+
width?: 'full' | 'fit'
|
|
117
|
+
|
|
115
118
|
/** Whether toolbar should stick to its position */
|
|
116
119
|
sticky?: boolean
|
|
117
120
|
|
package/src/utils/palette.ts
CHANGED
|
@@ -83,7 +83,11 @@ export function rgbToHex(rgb: RGB): string {
|
|
|
83
83
|
/**
|
|
84
84
|
* Convert RGB to HSL
|
|
85
85
|
*/
|
|
86
|
-
interface NormalizedRGB {
|
|
86
|
+
interface NormalizedRGB {
|
|
87
|
+
r: number
|
|
88
|
+
g: number
|
|
89
|
+
b: number
|
|
90
|
+
}
|
|
87
91
|
|
|
88
92
|
function computeHue(norm: NormalizedRGB, max: number, d: number): number {
|
|
89
93
|
const { r, g, b } = norm
|
|
@@ -545,11 +549,28 @@ export function applyPalette(
|
|
|
545
549
|
}
|
|
546
550
|
|
|
547
551
|
const DEFAULT_PALETTE_ROLES: ColorRole[] = [
|
|
548
|
-
'primary',
|
|
552
|
+
'primary',
|
|
553
|
+
'secondary',
|
|
554
|
+
'accent',
|
|
555
|
+
'surface',
|
|
556
|
+
'success',
|
|
557
|
+
'warning',
|
|
558
|
+
'danger',
|
|
559
|
+
'info'
|
|
549
560
|
]
|
|
550
561
|
|
|
551
562
|
const ALL_SHADE_KEYS: ShadeKey[] = [
|
|
552
|
-
'50',
|
|
563
|
+
'50',
|
|
564
|
+
'100',
|
|
565
|
+
'200',
|
|
566
|
+
'300',
|
|
567
|
+
'400',
|
|
568
|
+
'500',
|
|
569
|
+
'600',
|
|
570
|
+
'700',
|
|
571
|
+
'800',
|
|
572
|
+
'900',
|
|
573
|
+
'950'
|
|
553
574
|
]
|
|
554
575
|
|
|
555
576
|
/**
|