@urbicon-ui/blocks 6.21.1 → 6.21.2

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 (66) hide show
  1. package/dist/components/NumberInput/NumberInput.svelte +197 -0
  2. package/dist/components/NumberInput/NumberInput.svelte.d.ts +4 -0
  3. package/dist/components/NumberInput/index.d.ts +55 -0
  4. package/dist/components/NumberInput/index.js +1 -0
  5. package/dist/components/index.d.ts +2 -0
  6. package/dist/components/index.js +1 -0
  7. package/dist/i18n/index.d.ts +6 -0
  8. package/dist/primitives/Badge/Badge.svelte +12 -4
  9. package/dist/primitives/Badge/index.d.ts +26 -6
  10. package/dist/primitives/Breadcrumb/Breadcrumb.svelte +11 -6
  11. package/dist/primitives/Breadcrumb/index.d.ts +2 -2
  12. package/dist/primitives/Checkbox/Checkbox.svelte +3 -3
  13. package/dist/primitives/Combobox/Combobox.svelte +478 -104
  14. package/dist/primitives/Combobox/combobox.variants.d.ts +89 -17
  15. package/dist/primitives/Combobox/combobox.variants.js +94 -13
  16. package/dist/primitives/Combobox/index.d.ts +166 -51
  17. package/dist/primitives/ConfirmDialog/index.d.ts +19 -0
  18. package/dist/primitives/Dialog/Dialog.svelte +74 -0
  19. package/dist/primitives/Dialog/index.d.ts +12 -1
  20. package/dist/primitives/Drawer/Drawer.svelte +2 -1
  21. package/dist/primitives/Drawer/drawer.variants.d.ts +8 -0
  22. package/dist/primitives/Drawer/drawer.variants.js +45 -10
  23. package/dist/primitives/Drawer/index.d.ts +13 -4
  24. package/dist/primitives/FormField/FormField.svelte +6 -6
  25. package/dist/primitives/FormField/index.d.ts +13 -4
  26. package/dist/primitives/Input/Input.svelte +8 -4
  27. package/dist/primitives/Menu/Menu.svelte +66 -4
  28. package/dist/primitives/Menu/index.d.ts +25 -0
  29. package/dist/primitives/Pagination/Pagination.svelte +28 -13
  30. package/dist/primitives/Pagination/index.d.ts +44 -0
  31. package/dist/primitives/Popover/Popover.svelte +10 -0
  32. package/dist/primitives/Popover/index.d.ts +3 -3
  33. package/dist/primitives/RadioGroup/RadioGroup.svelte +3 -3
  34. package/dist/primitives/Select/Select.svelte +21 -11
  35. package/dist/primitives/Select/index.d.ts +7 -0
  36. package/dist/primitives/Select/select.variants.d.ts +17 -17
  37. package/dist/primitives/Select/select.variants.js +18 -0
  38. package/dist/primitives/Slider/Slider.svelte +4 -8
  39. package/dist/primitives/Textarea/Textarea.svelte +4 -5
  40. package/dist/primitives/Textarea/textarea.variants.d.ts +7 -7
  41. package/dist/primitives/Textarea/textarea.variants.js +5 -2
  42. package/dist/primitives/Toast/Toaster.svelte +42 -2
  43. package/dist/primitives/Toast/index.d.ts +30 -0
  44. package/dist/primitives/Toast/toast.store.svelte.d.ts +25 -1
  45. package/dist/primitives/Toast/toast.store.svelte.js +91 -1
  46. package/dist/primitives/Toast/toast.variants.d.ts +18 -0
  47. package/dist/primitives/Toast/toast.variants.js +15 -0
  48. package/dist/primitives/Toggle/Toggle.svelte +17 -3
  49. package/dist/primitives/Toggle/index.d.ts +8 -2
  50. package/dist/primitives/Toggle/toggle.variants.d.ts +7 -0
  51. package/dist/primitives/Toggle/toggle.variants.js +26 -0
  52. package/dist/primitives/Tooltip/Tooltip.svelte +47 -21
  53. package/dist/primitives/Tooltip/Tooltip.svelte.d.ts +1 -1
  54. package/dist/primitives/Tooltip/index.d.ts +31 -3
  55. package/dist/primitives/Tooltip/tooltip.variants.d.ts +4 -4
  56. package/dist/primitives/Tooltip/tooltip.variants.js +2 -2
  57. package/dist/primitives/index.d.ts +3 -3
  58. package/dist/translations/de.d.ts +3 -0
  59. package/dist/translations/de.js +4 -1
  60. package/dist/translations/en.d.ts +3 -0
  61. package/dist/translations/en.js +4 -1
  62. package/dist/utils/guide.svelte.d.ts +8 -2
  63. package/dist/utils/guide.svelte.js +86 -5
  64. package/dist/utils/use-form-field.svelte.d.ts +9 -9
  65. package/dist/utils/use-form-field.svelte.js +12 -12
  66. package/package.json +3 -3
