@urbicon-ui/blocks 6.21.1 → 6.21.3
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/dist/components/NumberInput/NumberInput.svelte +197 -0
- package/dist/components/NumberInput/NumberInput.svelte.d.ts +4 -0
- package/dist/components/NumberInput/index.d.ts +55 -0
- package/dist/components/NumberInput/index.js +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +1 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/primitives/Avatar/avatar.variants.d.ts +6 -6
- package/dist/primitives/Badge/Badge.svelte +18 -8
- package/dist/primitives/Badge/index.d.ts +26 -6
- package/dist/primitives/Breadcrumb/Breadcrumb.svelte +11 -6
- package/dist/primitives/Breadcrumb/index.d.ts +2 -2
- package/dist/primitives/Button/button.variants.d.ts +4 -4
- package/dist/primitives/ButtonGroup/index.d.ts +1 -1
- package/dist/primitives/Card/card.variants.d.ts +5 -5
- package/dist/primitives/Checkbox/Checkbox.svelte +3 -3
- package/dist/primitives/Combobox/Combobox.svelte +478 -104
- package/dist/primitives/Combobox/combobox.variants.d.ts +89 -17
- package/dist/primitives/Combobox/combobox.variants.js +94 -13
- package/dist/primitives/Combobox/index.d.ts +166 -51
- package/dist/primitives/ConfirmDialog/index.d.ts +19 -0
- package/dist/primitives/Dialog/Dialog.svelte +74 -0
- package/dist/primitives/Dialog/dialog.variants.d.ts +9 -9
- package/dist/primitives/Dialog/index.d.ts +12 -1
- package/dist/primitives/Drawer/Drawer.svelte +2 -1
- package/dist/primitives/Drawer/drawer.variants.d.ts +8 -0
- package/dist/primitives/Drawer/drawer.variants.js +45 -10
- package/dist/primitives/Drawer/index.d.ts +13 -4
- package/dist/primitives/FormField/FormField.svelte +6 -6
- package/dist/primitives/FormField/index.d.ts +13 -4
- package/dist/primitives/Input/Input.svelte +8 -4
- package/dist/primitives/Input/input.variants.d.ts +9 -9
- package/dist/primitives/Menu/Menu.svelte +66 -4
- package/dist/primitives/Menu/index.d.ts +25 -0
- package/dist/primitives/Pagination/Pagination.svelte +28 -13
- package/dist/primitives/Pagination/index.d.ts +44 -0
- package/dist/primitives/Popover/Popover.svelte +34 -3
- package/dist/primitives/Popover/index.d.ts +3 -3
- package/dist/primitives/RadioGroup/RadioGroup.svelte +3 -3
- package/dist/primitives/Select/Select.svelte +21 -11
- package/dist/primitives/Select/index.d.ts +7 -0
- package/dist/primitives/Select/select.variants.d.ts +17 -17
- package/dist/primitives/Select/select.variants.js +18 -0
- package/dist/primitives/Skeleton/skeleton.variants.d.ts +3 -3
- package/dist/primitives/Slider/Slider.svelte +4 -8
- package/dist/primitives/Spinner/spinner.variants.d.ts +16 -16
- package/dist/primitives/Textarea/Textarea.svelte +13 -5
- package/dist/primitives/Textarea/textarea.variants.d.ts +7 -7
- package/dist/primitives/Textarea/textarea.variants.js +5 -2
- package/dist/primitives/Toast/Toaster.svelte +42 -2
- package/dist/primitives/Toast/index.d.ts +30 -0
- package/dist/primitives/Toast/toast.store.svelte.d.ts +25 -1
- package/dist/primitives/Toast/toast.store.svelte.js +91 -1
- package/dist/primitives/Toast/toast.variants.d.ts +18 -0
- package/dist/primitives/Toast/toast.variants.js +15 -0
- package/dist/primitives/Toggle/Toggle.svelte +17 -3
- package/dist/primitives/Toggle/index.d.ts +8 -2
- package/dist/primitives/Toggle/toggle.variants.d.ts +7 -0
- package/dist/primitives/Toggle/toggle.variants.js +26 -0
- package/dist/primitives/Toolbar/toolbar.variants.d.ts +6 -6
- package/dist/primitives/Tooltip/Tooltip.svelte +47 -21
- package/dist/primitives/Tooltip/Tooltip.svelte.d.ts +1 -1
- package/dist/primitives/Tooltip/index.d.ts +31 -3
- package/dist/primitives/Tooltip/tooltip.variants.d.ts +4 -4
- package/dist/primitives/Tooltip/tooltip.variants.js +2 -2
- package/dist/primitives/index.d.ts +3 -3
- package/dist/style/semantic.css +55 -29
- package/dist/translations/de.d.ts +3 -0
- package/dist/translations/de.js +4 -1
- package/dist/translations/en.d.ts +3 -0
- package/dist/translations/en.js +4 -1
- package/dist/utils/figma-token-export.d.ts +13 -0
- package/dist/utils/figma-token-export.js +99 -26
- package/dist/utils/guide.svelte.d.ts +8 -2
- package/dist/utils/guide.svelte.js +86 -5
- package/dist/utils/use-form-field.svelte.d.ts +9 -9
- package/dist/utils/use-form-field.svelte.js +12 -12
- package/package.json +3 -3
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Input } from '../../primitives/Input';
|
|
3
|
+
import { resolveIcon } from '../../icons';
|
|
4
|
+
import ChevronDownIconDefault from '../../icons/ChevronDownIcon.svelte';
|
|
5
|
+
import type { NumberInputProps } from './index';
|
|
6
|
+
|
|
7
|
+
const ChevronIcon = resolveIcon('chevronDown', ChevronDownIconDefault);
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
value = $bindable(null),
|
|
11
|
+
min,
|
|
12
|
+
max,
|
|
13
|
+
step = 1,
|
|
14
|
+
precision,
|
|
15
|
+
hideStepper = false,
|
|
16
|
+
disabled = false,
|
|
17
|
+
readonly = false,
|
|
18
|
+
name,
|
|
19
|
+
onValueChange,
|
|
20
|
+
rightIcon: userRightIcon,
|
|
21
|
+
...inputProps
|
|
22
|
+
}: NumberInputProps = $props();
|
|
23
|
+
|
|
24
|
+
// Tracks focus so the wheel handler only steers a focused field (scrolling the
|
|
25
|
+
// page over an unfocused one must not change it). Set by the input's focus/blur.
|
|
26
|
+
let focused = $state(false);
|
|
27
|
+
|
|
28
|
+
// Display mirrors `value` (derived) but is overridden with the raw text while
|
|
29
|
+
// typing, so intermediate states — a trailing separator, a lone leading `-` —
|
|
30
|
+
// survive until blur. Same override pattern as CurrencyInput.
|
|
31
|
+
let display = $derived(value == null ? '' : formatNumber(value));
|
|
32
|
+
|
|
33
|
+
function decimalsOf(n: number): number {
|
|
34
|
+
if (!Number.isFinite(n)) return 0;
|
|
35
|
+
const s = String(n);
|
|
36
|
+
// Exponential notation (e.g. `1e-7` → "1e-7") carries no literal `.`; derive
|
|
37
|
+
// the decimal count from the exponent plus any mantissa fraction so a tiny
|
|
38
|
+
// step isn't silently rounded down to 0 decimals (which would swallow it).
|
|
39
|
+
const e = s.indexOf('e');
|
|
40
|
+
if (e !== -1) {
|
|
41
|
+
const mantissa = s.slice(0, e);
|
|
42
|
+
const exp = Number(s.slice(e + 1));
|
|
43
|
+
const dot = mantissa.indexOf('.');
|
|
44
|
+
const mantissaDecimals = dot === -1 ? 0 : mantissa.length - dot - 1;
|
|
45
|
+
return Math.max(0, mantissaDecimals - exp);
|
|
46
|
+
}
|
|
47
|
+
const dot = s.indexOf('.');
|
|
48
|
+
return dot === -1 ? 0 : s.length - dot - 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function roundTo(v: number, decimals: number): number {
|
|
52
|
+
const factor = 10 ** decimals;
|
|
53
|
+
return Math.round(v * factor) / factor;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatNumber(v: number): string {
|
|
57
|
+
return precision != null ? v.toFixed(precision) : String(v);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseNumber(raw: string): number | null {
|
|
61
|
+
// Accept a comma as the decimal separator so `1,5` works under DE keyboards.
|
|
62
|
+
const t = raw.trim().replace(',', '.');
|
|
63
|
+
if (t === '') return null;
|
|
64
|
+
const n = Number(t);
|
|
65
|
+
// NaN (e.g. a lone `-` mid-compose, or junk) leaves the value untouched so
|
|
66
|
+
// the raw text can stay in the field until it parses or the user blurs.
|
|
67
|
+
return Number.isFinite(n) ? n : (value ?? null);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function clampValue(v: number | null): number | null {
|
|
71
|
+
if (v == null) return null;
|
|
72
|
+
let r = v;
|
|
73
|
+
if (min != null && r < min) r = min;
|
|
74
|
+
if (max != null && r > max) r = max;
|
|
75
|
+
if (precision != null) r = roundTo(r, precision);
|
|
76
|
+
return r;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function commit(next: number | null) {
|
|
80
|
+
if (next !== value) {
|
|
81
|
+
value = next;
|
|
82
|
+
onValueChange?.(next);
|
|
83
|
+
}
|
|
84
|
+
display = next == null ? '' : formatNumber(next);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function stepBy(dir: 1 | -1) {
|
|
88
|
+
if (disabled || readonly) return;
|
|
89
|
+
// Round on the step's own scale so 0.1 + 0.2 lands on 0.3, not 0.30000000004.
|
|
90
|
+
const decimals = precision ?? decimalsOf(step);
|
|
91
|
+
// From an empty field, the first step lands *on* the near bound (min for ↑,
|
|
92
|
+
// max for ↓) when one is set, instead of overshooting it by a full step.
|
|
93
|
+
if (value == null) {
|
|
94
|
+
const bound = dir === 1 ? min : max;
|
|
95
|
+
commit(clampValue(roundTo(bound ?? dir * step, decimals)));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
commit(clampValue(roundTo(value + dir * step, decimals)));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handleInput(event: Event) {
|
|
102
|
+
const raw = (event.currentTarget as HTMLInputElement).value;
|
|
103
|
+
const parsed = parseNumber(raw);
|
|
104
|
+
if (parsed !== value) {
|
|
105
|
+
value = parsed;
|
|
106
|
+
onValueChange?.(parsed);
|
|
107
|
+
}
|
|
108
|
+
display = raw; // keep the raw text; clamp/format happens on blur
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function handleFocus() {
|
|
112
|
+
focused = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function handleBlur() {
|
|
116
|
+
focused = false;
|
|
117
|
+
commit(clampValue(value));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
121
|
+
// Bail before preventDefault so a disabled/readonly field keeps native caret
|
|
122
|
+
// motion (ArrowUp/Down jump to start/end) instead of swallowing the keys.
|
|
123
|
+
if (disabled || readonly) return;
|
|
124
|
+
if (event.key === 'ArrowUp') {
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
stepBy(1);
|
|
127
|
+
} else if (event.key === 'ArrowDown') {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
stepBy(-1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function handleWheel(event: WheelEvent) {
|
|
134
|
+
// Only steer the value when the field is focused, so scrolling the page with
|
|
135
|
+
// the pointer over an unfocused field doesn't silently change it. Bail on
|
|
136
|
+
// disabled/readonly before preventDefault so page scroll isn't blocked either.
|
|
137
|
+
if (disabled || readonly || !focused) return;
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
stepBy(event.deltaY < 0 ? 1 : -1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const atMin = $derived(min != null && value != null && value <= min);
|
|
143
|
+
const atMax = $derived(max != null && value != null && value >= max);
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
{#snippet stepper()}
|
|
147
|
+
<!-- Re-enable pointer events: Input renders a right-side snippet inside a
|
|
148
|
+
`pointer-events-none` decoration container (input.variants.ts), so without
|
|
149
|
+
this the stepper buttons would be dead to mouse clicks in a real browser. -->
|
|
150
|
+
<span class="pointer-events-auto -my-1 flex flex-col justify-center">
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
class="text-text-tertiary hover:text-text-primary flex items-center justify-center px-0.5 transition-colors disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-none"
|
|
154
|
+
tabindex={-1}
|
|
155
|
+
aria-hidden="true"
|
|
156
|
+
disabled={disabled || readonly || atMax}
|
|
157
|
+
onmousedown={(e) => e.preventDefault()}
|
|
158
|
+
onclick={() => stepBy(1)}
|
|
159
|
+
>
|
|
160
|
+
<ChevronIcon class="h-3.5 w-3.5 rotate-180" />
|
|
161
|
+
</button>
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
class="text-text-tertiary hover:text-text-primary flex items-center justify-center px-0.5 transition-colors disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-none"
|
|
165
|
+
tabindex={-1}
|
|
166
|
+
aria-hidden="true"
|
|
167
|
+
disabled={disabled || readonly || atMin}
|
|
168
|
+
onmousedown={(e) => e.preventDefault()}
|
|
169
|
+
onclick={() => stepBy(-1)}
|
|
170
|
+
>
|
|
171
|
+
<ChevronIcon class="h-3.5 w-3.5" />
|
|
172
|
+
</button>
|
|
173
|
+
</span>
|
|
174
|
+
{/snippet}
|
|
175
|
+
|
|
176
|
+
<Input
|
|
177
|
+
{...inputProps}
|
|
178
|
+
{disabled}
|
|
179
|
+
{readonly}
|
|
180
|
+
type="text"
|
|
181
|
+
inputmode="decimal"
|
|
182
|
+
role="spinbutton"
|
|
183
|
+
aria-valuenow={value ?? undefined}
|
|
184
|
+
aria-valuemin={min}
|
|
185
|
+
aria-valuemax={max}
|
|
186
|
+
value={display}
|
|
187
|
+
oninput={handleInput}
|
|
188
|
+
onfocus={handleFocus}
|
|
189
|
+
onblur={handleBlur}
|
|
190
|
+
onkeydown={handleKeydown}
|
|
191
|
+
onwheel={handleWheel}
|
|
192
|
+
rightIcon={userRightIcon ?? (hideStepper ? undefined : stepper)}
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
{#if name}
|
|
196
|
+
<input type="hidden" {name} value={value ?? ''} />
|
|
197
|
+
{/if}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { InputProps } from '../../primitives/Input/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* @description Generic numeric input with min/max/step bounds, stepper buttons,
|
|
5
|
+
* and Arrow-key / mouse-wheel increment. Built on {@link Input}, so it inherits
|
|
6
|
+
* labels, validation, sizes and variants. Values are plain numbers (not minor
|
|
7
|
+
* units) — {@link CurrencyInput} is the domain-specific specialization for money.
|
|
8
|
+
*
|
|
9
|
+
* The field accepts a leading `-`, a single decimal separator (`.` or `,`), and
|
|
10
|
+
* clamps to `[min, max]` on blur; the stepper and Arrow keys clamp immediately.
|
|
11
|
+
* Set {@link precision} to fix the number of decimal places.
|
|
12
|
+
*
|
|
13
|
+
* @tag form
|
|
14
|
+
* @related Input
|
|
15
|
+
* @related CurrencyInput
|
|
16
|
+
* @related Slider
|
|
17
|
+
* @stability beta
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```svelte
|
|
21
|
+
* <script>
|
|
22
|
+
* let qty = $state(1);
|
|
23
|
+
* </script>
|
|
24
|
+
* <NumberInput label="Quantity" bind:value={qty} min={0} max={99} step={1} />
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example Decimal step with fixed precision
|
|
28
|
+
* ```svelte
|
|
29
|
+
* <NumberInput label="Rate" bind:value={rate} min={0} max={1} step={0.05} precision={2} />
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export interface NumberInputProps extends Omit<InputProps, 'value' | 'type' | 'inputmode' | 'oninput' | 'onfocus' | 'onblur' | 'onkeydown' | 'onwheel' | 'clearable' | 'onClear' | 'children'> {
|
|
33
|
+
/** Current numeric value. `null` when the field is empty. Supports `bind:value`. */
|
|
34
|
+
value?: number | null;
|
|
35
|
+
/** Minimum allowed value. Clamped on step / Arrow / blur. */
|
|
36
|
+
min?: number;
|
|
37
|
+
/** Maximum allowed value. Clamped on step / Arrow / blur. */
|
|
38
|
+
max?: number;
|
|
39
|
+
/** Increment applied by the stepper buttons, Arrow keys, and wheel. @default 1 */
|
|
40
|
+
step?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Fixed number of decimal places for display and rounding. When unset, the
|
|
43
|
+
* value is shown as typed and the step's own decimals drive rounding.
|
|
44
|
+
*/
|
|
45
|
+
precision?: number;
|
|
46
|
+
/** Hide the up/down stepper buttons (Arrow keys + wheel still work). @default false */
|
|
47
|
+
hideStepper?: boolean;
|
|
48
|
+
/** Shared `name` for a hidden input for native form submission. */
|
|
49
|
+
name?: string;
|
|
50
|
+
/** Fires after the value changes (typing, stepper, Arrow, wheel, or clamp). */
|
|
51
|
+
onValueChange?: (value: number | null) => void;
|
|
52
|
+
/** A custom right-side adornment. Overrides the stepper — pair with `hideStepper` or provide your own controls. */
|
|
53
|
+
rightIcon?: Snippet;
|
|
54
|
+
}
|
|
55
|
+
export { default as NumberInput } from './NumberInput.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as NumberInput } from './NumberInput.svelte';
|
|
@@ -18,6 +18,8 @@ export * from './Guide/index.js';
|
|
|
18
18
|
export * from './LineChart/index.js';
|
|
19
19
|
export type { LocaleSwitcherProps } from './LocaleSwitcher/index.js';
|
|
20
20
|
export * from './LocaleSwitcher/index.js';
|
|
21
|
+
export type { NumberInputProps } from './NumberInput/index.js';
|
|
22
|
+
export * from './NumberInput/index.js';
|
|
21
23
|
export type { PlannerCellContext, PlannerDayContext, PlannerHeaderContext, PlannerProps, PlannerRange, PlannerSlotName, PlannerView } from './Planner/index.js';
|
|
22
24
|
export * from './Planner/index.js';
|
|
23
25
|
export type { SankeyIntent, SankeyLaidOutLinkWithMeta, SankeyLaidOutNodeWithMeta, SankeyLink, SankeyNode, SankeyProps } from './Sankey/index.js';
|
package/dist/components/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from './FileUpload/index.js';
|
|
|
15
15
|
export * from './Guide/index.js';
|
|
16
16
|
export * from './LineChart/index.js';
|
|
17
17
|
export * from './LocaleSwitcher/index.js';
|
|
18
|
+
export * from './NumberInput/index.js';
|
|
18
19
|
export * from './Planner/index.js';
|
|
19
20
|
export * from './Sankey/index.js';
|
|
20
21
|
export * from './SidebarLayout/index.js';
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ declare const blocksTranslations: {
|
|
|
3
3
|
en: {
|
|
4
4
|
readonly accessibility: {
|
|
5
5
|
readonly avatar: "Avatar";
|
|
6
|
+
readonly breadcrumb: "Breadcrumb";
|
|
7
|
+
readonly breadcrumbExpand: "Show all breadcrumb items";
|
|
6
8
|
readonly clearInput: "Clear input";
|
|
7
9
|
readonly clearSearch: "Clear search";
|
|
8
10
|
readonly clearSelection: "Clear selection";
|
|
@@ -21,6 +23,7 @@ declare const blocksTranslations: {
|
|
|
21
23
|
readonly removableBadge: "Removable badge";
|
|
22
24
|
readonly removeBadge: "Remove badge";
|
|
23
25
|
readonly removeFile: "Remove {{name}}";
|
|
26
|
+
readonly removeTag: "Remove {{label}}";
|
|
24
27
|
};
|
|
25
28
|
readonly button: {
|
|
26
29
|
readonly close: "Close";
|
|
@@ -189,6 +192,8 @@ declare const blocksTranslations: {
|
|
|
189
192
|
de: {
|
|
190
193
|
readonly accessibility: {
|
|
191
194
|
readonly avatar: "Avatar";
|
|
195
|
+
readonly breadcrumb: "Pfadnavigation";
|
|
196
|
+
readonly breadcrumbExpand: "Alle Pfadebenen anzeigen";
|
|
192
197
|
readonly clearInput: "Eingabe löschen";
|
|
193
198
|
readonly clearSearch: "Suche löschen";
|
|
194
199
|
readonly clearSelection: "Auswahl löschen";
|
|
@@ -207,6 +212,7 @@ declare const blocksTranslations: {
|
|
|
207
212
|
readonly removableBadge: "Entfernbarer Badge";
|
|
208
213
|
readonly removeBadge: "Badge entfernen";
|
|
209
214
|
readonly removeFile: "{{name}} entfernen";
|
|
215
|
+
readonly removeTag: "{{label}} entfernen";
|
|
210
216
|
};
|
|
211
217
|
readonly button: {
|
|
212
218
|
readonly close: "Schließen";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SlotNames, type VariantProps } from '../../utils/variants.js';
|
|
2
2
|
export declare const avatarVariants: ((props?: {
|
|
3
|
-
size?: "sm" | "md" | "lg" | "
|
|
3
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xl" | undefined;
|
|
4
4
|
variant?: "rounded" | "circle" | "square" | undefined;
|
|
5
5
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
6
6
|
status?: "offline" | "online" | "away" | "busy" | undefined;
|
|
@@ -11,7 +11,7 @@ export declare const avatarVariants: ((props?: {
|
|
|
11
11
|
interactive?: boolean | undefined;
|
|
12
12
|
} | undefined) => {
|
|
13
13
|
base: (props?: ({
|
|
14
|
-
size?: "sm" | "md" | "lg" | "
|
|
14
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xl" | undefined;
|
|
15
15
|
variant?: "rounded" | "circle" | "square" | undefined;
|
|
16
16
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
17
17
|
status?: "offline" | "online" | "away" | "busy" | undefined;
|
|
@@ -24,7 +24,7 @@ export declare const avatarVariants: ((props?: {
|
|
|
24
24
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
25
25
|
}) | undefined) => string;
|
|
26
26
|
frame: (props?: ({
|
|
27
|
-
size?: "sm" | "md" | "lg" | "
|
|
27
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xl" | undefined;
|
|
28
28
|
variant?: "rounded" | "circle" | "square" | undefined;
|
|
29
29
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
30
30
|
status?: "offline" | "online" | "away" | "busy" | undefined;
|
|
@@ -37,7 +37,7 @@ export declare const avatarVariants: ((props?: {
|
|
|
37
37
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
38
38
|
}) | undefined) => string;
|
|
39
39
|
image: (props?: ({
|
|
40
|
-
size?: "sm" | "md" | "lg" | "
|
|
40
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xl" | undefined;
|
|
41
41
|
variant?: "rounded" | "circle" | "square" | undefined;
|
|
42
42
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
43
43
|
status?: "offline" | "online" | "away" | "busy" | undefined;
|
|
@@ -50,7 +50,7 @@ export declare const avatarVariants: ((props?: {
|
|
|
50
50
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
51
51
|
}) | undefined) => string;
|
|
52
52
|
fallback: (props?: ({
|
|
53
|
-
size?: "sm" | "md" | "lg" | "
|
|
53
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xl" | undefined;
|
|
54
54
|
variant?: "rounded" | "circle" | "square" | undefined;
|
|
55
55
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
56
56
|
status?: "offline" | "online" | "away" | "busy" | undefined;
|
|
@@ -63,7 +63,7 @@ export declare const avatarVariants: ((props?: {
|
|
|
63
63
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
64
64
|
}) | undefined) => string;
|
|
65
65
|
status: (props?: ({
|
|
66
|
-
size?: "sm" | "md" | "lg" | "
|
|
66
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xl" | undefined;
|
|
67
67
|
variant?: "rounded" | "circle" | "square" | undefined;
|
|
68
68
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
69
69
|
status?: "offline" | "online" | "away" | "busy" | undefined;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
let {
|
|
16
16
|
tier,
|
|
17
17
|
intent = 'primary',
|
|
18
|
+
purpose,
|
|
18
19
|
variant = 'filled',
|
|
19
20
|
size = 'md',
|
|
20
21
|
counter = false,
|
|
@@ -44,18 +45,23 @@
|
|
|
44
45
|
const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
|
|
45
46
|
|
|
46
47
|
let badgeElement = $state<HTMLElement>();
|
|
47
|
-
let isHovered = $state(false);
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// `purpose` is the canonical semantic axis; it orchestrates the low-level
|
|
50
|
+
// visual props so the tv() config stays as-is. When set it wins over the
|
|
51
|
+
// deprecated `variant="dot"` / `counter` boolean; when unset those apply
|
|
52
|
+
// directly (back-compat).
|
|
53
|
+
const effVariant = $derived(purpose === 'dot' ? 'dot' : variant);
|
|
54
|
+
const effCounter = $derived(purpose === 'counter' || counter);
|
|
55
|
+
const isDot = $derived(effVariant === 'dot');
|
|
56
|
+
const isInteractive = $derived(purpose === 'chip' || interactive || !!onclick);
|
|
51
57
|
const isRemovable = $derived(removable && !isDot);
|
|
52
58
|
|
|
53
59
|
const variantProps: BadgeVariants = $derived({
|
|
54
60
|
tier: effectiveTier,
|
|
55
61
|
intent,
|
|
56
|
-
variant,
|
|
62
|
+
variant: effVariant,
|
|
57
63
|
size,
|
|
58
|
-
counter:
|
|
64
|
+
counter: effCounter || undefined,
|
|
59
65
|
pulse: pulse || undefined,
|
|
60
66
|
removable: isRemovable || undefined,
|
|
61
67
|
interactive: isInteractive || undefined,
|
|
@@ -77,12 +83,10 @@
|
|
|
77
83
|
});
|
|
78
84
|
|
|
79
85
|
function handleMouseEnter() {
|
|
80
|
-
isHovered = true;
|
|
81
86
|
onHover?.(true);
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
function handleMouseLeave() {
|
|
85
|
-
isHovered = false;
|
|
86
90
|
onHover?.(false);
|
|
87
91
|
}
|
|
88
92
|
|
|
@@ -97,6 +101,10 @@
|
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
function handleKeydown(event: KeyboardEvent) {
|
|
104
|
+
// Mirror the pointer path: `disabled` sets pointer-events-none (mouse dead) and
|
|
105
|
+
// handleClick guards — without this guard the keyboard could still activate
|
|
106
|
+
// or remove a disabled badge.
|
|
107
|
+
if (disabled) return;
|
|
100
108
|
if (isRemovable && (event.key === 'Delete' || event.key === 'Backspace')) {
|
|
101
109
|
handleRemove(event);
|
|
102
110
|
}
|
|
@@ -117,7 +125,8 @@
|
|
|
117
125
|
: styles.base({ class: [slotClasses?.base, className] })
|
|
118
126
|
]}
|
|
119
127
|
{role}
|
|
120
|
-
|
|
128
|
+
data-purpose={purpose}
|
|
129
|
+
tabindex={isInteractive && !disabled ? 0 : undefined}
|
|
121
130
|
onmouseenter={handleMouseEnter}
|
|
122
131
|
onmouseleave={handleMouseLeave}
|
|
123
132
|
onclick={handleClick}
|
|
@@ -140,6 +149,7 @@
|
|
|
140
149
|
<Button
|
|
141
150
|
variant="ghost"
|
|
142
151
|
size="xs"
|
|
152
|
+
{disabled}
|
|
143
153
|
class={unstyled
|
|
144
154
|
? (slotClasses?.removeButton ?? '')
|
|
145
155
|
: styles.removeButton({ class: slotClasses?.removeButton })}
|
|
@@ -6,7 +6,21 @@ import type { BadgePlacement, BadgeSlots, BadgeVariants } from './badge.variants
|
|
|
6
6
|
* Shared fields that apply to every Badge variant — `dot` and the
|
|
7
7
|
* label-style variants both accept these.
|
|
8
8
|
*/
|
|
9
|
-
interface BadgeBaseProps extends Omit<BadgeVariants, 'variant'>, Omit<HTMLAttributes<HTMLElement>, 'children'> {
|
|
9
|
+
interface BadgeBaseProps extends Omit<BadgeVariants, 'variant' | 'counter'>, Omit<HTMLAttributes<HTMLElement>, 'children'> {
|
|
10
|
+
/**
|
|
11
|
+
* The badge's semantic purpose — the canonical axis that resolves what the
|
|
12
|
+
* badge *is*, since a bare Badge served five overlapping roles. Orchestrates
|
|
13
|
+
* the low-level visual props so you rarely set them directly:
|
|
14
|
+
* - `status` — a state marker (Active, Failed); pairs with `intent`.
|
|
15
|
+
* - `tag` — a neutral inline label (category, version).
|
|
16
|
+
* - `counter` — a compact numeric pill (replaces the `counter` boolean).
|
|
17
|
+
* - `dot` — a pure indicator, content hidden (replaces `variant="dot"`).
|
|
18
|
+
* - `chip` — a removable, interactive filter chip; pair with `removable`.
|
|
19
|
+
*
|
|
20
|
+
* Leave unset to drive the badge purely by the low-level props (back-compat).
|
|
21
|
+
* When set, `purpose` wins over `variant="dot"` / the `counter` boolean.
|
|
22
|
+
*/
|
|
23
|
+
purpose?: 'status' | 'tag' | 'counter' | 'dot' | 'chip';
|
|
10
24
|
/** Add a pulsing animation to draw attention (e.g. for live indicators). */
|
|
11
25
|
pulse?: boolean;
|
|
12
26
|
/** Visually disable the badge (reduced opacity, no pointer events). */
|
|
@@ -60,7 +74,10 @@ interface BadgeStandardProps extends BadgeBaseProps {
|
|
|
60
74
|
variant?: 'filled' | 'outlined' | 'soft';
|
|
61
75
|
/** Badge content (text, icons, numbers). */
|
|
62
76
|
children?: Snippet;
|
|
63
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Display as a compact pill for numeric counts (tightens padding, tabular-nums).
|
|
79
|
+
* @deprecated Prefer `purpose="counter"` — the canonical semantic axis. Kept for back-compat.
|
|
80
|
+
*/
|
|
64
81
|
counter?: boolean;
|
|
65
82
|
/** Show a remove (×) button. */
|
|
66
83
|
removable?: boolean;
|
|
@@ -81,16 +98,19 @@ interface BadgeStandardProps extends BadgeBaseProps {
|
|
|
81
98
|
* `onRemove` at the type level, while `filled` / `outlined` / `soft`
|
|
82
99
|
* accept the full surface.
|
|
83
100
|
*
|
|
84
|
-
* @example
|
|
101
|
+
* @example Purpose-driven (canonical) — the intent reads from `purpose`
|
|
85
102
|
* ```svelte
|
|
86
|
-
* <Badge
|
|
103
|
+
* <Badge purpose="status" intent="success">Active</Badge>
|
|
104
|
+
* <Badge purpose="counter" intent="danger">5</Badge>
|
|
105
|
+
* <Badge purpose="chip" removable onRemove={() => …}>React</Badge>
|
|
106
|
+
* <Badge purpose="dot" intent="warning" />
|
|
87
107
|
* ```
|
|
88
108
|
*
|
|
89
|
-
* @example
|
|
109
|
+
* @example Notification counter anchored to a trigger
|
|
90
110
|
* ```svelte
|
|
91
111
|
* <div class="relative">
|
|
92
112
|
* <Button>Notifications</Button>
|
|
93
|
-
* <Badge intent="danger"
|
|
113
|
+
* <Badge purpose="counter" intent="danger" placement="top-end" border>5</Badge>
|
|
94
114
|
* </div>
|
|
95
115
|
* ```
|
|
96
116
|
*/
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { tick } from 'svelte';
|
|
3
|
+
import { useBlocksI18n } from '../..';
|
|
3
4
|
import { getBlocksConfig, resolveSlotClasses } from '../../provider';
|
|
4
5
|
import { breadcrumbVariants, type BreadcrumbVariants } from './breadcrumb.variants';
|
|
5
6
|
import type { BreadcrumbProps, BreadcrumbItem } from './index';
|
|
6
7
|
|
|
8
|
+
const bt = useBlocksI18n();
|
|
9
|
+
|
|
7
10
|
let {
|
|
8
11
|
items,
|
|
9
12
|
size = 'md',
|
|
@@ -12,8 +15,11 @@
|
|
|
12
15
|
maxItems,
|
|
13
16
|
itemsBeforeCollapse = 1,
|
|
14
17
|
itemsAfterCollapse = 1,
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
// A11y labels default to the localized `accessibility.*` strings in the
|
|
19
|
+
// markup below (resolved per render, so a locale switch updates them) —
|
|
20
|
+
// Breadcrumb was the last primitive with built-in English-only strings.
|
|
21
|
+
expandLabel,
|
|
22
|
+
'aria-label': ariaLabel,
|
|
17
23
|
class: className = '',
|
|
18
24
|
unstyled: unstyledProp = false,
|
|
19
25
|
slotClasses: slotClassesProp = {},
|
|
@@ -43,8 +49,7 @@
|
|
|
43
49
|
const after = $derived(Math.max(1, itemsAfterCollapse));
|
|
44
50
|
|
|
45
51
|
type BreadcrumbEntry =
|
|
46
|
-
|
|
47
|
-
| { kind: 'ellipsis' };
|
|
52
|
+
{ kind: 'item'; item: BreadcrumbItem; current: boolean } | { kind: 'ellipsis' };
|
|
48
53
|
|
|
49
54
|
const entries = $derived.by((): BreadcrumbEntry[] => {
|
|
50
55
|
const last = items.length - 1;
|
|
@@ -90,7 +95,7 @@
|
|
|
90
95
|
class={unstyled
|
|
91
96
|
? [slotClasses?.nav, className].filter(Boolean).join(' ')
|
|
92
97
|
: styles.nav({ class: [slotClasses?.nav, className] })}
|
|
93
|
-
aria-label={ariaLabel}
|
|
98
|
+
aria-label={ariaLabel ?? bt('accessibility.breadcrumb')}
|
|
94
99
|
{...restProps}
|
|
95
100
|
>
|
|
96
101
|
<ol class={unstyled ? (slotClasses?.list ?? '') : styles.list({ class: slotClasses?.list })}>
|
|
@@ -102,7 +107,7 @@
|
|
|
102
107
|
class={unstyled
|
|
103
108
|
? (slotClasses?.ellipsis ?? '')
|
|
104
109
|
: styles.ellipsis({ class: slotClasses?.ellipsis })}
|
|
105
|
-
aria-label={expandLabel}
|
|
110
|
+
aria-label={expandLabel ?? bt('accessibility.breadcrumbExpand')}
|
|
106
111
|
onclick={expand}
|
|
107
112
|
>
|
|
108
113
|
…
|
|
@@ -72,9 +72,9 @@ export interface BreadcrumbProps extends BreadcrumbVariants, Omit<HTMLAttributes
|
|
|
72
72
|
itemsBeforeCollapse?: number;
|
|
73
73
|
/** Trailing items kept visible when collapsed; the current page is always included. @default 1 */
|
|
74
74
|
itemsAfterCollapse?: number;
|
|
75
|
-
/** Accessible label for the "…" button that expands a collapsed trail.
|
|
75
|
+
/** Accessible label for the "…" button that expands a collapsed trail. Defaults to the localized `accessibility.breadcrumbExpand`. */
|
|
76
76
|
expandLabel?: string;
|
|
77
|
-
/** Accessible label for the nav element
|
|
77
|
+
/** Accessible label for the nav element. Defaults to the localized `accessibility.breadcrumb`. */
|
|
78
78
|
'aria-label'?: string;
|
|
79
79
|
/** Custom CSS class */
|
|
80
80
|
class?: string;
|
|
@@ -4,7 +4,7 @@ export declare const buttonVariants: ((props?: {
|
|
|
4
4
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
5
5
|
pressed?: boolean | undefined;
|
|
6
6
|
variant?: "text" | "ghost" | "filled" | "outlined" | undefined;
|
|
7
|
-
size?: "sm" | "md" | "lg" | "
|
|
7
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xs" | undefined;
|
|
8
8
|
loading?: boolean | undefined;
|
|
9
9
|
loadingPlacement?: "start" | "end" | "overlay" | undefined;
|
|
10
10
|
active?: boolean | undefined;
|
|
@@ -15,7 +15,7 @@ export declare const buttonVariants: ((props?: {
|
|
|
15
15
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
16
16
|
pressed?: boolean | undefined;
|
|
17
17
|
variant?: "text" | "ghost" | "filled" | "outlined" | undefined;
|
|
18
|
-
size?: "sm" | "md" | "lg" | "
|
|
18
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xs" | undefined;
|
|
19
19
|
loading?: boolean | undefined;
|
|
20
20
|
loadingPlacement?: "start" | "end" | "overlay" | undefined;
|
|
21
21
|
active?: boolean | undefined;
|
|
@@ -28,7 +28,7 @@ export declare const buttonVariants: ((props?: {
|
|
|
28
28
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
29
29
|
pressed?: boolean | undefined;
|
|
30
30
|
variant?: "text" | "ghost" | "filled" | "outlined" | undefined;
|
|
31
|
-
size?: "sm" | "md" | "lg" | "
|
|
31
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xs" | undefined;
|
|
32
32
|
loading?: boolean | undefined;
|
|
33
33
|
loadingPlacement?: "start" | "end" | "overlay" | undefined;
|
|
34
34
|
active?: boolean | undefined;
|
|
@@ -41,7 +41,7 @@ export declare const buttonVariants: ((props?: {
|
|
|
41
41
|
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
42
42
|
pressed?: boolean | undefined;
|
|
43
43
|
variant?: "text" | "ghost" | "filled" | "outlined" | undefined;
|
|
44
|
-
size?: "sm" | "md" | "lg" | "
|
|
44
|
+
size?: "sm" | "md" | "lg" | "xs" | "xl" | "2xs" | undefined;
|
|
45
45
|
loading?: boolean | undefined;
|
|
46
46
|
loadingPlacement?: "start" | "end" | "overlay" | undefined;
|
|
47
47
|
active?: boolean | undefined;
|
|
@@ -45,7 +45,7 @@ export interface ButtonGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, '
|
|
|
45
45
|
orientation?: ButtonGroupOrientation;
|
|
46
46
|
/** Visually connect buttons (overlapping borders, shared rounding). When `false`, buttons are spaced with a small gap. */
|
|
47
47
|
connected?: boolean;
|
|
48
|
-
/** Size propagated to child Buttons (
|
|
48
|
+
/** Size propagated to child Buttons (the group value wins over a Button's own `size`). */
|
|
49
49
|
size?: ComponentSize;
|
|
50
50
|
/** Semantic colour propagated to child Buttons. */
|
|
51
51
|
intent?: ComponentIntent;
|