@@ -11,7 +11,8 @@
11
11
  import WarningTriangleIconDefault from '../../icons/WarningTriangleIcon.svelte';
12
12
  import DangerCircleIconDefault from '../../icons/DangerCircleIcon.svelte';
13
13
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
14
- import type { ToastProps } from './index';
14
+ import Spinner from '../Spinner/Spinner.svelte';
15
+ import type { ToastData, ToastProps } from './index';
15
16
 
16
17
  const bt = useBlocksI18n();
17
18
 
@@ -66,12 +67,25 @@
66
67
  content: () => '',
67
68
  title: () => '',
68
69
  description: () => '',
70
+ actions: () => '',
71
+ actionButton: () => '',
72
+ cancelButton: () => '',
69
73
  dismissButton: () => '',
70
74
  progress: () => ''
71
75
  }
72
76
  : toastVariants({ placement })
73
77
  );
74
78
 
79
+ // Run a toast action/cancel button: fire its handler, then dismiss unless the
80
+ // action opted out (`dismissOnClick: false`, e.g. a "Retry" that re-issues the
81
+ // async work and updates the same toast).
82
+ function runAction(toast: ToastData, which: 'action' | 'cancel') {
83
+ const action = toast[which];
84
+ if (!action) return;
85
+ action.onClick?.(toast.id);
86
+ if (action.dismissOnClick !== false) toaster.dismiss(toast.id);
87
+ }
88
+
75
89
  function slot(key: keyof typeof styles, intent?: string) {
76
90
  const overrides = slotClasses?.[key];
77
91
  if (unstyled) return overrides ?? '';
@@ -115,7 +129,11 @@
115
129
  {#each visibleToasts as toast (toast.id)}
116
130
  {@const IntentIcon = INTENT_ICON_MAP[toast.intent] ?? INTENT_ICON_MAP.neutral}
117
131
  <div class={slot('toast', toast.intent)} role="alert" transition:fly={flyParams()}>
118
- <IntentIcon class={slot('icon', toast.intent)} />
132
+ {#if toast.loading}
133
+ <span class={slot('icon', toast.intent)}><Spinner size="sm" /></span>
134
+ {:else}
135
+ <IntentIcon class={slot('icon', toast.intent)} />
136
+ {/if}
119
137
 
120
138
  <div class={slot('content')}>
121
139
  {#if toast.title}
@@ -124,6 +142,28 @@
124
142
  {#if toast.description}
125
143
  <div class={slot('description')}>{toast.description}</div>
126
144
  {/if}
145
+ {#if toast.action || toast.cancel}
146
+ <div class={slot('actions')}>
147
+ {#if toast.action}
148
+ <button
149
+ type="button"
150
+ class={slot('actionButton')}
151
+ onclick={() => runAction(toast, 'action')}
152
+ >
153
+ {toast.action.label}
154
+ </button>
155
+ {/if}
156
+ {#if toast.cancel}
157
+ <button
158
+ type="button"
159
+ class={slot('cancelButton')}
160
+ onclick={() => runAction(toast, 'cancel')}
161
+ >
162
+ {toast.cancel.label}
163
+ </button>
164
+ {/if}
165
+ </div>
166
+ {/if}
127
167
  </div>
128
168
 
129
169
  {#if toast.dismissible}
@@ -11,6 +11,17 @@ export declare const TOAST_INTENTS: readonly ["primary", "info", "success", "war
11
11
  * standard intent palette.
12
12
  */
13
13
  export type ToastIntent = (typeof TOAST_INTENTS)[number];
14
+ /**
15
+ * An action or cancel button rendered inside a toast (Sonner-style).
16
+ */
17
+ export interface ToastAction {
18
+ /** Button label. */
19
+ label: string;
20
+ /** Click handler. Receives the toast id so the handler can dismiss/update it. */
21
+ onClick?: (id: string) => void;
22
+ /** Dismiss the toast after the click. @default true */
23
+ dismissOnClick?: boolean;
24
+ }
14
25
  /**
15
26
  * Internal data shape for a rendered toast. Created by `toaster.add()`.
16
27
  */
@@ -29,6 +40,25 @@ export interface ToastData {
29
40
  dismissible: boolean;
30
41
  /** Show an animated progress bar that counts down the remaining `duration`. @default true */
31
42
  showProgress: boolean;
43
+ /** Primary action button (prominent). */
44
+ action?: ToastAction;
45
+ /** Secondary/cancel button (quiet). */
46
+ cancel?: ToastAction;
47
+ /** Render a spinner instead of the intent icon (used by `toaster.promise` while pending). @default false */
48
+ loading?: boolean;
49
+ }
50
+ /**
51
+ * Per-state config for `toaster.promise`. Each state is either a plain title
52
+ * string or a full {@link ToastInput}; `success`/`error` may also be a function
53
+ * of the resolved value / rejection reason.
54
+ */
55
+ export interface ToastPromiseOptions<T> {
56
+ /** Shown while the promise is pending (spinner, persistent, not dismissible). */
57
+ loading: string | ToastInput;
58
+ /** Shown when the promise resolves. */
59
+ success: string | ToastInput | ((value: T) => string | ToastInput);
60
+ /** Shown when the promise rejects. */
61
+ error: string | ToastInput | ((reason: unknown) => string | ToastInput);
32
62
  }
33
63
  /**
34
64
  * Options accepted by `toaster.add()` and the shorthand methods (`info`, `success`, `warning`, `danger`).
@@ -1,4 +1,4 @@
1
- import type { ToastData, ToastInput, ToastShorthandOpts } from './index.js';
1
+ import type { ToastData, ToastInput, ToastPromiseOptions, ToastShorthandOpts } from './index.js';
2
2
  import type { ToastPlacement } from './toast.variants.js';
3
3
  /**
4
4
  * Reactive store that manages the global toast queue.
@@ -24,6 +24,30 @@ declare class ToastStore {
24
24
  registerSubscriber(): () => void;
25
25
  /** Create a toast with full control over all options. Returns the toast ID for programmatic dismissal. */
26
26
  add(input: ToastInput): string;
27
+ /**
28
+ * Merge new fields into an existing toast in place — same id, same stack
29
+ * position — and reset its auto-dismiss timer to the new `duration`. No-op if
30
+ * the toast is gone. Backs `promise()`, which flips a pending toast to
31
+ * success/error without it sliding out and a new one flying in.
32
+ */
33
+ update(id: string, input: ToastInput): void;
34
+ /**
35
+ * Drive a toast through a promise's lifecycle (Sonner-style). Shows a
36
+ * persistent, non-dismissible spinner toast while pending, then flips it in
37
+ * place to success on resolve or danger on reject. `success`/`error` may be a
38
+ * plain title, a full {@link ToastInput}, or a function of the resolved value
39
+ * / rejection reason. Returns the toast id.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * toaster.promise(saveDraft(), {
44
+ * loading: 'Saving…',
45
+ * success: (draft) => `Saved “${draft.title}”`,
46
+ * error: (e) => `Could not save: ${e.message}`
47
+ * });
48
+ * ```
49
+ */
50
+ promise<T>(promise: Promise<T>, opts: ToastPromiseOptions<T>): string;
27
51
  /** Remove a single toast by ID. Clears its auto-dismiss timer. */
28
52
  dismiss(id: string): void;
29
53
  /** Remove all toasts and cancel their timers. */
@@ -50,7 +50,10 @@ class ToastStore {
50
50
  description: input.description,
51
51
  duration: input.duration ?? 5000,
52
52
  dismissible: input.dismissible ?? true,
53
- showProgress: input.showProgress ?? true
53
+ showProgress: input.showProgress ?? true,
54
+ action: input.action,
55
+ cancel: input.cancel,
56
+ loading: input.loading ?? false
54
57
  };
55
58
  this.toasts = [...this.toasts, toast];
56
59
  if (toast.duration > 0) {
@@ -58,6 +61,93 @@ class ToastStore {
58
61
  }
59
62
  return id;
60
63
  }
64
+ /**
65
+ * Merge new fields into an existing toast in place — same id, same stack
66
+ * position — and reset its auto-dismiss timer to the new `duration`. No-op if
67
+ * the toast is gone. Backs `promise()`, which flips a pending toast to
68
+ * success/error without it sliding out and a new one flying in.
69
+ */
70
+ update(id, input) {
71
+ const idx = this.toasts.findIndex((t) => t.id === id);
72
+ if (idx === -1)
73
+ return;
74
+ const next = { ...this.toasts[idx], ...input };
75
+ this.toasts = [...this.toasts.slice(0, idx), next, ...this.toasts.slice(idx + 1)];
76
+ const timer = this.timers.get(id);
77
+ if (timer) {
78
+ clearTimeout(timer);
79
+ this.timers.delete(id);
80
+ }
81
+ if (next.duration > 0) {
82
+ this.timers.set(id, setTimeout(() => this.dismiss(id), next.duration));
83
+ }
84
+ }
85
+ /**
86
+ * Drive a toast through a promise's lifecycle (Sonner-style). Shows a
87
+ * persistent, non-dismissible spinner toast while pending, then flips it in
88
+ * place to success on resolve or danger on reject. `success`/`error` may be a
89
+ * plain title, a full {@link ToastInput}, or a function of the resolved value
90
+ * / rejection reason. Returns the toast id.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * toaster.promise(saveDraft(), {
95
+ * loading: 'Saving…',
96
+ * success: (draft) => `Saved “${draft.title}”`,
97
+ * error: (e) => `Could not save: ${e.message}`
98
+ * });
99
+ * ```
100
+ */
101
+ promise(promise, opts) {
102
+ const loadingInput = typeof opts.loading === 'string' ? { title: opts.loading } : opts.loading;
103
+ const id = this.add({
104
+ intent: 'neutral',
105
+ ...loadingInput,
106
+ loading: true,
107
+ duration: 0,
108
+ dismissible: false,
109
+ showProgress: false
110
+ });
111
+ // Shared settle fields: leave the spinner/persistent/non-dismissible state
112
+ // and become a normal auto-dismissing toast. `description: undefined` clears
113
+ // the loading description unless the success/error config sets its own.
114
+ const settled = {
115
+ loading: false,
116
+ duration: 5000,
117
+ dismissible: true,
118
+ showProgress: true
119
+ };
120
+ // A throwing user formatter must not turn into an unhandled rejection on the
121
+ // internal `.then`; fall back to a bare settled toast (still flips out of the
122
+ // loading state) and dev-warn instead.
123
+ const resolveMessage = (fn, arg, label) => {
124
+ let out;
125
+ try {
126
+ out = typeof fn === 'function' ? fn(arg) : fn;
127
+ }
128
+ catch (err) {
129
+ if (import.meta.env?.DEV)
130
+ console.warn(`[Toast] promise ${label} formatter threw:`, err);
131
+ }
132
+ return typeof out === 'string' ? { title: out } : out;
133
+ };
134
+ promise.then((value) => {
135
+ this.update(id, {
136
+ intent: 'success',
137
+ description: undefined,
138
+ ...settled,
139
+ ...resolveMessage(opts.success, value, 'success')
140
+ });
141
+ }, (reason) => {
142
+ this.update(id, {
143
+ intent: 'danger',
144
+ description: undefined,
145
+ ...settled,
146
+ ...resolveMessage(opts.error, reason, 'error')
147
+ });
148
+ });
149
+ return id;
150
+ }
61
151
  /** Remove a single toast by ID. Clears its auto-dismiss timer. */
62
152
  dismiss(id) {
63
153
  const timer = this.timers.get(id);
@@ -39,6 +39,24 @@ export declare const toastVariants: ((props?: {
39
39
  } & {
40
40
  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;
41
41
  }) | undefined) => string;
42
+ actions: (props?: ({
43
+ intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
44
+ placement?: "bottom-right" | "top-right" | "bottom-left" | "top-left" | "top-center" | "bottom-center" | undefined;
45
+ } & {
46
+ 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;
47
+ }) | undefined) => string;
48
+ actionButton: (props?: ({
49
+ intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
50
+ placement?: "bottom-right" | "top-right" | "bottom-left" | "top-left" | "top-center" | "bottom-center" | undefined;
51
+ } & {
52
+ 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;
53
+ }) | undefined) => string;
54
+ cancelButton: (props?: ({
55
+ intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
56
+ placement?: "bottom-right" | "top-right" | "bottom-left" | "top-left" | "top-center" | "bottom-center" | undefined;
57
+ } & {
58
+ 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;
59
+ }) | undefined) => string;
42
60
  dismissButton: (props?: ({
43
61
  intent?: "primary" | "info" | "success" | "warning" | "danger" | "neutral" | undefined;
44
62
  placement?: "bottom-right" | "top-right" | "bottom-left" | "top-left" | "top-center" | "bottom-center" | undefined;
@@ -16,6 +16,21 @@ export const toastVariants = tv({
16
16
  content: 'flex-1 min-w-0',
17
17
  title: 'font-semibold text-sm leading-tight',
18
18
  description: 'text-sm mt-0.5 opacity-80',
19
+ // Action row (Sonner-style). Intent-neutral, prominent primary + quiet
20
+ // cancel — the toast's intent already reads through the icon/progress, so
21
+ // the buttons stay contrast-based rather than intent-coloured.
22
+ actions: 'flex items-center gap-2 mt-2.5',
23
+ // tier: modify — small interactive sub-elements on a contain surface.
24
+ actionButton: [
25
+ 'rounded-modify px-2.5 py-1 text-xs font-medium',
26
+ 'bg-text-primary/10 hover:bg-text-primary/20 text-text-primary',
27
+ 'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/40'
28
+ ],
29
+ cancelButton: [
30
+ 'rounded-modify px-2.5 py-1 text-xs font-medium',
31
+ 'text-text-secondary hover:text-text-primary',
32
+ 'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/40'
33
+ ],
19
34
  // tier: modify — small interactive sub-element on a contain surface.
20
35
  dismissButton: [
21
36
  'shrink-0 rounded-modify p-1',
@@ -11,6 +11,7 @@
11
11
  checked = $bindable(false),
12
12
  label,
13
13
  helper,
14
+ error,
14
15
  tier,
15
16
  size = 'md',
16
17
  intent = 'primary',
@@ -35,7 +36,8 @@
35
36
  const id = $derived(idProp ?? propsId);
36
37
  const ff = useFormField(() => ({
37
38
  fieldId: id,
38
- hint: helper,
39
+ helper,
40
+ error,
39
41
  required,
40
42
  disabled
41
43
  }));
@@ -61,6 +63,7 @@
61
63
  appearance,
62
64
  checked,
63
65
  disabled,
66
+ error: !!error,
64
67
  withBorder
65
68
  });
66
69
 
@@ -111,6 +114,7 @@
111
114
  aria-checked={checked}
112
115
  aria-label={label ? undefined : bt('accessibility.toggle') || 'Toggle'}
113
116
  aria-describedby={ff.describedBy}
117
+ aria-invalid={ff.invalid ? 'true' : undefined}
114
118
  onchange={handleChange}
115
119
  {...restProps}
116
120
  />
@@ -135,9 +139,19 @@
135
139
  {/if}
136
140
  </label>
137
141
 
138
- {#if ff.hintId}
142
+ {#if ff.errorId}
143
+ <div
144
+ id={ff.errorId}
145
+ class={unstyled
146
+ ? (slotClasses?.message ?? '')
147
+ : styles.message({ class: slotClasses?.message })}
148
+ role="alert"
149
+ >
150
+ {error}
151
+ </div>
152
+ {:else if ff.helperId}
139
153
  <div
140
- id={ff.hintId}
154
+ id={ff.helperId}
141
155
  class={unstyled
142
156
  ? (slotClasses?.message ?? '')
143
157
  : styles.message({ class: slotClasses?.message })}
@@ -24,13 +24,19 @@ import type { ToggleSlots, ToggleVariants } from './toggle.variants.js';
24
24
  * />
25
25
  * ```
26
26
  */
27
- export interface ToggleProps extends Omit<ToggleVariants, 'checked'>, Omit<HTMLInputAttributes, 'type' | 'size' | 'checked' | 'class' | 'children'> {
27
+ export interface ToggleProps extends Omit<ToggleVariants, 'checked' | 'error'>, Omit<HTMLInputAttributes, 'type' | 'size' | 'checked' | 'class' | 'children'> {
28
28
  /** Current on/off state. Supports two-way binding via `bind:checked`. */
29
29
  checked?: boolean;
30
30
  /** Text label displayed to the right of the toggle track. */
31
31
  label?: string;
32
- /** Hint text shown below the control. Useful for explaining side-effects of the toggle. */
32
+ /** Helper text shown below the control. Hidden when `error` is set. Useful for explaining side-effects of the toggle. */
33
33
  helper?: string;
34
+ /**
35
+ * Error message shown below the control. Replaces `helper`, flags the
36
+ * hidden input `aria-invalid`, and marks the unchecked track with the
37
+ * danger colour — e.g. for a consent toggle that must be switched on.
38
+ */
39
+ error?: string;
34
40
  /** Prevent interaction and dim the control. */
35
41
  disabled?: boolean;
36
42
  /** Mark the native input as required for form validation. */
@@ -6,6 +6,7 @@ export declare const toggleVariants: ((props?: {
6
6
  appearance?: "default" | "dot" | undefined;
7
7
  checked?: boolean | undefined;
8
8
  disabled?: boolean | undefined;
9
+ error?: boolean | undefined;
9
10
  withBorder?: boolean | undefined;
10
11
  } | undefined) => {
11
12
  wrapper: (props?: ({
@@ -15,6 +16,7 @@ export declare const toggleVariants: ((props?: {
15
16
  appearance?: "default" | "dot" | undefined;
16
17
  checked?: boolean | undefined;
17
18
  disabled?: boolean | undefined;
19
+ error?: boolean | undefined;
18
20
  withBorder?: boolean | undefined;
19
21
  } & {
20
22
  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;
@@ -26,6 +28,7 @@ export declare const toggleVariants: ((props?: {
26
28
  appearance?: "default" | "dot" | undefined;
27
29
  checked?: boolean | undefined;
28
30
  disabled?: boolean | undefined;
31
+ error?: boolean | undefined;
29
32
  withBorder?: boolean | undefined;
30
33
  } & {
31
34
  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;
@@ -37,6 +40,7 @@ export declare const toggleVariants: ((props?: {
37
40
  appearance?: "default" | "dot" | undefined;
38
41
  checked?: boolean | undefined;
39
42
  disabled?: boolean | undefined;
43
+ error?: boolean | undefined;
40
44
  withBorder?: boolean | undefined;
41
45
  } & {
42
46
  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;
@@ -48,6 +52,7 @@ export declare const toggleVariants: ((props?: {
48
52
  appearance?: "default" | "dot" | undefined;
49
53
  checked?: boolean | undefined;
50
54
  disabled?: boolean | undefined;
55
+ error?: boolean | undefined;
51
56
  withBorder?: boolean | undefined;
52
57
  } & {
53
58
  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;
@@ -59,6 +64,7 @@ export declare const toggleVariants: ((props?: {
59
64
  appearance?: "default" | "dot" | undefined;
60
65
  checked?: boolean | undefined;
61
66
  disabled?: boolean | undefined;
67
+ error?: boolean | undefined;
62
68
  withBorder?: boolean | undefined;
63
69
  } & {
64
70
  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;
@@ -70,6 +76,7 @@ export declare const toggleVariants: ((props?: {
70
76
  appearance?: "default" | "dot" | undefined;
71
77
  checked?: boolean | undefined;
72
78
  disabled?: boolean | undefined;
79
+ error?: boolean | undefined;
73
80
  withBorder?: boolean | undefined;
74
81
  } & {
75
82
  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;
@@ -86,6 +86,11 @@ export const toggleVariants = tv({
86
86
  control: 'opacity-50 cursor-not-allowed pointer-events-none'
87
87
  }
88
88
  },
89
+ error: {
90
+ true: {
91
+ message: 'text-danger'
92
+ }
93
+ },
89
94
  withBorder: {
90
95
  true: {},
91
96
  false: {}
@@ -153,6 +158,19 @@ export const toggleVariants = tv({
153
158
  intent: 'neutral',
154
159
  class: { track: 'bg-neutral border-neutral' }
155
160
  },
161
+ // ── Error state: danger boundary on the unchecked track (Switch-Pill) ──
162
+ // Mirrors Checkbox ("error overrides unchecked border"): the off state
163
+ // carries the danger mark, the on state keeps its intent colour — an
164
+ // error on a boolean control usually means "must be switched on".
165
+ // The dot-appearance twin lives at the END of the compound list so it
166
+ // folds over the dot-unchecked `border-border-default` (order is
167
+ // semantic — later compounds win conflicting buckets).
168
+ {
169
+ appearance: 'default',
170
+ error: true,
171
+ checked: false,
172
+ class: { track: 'border-danger peer-focus-visible:ring-danger/40' }
173
+ },
156
174
  // ── Thumb translation per size when checked (Switch-Pill only) ──
157
175
  { appearance: 'default', checked: true, size: 'xs', class: { thumb: 'translate-x-4' } },
158
176
  { appearance: 'default', checked: true, size: 'sm', class: { thumb: 'translate-x-5' } },
@@ -212,6 +230,13 @@ export const toggleVariants = tv({
212
230
  checked: true,
213
231
  intent: 'neutral',
214
232
  class: { track: 'border-neutral bg-neutral' }
233
+ },
234
+ // ── Error state, dot appearance (must fold over dot-unchecked border) ──
235
+ {
236
+ appearance: 'dot',
237
+ error: true,
238
+ checked: false,
239
+ class: { track: 'border-danger' }
215
240
  }
216
241
  ],
217
242
  defaultVariants: {
@@ -221,6 +246,7 @@ export const toggleVariants = tv({
221
246
  appearance: 'default',
222
247
  checked: false,
223
248
  disabled: false,
249
+ error: false,
224
250
  withBorder: false
225
251
  }
226
252
  });