@joanroucoux/cairn-ui 0.1.5 → 0.3.0

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/styles/tokens.css CHANGED
@@ -54,6 +54,80 @@
54
54
  --accent-foreground: light-dark(#111414, #edefee);
55
55
  --destructive: light-dark(#b8443c, #a33a33);
56
56
  --destructive-foreground: light-dark(#fafbfa, #fafbfa);
57
+
58
+ /* Type - Rubik, one family for logotype and interface (self-hosted, 400/500/600/700).
59
+ Names follow Tailwind 4 namespaces: registered in @theme they yield text-body, rounded-control,
60
+ ease-out... Reset Tailwind's defaults (--text-*: initial) so only this scale exists.
61
+ Weights: 400 text, 500 labels and buttons, 600 titles and the dominant figure, 700 logotype only. */
62
+ --font-sans: 'Rubik', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
63
+ --numeric: tabular-nums; /* amounts that align: tables, lists, columns. The lone big figure stays proportional. */
64
+ --text-caption: 0.75rem;
65
+ --text-caption--line-height: 1.0625rem; /* 12/17 axes, badges, price dates */
66
+ --text-label: 0.875rem;
67
+ --text-label--line-height: 1.25rem; /* 14/20 labels, secondary text, cells */
68
+ --text-body: 1rem;
69
+ --text-body--line-height: 1.5rem; /* 16/24 rows, body, inputs (no iOS zoom) */
70
+ --text-title: 1.25rem;
71
+ --text-title--line-height: 1.75rem; /* 20/28 card and section titles */
72
+ --text-heading: 1.75rem;
73
+ --text-heading--line-height: 2.125rem; /* 28/34 page title in the header */
74
+ --text-display: 2.5rem;
75
+ --text-display--line-height: 2.75rem; /* 40/44 the one dominant figure */
76
+ --tracking-display: -0.02em; /* heading, display */
77
+ --tracking-caption: 0.01em; /* caption */
78
+
79
+ /* Space - Tailwind's 4px step stays; these name the recurring gaps */
80
+ --gutter: 1rem; /* page side padding */
81
+ --inset-card: 1rem; /* card padding */
82
+ --stack-group: 1rem; /* between items of one group */
83
+ --stack-section: 1rem; /* between cards */
84
+ --row-min: 2.75rem; /* 44px touch target */
85
+ --header-height: 3.5rem; /* page header below the status bar / above the content */
86
+
87
+ /* Radius - one rule: controls 8, containers 12, pills round.
88
+ A control nested in a control subtracts its inset: calc(var(--radius-control) - 2px). */
89
+ --radius-control: 0.5rem; /* button, input, select, segmented, nav item, row hover */
90
+ --radius-container: 0.75rem; /* card, dialog, sheet top corners, tooltip */
91
+ --radius-pill: 9999px; /* avatar, badge, delta pill */
92
+
93
+ /* Motion - only transform and opacity animate. Never ease-in. Exits are faster than entries.
94
+ Nothing animates on page entry: the dashboard opens several times a day. */
95
+ --duration-press: 120ms; /* press feedback, on pointer down */
96
+ --duration-fast: 180ms; /* menus, segmented thumb */
97
+ --duration-base: 260ms; /* dialogs, sheets, list to detail, chart range change */
98
+ --duration-exit: 180ms; /* every exit */
99
+ --ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* what appears or moves */
100
+ --ease-sheet: cubic-bezier(0.32, 0.72, 0, 1); /* sheets rising from the bottom, and back */
101
+ --press-scale: 0.97;
102
+ --enter-scale: 0.95; /* dialogs and menus start at scale(0.95) and opacity 0, never scale(0) */
103
+ --pulse-duration: 1600ms; /* skeleton opacity pulse */
104
+
105
+ /* Browser theme color (meta theme-color, iOS status bar) follows the top of the page:
106
+ light #f7f8f8, dark #0a0b0b - the --background values. */
107
+ }
108
+
109
+ @media (min-width: 64rem) {
110
+ :root {
111
+ --gutter: 2rem;
112
+ --inset-card: 1.5rem;
113
+ --stack-section: 1.5rem;
114
+ --header-height: 4.5rem;
115
+ }
116
+ }
117
+
118
+ @media (pointer: fine) {
119
+ :root {
120
+ --row-min: 2.5rem; /* 40px with a mouse: denser tables */
121
+ }
122
+ }
123
+
124
+ @media (prefers-reduced-motion: reduce) {
125
+ :root {
126
+ --press-scale: 1;
127
+ --enter-scale: 1; /* slides and scales become short fades */
128
+ --duration-base: 150ms;
129
+ --pulse-duration: 0ms;
130
+ }
57
131
  }
58
132
 
59
133
  :root[data-theme='light'] {
@@ -1,5 +1,69 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { Signal, ElementRef } from '@angular/core';
2
+ import { InjectionToken, Signal, ElementRef } from '@angular/core';
3
+
4
+ /** App-wide "amounts are hidden" state. Defaults to never masked; the app overrides it. */
5
+ declare const UI_AMOUNT_MASKED: InjectionToken<Signal<boolean>>;
6
+ /** Available figure styles. `AmountNumeric` is derived from this tuple. */
7
+ declare const AMOUNT_NUMERICS: readonly ["tabular", "proportional"];
8
+ type AmountNumeric = (typeof AMOUNT_NUMERICS)[number];
9
+ /** Available masking behaviors. `AmountMasking` is derived from this tuple. */
10
+ declare const AMOUNT_MASKINGS: readonly ["dots", "hide"];
11
+ type AmountMasking = (typeof AMOUNT_MASKINGS)[number];
12
+ /**
13
+ * A number or an amount of money, formatted for the locale and masked when the app hides amounts.
14
+ *
15
+ * @example
16
+ * <ui-amount [value]="total" currency="EUR" numeric="proportional" />
17
+ * <ui-amount [value]="dayChange" currency="EUR" signed whenMasked="hide" />
18
+ */
19
+ declare class UiAmount {
20
+ #private;
21
+ readonly value: _angular_core.InputSignal<number | null | undefined>;
22
+ readonly currency: _angular_core.InputSignal<string | undefined>;
23
+ readonly locale: _angular_core.InputSignal<string>;
24
+ readonly signed: _angular_core.InputSignalWithTransform<boolean, unknown>;
25
+ readonly fractionDigits: _angular_core.InputSignal<number>;
26
+ readonly numeric: _angular_core.InputSignal<"tabular" | "proportional">;
27
+ readonly whenMasked: _angular_core.InputSignal<"dots" | "hide">;
28
+ protected readonly hidden: Signal<boolean>;
29
+ protected readonly text: Signal<string>;
30
+ protected readonly classes: Signal<string>;
31
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiAmount, never>;
32
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiAmount, "ui-amount", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "currency": { "alias": "currency"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "signed": { "alias": "signed"; "required": false; "isSignal": true; }; "fractionDigits": { "alias": "fractionDigits"; "required": false; "isSignal": true; }; "numeric": { "alias": "numeric"; "required": false; "isSignal": true; }; "whenMasked": { "alias": "whenMasked"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
33
+ }
34
+
35
+ type AmountFormatOptions = {
36
+ locale: string;
37
+ currency?: string;
38
+ signed?: boolean;
39
+ fractionDigits?: number;
40
+ };
41
+ /** Formats a number or an amount for the locale, with a missing value as an em dash and masking behind four bullets. */
42
+ declare function formatAmount(value: number | null | undefined, options: AmountFormatOptions, masked?: boolean): string;
43
+
44
+ /** Available async block states. `AsyncState` is derived from this tuple. */
45
+ declare const ASYNC_STATES: readonly ["loading", "error", "empty", "ready"];
46
+ type AsyncState = (typeof ASYNC_STATES)[number];
47
+ /**
48
+ * The three states of a block that depends on a service call, next to the ready content.
49
+ *
50
+ * @example
51
+ * <ui-async [state]="totalState()" errorTitle="Total could not be loaded" errorMessage="The server did not answer." retryLabel="Retry" (retry)="reload()">
52
+ * <span asyncLoading><ui-skeleton shape="figure" /></span>
53
+ * <span asyncEmpty>Add a holding to see your total.</span>
54
+ * <span>{{ total() }}</span>
55
+ * </ui-async>
56
+ */
57
+ declare class UiAsync {
58
+ readonly state: _angular_core.InputSignal<"loading" | "error" | "empty" | "ready">;
59
+ readonly errorTitle: _angular_core.InputSignal<string | undefined>;
60
+ readonly errorMessage: _angular_core.InputSignal<string | undefined>;
61
+ readonly retryLabel: _angular_core.InputSignal<string | undefined>;
62
+ readonly retry: _angular_core.OutputEmitterRef<void>;
63
+ protected readonly retryButtonClasses = "mt-2 relative inline-flex items-center justify-center gap-2 rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[transform,background-color,opacity] duration-(--duration-press) ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) min-h-(--row-min) px-4 text-label bg-(--card) text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow)";
64
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiAsync, never>;
65
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiAsync, "ui-async", never, { "state": { "alias": "state"; "required": true; "isSignal": true; }; "errorTitle": { "alias": "errorTitle"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "retryLabel": { "alias": "retryLabel"; "required": false; "isSignal": true; }; }, { "retry": "retry"; }, never, ["[asyncLoading]", "[asyncEmpty]", "*"], true, never>;
66
+ }
3
67
 
4
68
  /** Available avatar sizes. `AvatarSize` is derived from this tuple. */
5
69
  declare const AVATAR_SIZES: readonly ["sm", "md"];
@@ -13,52 +77,49 @@ type AvatarSize = (typeof AVATAR_SIZES)[number];
13
77
  declare class UiAvatar {
14
78
  readonly initials: _angular_core.InputSignal<string>;
15
79
  readonly label: _angular_core.InputSignal<string>;
16
- readonly size: _angular_core.InputSignal<"sm" | "md">;
80
+ readonly size: _angular_core.InputSignal<"md" | "sm">;
17
81
  protected readonly classes: _angular_core.Signal<string>;
18
82
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiAvatar, never>;
19
83
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiAvatar, "ui-avatar", never, { "initials": { "alias": "initials"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
20
84
  }
21
85
 
22
86
  /** Available badge variants. `BadgeVariant` is derived from this tuple. */
23
- declare const BADGE_VARIANTS: readonly ["primary", "secondary", "destructive", "outline"];
87
+ declare const BADGE_VARIANTS: readonly ["neutral", "outline"];
24
88
  type BadgeVariant = (typeof BADGE_VARIANTS)[number];
25
89
  /**
26
- * Small status descriptor for counts, states or labels.
90
+ * Small status descriptor for counts, states or labels, such as an instrument's asset class.
27
91
  *
28
92
  * @example
29
- * <ui-badge variant="secondary">Draft</ui-badge>
93
+ * <ui-badge>ETF</ui-badge>
30
94
  */
31
95
  declare class UiBadge {
32
- readonly variant: _angular_core.InputSignal<"primary" | "secondary" | "destructive" | "outline">;
96
+ readonly variant: _angular_core.InputSignal<"neutral" | "outline">;
33
97
  protected readonly classes: _angular_core.Signal<string>;
34
98
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiBadge, never>;
35
99
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiBadge, "ui-badge", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
36
100
  }
37
101
 
38
102
  /** Available button variants. `ButtonVariant` is derived from this tuple. */
39
- declare const BUTTON_VARIANTS: readonly ["primary", "secondary", "outline", "destructive", "ghost"];
103
+ declare const BUTTON_VARIANTS: readonly ["primary", "outline", "ghost", "destructive"];
40
104
  type ButtonVariant = (typeof BUTTON_VARIANTS)[number];
41
- /** Available button sizes. `md` is the 44px touch target Cairn asks for everywhere. */
42
- declare const BUTTON_SIZES: readonly ["sm", "md", "lg"];
105
+ /** Available button sizes. `md` is the touch target Cairn asks for everywhere. */
106
+ declare const BUTTON_SIZES: readonly ["md", "lg", "xl", "icon"];
43
107
  type ButtonSize = (typeof BUTTON_SIZES)[number];
44
108
  /**
45
- * Styled native button. Applied as an attribute so the host keeps every native
46
- * button behavior (type, disabled, form submission, accessibility) for free.
47
- *
48
- * Also applies to an anchor, for the one case where the action really is a navigation:
49
- * a download the browser has to perform itself. The element stays a link, and so does
50
- * its role - never use this to make an ordinary link look like a form control.
109
+ * Styled native button or anchor, applied as an attribute so the host keeps every native behavior.
51
110
  *
52
111
  * @example
53
- * <button ui-button variant="destructive" size="sm">Delete</button>
54
- * <a ui-button variant="outline" href="/api/holdings/export" download>Export</a>
112
+ * <button ui-button variant="destructive">Delete</button>
113
+ * <button ui-button size="icon" variant="ghost" aria-label="More actions">...</button>
55
114
  */
56
115
  declare class UiButton {
57
- readonly variant: _angular_core.InputSignal<"primary" | "secondary" | "destructive" | "outline" | "ghost">;
58
- readonly size: _angular_core.InputSignal<"sm" | "md" | "lg">;
116
+ readonly variant: _angular_core.InputSignal<"outline" | "primary" | "ghost" | "destructive">;
117
+ readonly size: _angular_core.InputSignal<"md" | "lg" | "xl" | "icon">;
118
+ readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
59
119
  protected readonly classes: _angular_core.Signal<string>;
120
+ constructor();
60
121
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiButton, never>;
61
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiButton, "button[ui-button], a[ui-button]", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
122
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiButton, "button[ui-button], a[ui-button]", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
62
123
  }
63
124
 
64
125
  /** Available card variants. `CardVariant` is derived from this tuple. */
@@ -75,7 +136,7 @@ type CardPadding = (typeof CARD_PADDINGS)[number];
75
136
  */
76
137
  declare class UiCard {
77
138
  readonly variant: _angular_core.InputSignal<"default" | "elevated">;
78
- readonly padding: _angular_core.InputSignal<"sm" | "md" | "none">;
139
+ readonly padding: _angular_core.InputSignal<"md" | "sm" | "none">;
79
140
  protected readonly classes: _angular_core.Signal<string>;
80
141
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiCard, never>;
81
142
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiCard, "ui-card", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "padding": { "alias": "padding"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
@@ -125,8 +186,8 @@ declare class UiDelta {
125
186
  declare const DIALOG_WIDTHS: readonly ["md", "lg"];
126
187
  type DialogWidth = (typeof DIALOG_WIDTHS)[number];
127
188
  /**
128
- * Modal dialog on the native <dialog>: focus trap, focus return, Escape and the top layer
129
- * come from the platform. Two projection slots - the body, and [dialogActions] for the footer.
189
+ * Modal dialog on the native <dialog>: a sheet rising from the bottom under `64rem`, centered
190
+ * above it. Two projection slots - the body, and [dialogActions] for the footer.
130
191
  *
131
192
  * @example
132
193
  * <ui-dialog heading="Delete this holding?" [open]="confirming()" (dismissed)="confirming.set(false)">
@@ -139,7 +200,7 @@ declare class UiDialog {
139
200
  readonly heading: _angular_core.InputSignal<string>;
140
201
  readonly description: _angular_core.InputSignal<string | undefined>;
141
202
  readonly width: _angular_core.InputSignal<"md" | "lg">;
142
- readonly open: _angular_core.InputSignal<boolean>;
203
+ readonly open: _angular_core.InputSignalWithTransform<boolean, unknown>;
143
204
  readonly dismissed: _angular_core.OutputEmitterRef<void>;
144
205
  protected readonly headingId: string;
145
206
  protected readonly descriptionId: string;
@@ -178,10 +239,11 @@ declare class UiField {
178
239
  readonly labelHidden: _angular_core.InputSignalWithTransform<boolean, unknown>;
179
240
  readonly hint: _angular_core.InputSignal<string | undefined>;
180
241
  readonly error: _angular_core.InputSignal<string | undefined>;
242
+ readonly unit: _angular_core.InputSignal<string | undefined>;
181
243
  protected readonly hintId: string;
182
244
  protected readonly errorId: string;
183
245
  protected readonly control: _angular_core.Signal<UiControl | undefined>;
184
- protected readonly labelClasses: _angular_core.Signal<"sr-only" | "text-[13px] font-medium text-(--foreground)">;
246
+ protected readonly labelClasses: _angular_core.Signal<"sr-only" | "text-label font-medium text-(--muted-foreground)">;
185
247
  /**
186
248
  * The caller's own `error` wins: it is the only way to report something the control does not
187
249
  * know about, such as a search box refusing an empty query.
@@ -189,10 +251,10 @@ declare class UiField {
189
251
  protected readonly message: _angular_core.Signal<string | undefined>;
190
252
  constructor();
191
253
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiField, never>;
192
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiField, "ui-field", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "labelHidden": { "alias": "labelHidden"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; }, {}, ["control"], ["*"], true, never>;
254
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiField, "ui-field", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "labelHidden": { "alias": "labelHidden"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "unit": { "alias": "unit"; "required": false; "isSignal": true; }; }, {}, ["control"], ["*"], true, never>;
193
255
  }
194
256
 
195
- /** Available control sizes. `md` is the 44px touch target. */
257
+ /** Available control sizes. `md` is the 44/40px touch target. */
196
258
  declare const CONTROL_SIZES: readonly ["sm", "md"];
197
259
  type ControlSize = (typeof CONTROL_SIZES)[number];
198
260
  /**
@@ -207,7 +269,7 @@ type ControlSize = (typeof CONTROL_SIZES)[number];
207
269
  * <input uiInput type="email" placeholder="you@example.com" />
208
270
  */
209
271
  declare class UiInput implements UiControl {
210
- readonly size: _angular_core.InputSignal<"sm" | "md">;
272
+ readonly size: _angular_core.InputSignal<"md" | "sm">;
211
273
  readonly errors: _angular_core.InputSignal<readonly UiControlError[]>;
212
274
  readonly touched: _angular_core.InputSignal<boolean>;
213
275
  protected readonly classes: _angular_core.Signal<string>;
@@ -232,6 +294,61 @@ declare class UiTextarea implements UiControl {
232
294
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiTextarea, "textarea[uiTextarea]", never, { "errors": { "alias": "errors"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
233
295
  }
234
296
 
297
+ /**
298
+ * One action of a `ui-menu`; `destructive` paints it in `--negative`.
299
+ *
300
+ * @example
301
+ * <button uiMenuItem destructive (click)="remove()">Delete the line</button>
302
+ */
303
+ declare class UiMenuItem {
304
+ #private;
305
+ readonly destructive: _angular_core.InputSignalWithTransform<boolean, unknown>;
306
+ protected readonly classes: _angular_core.Signal<string>;
307
+ focus(): void;
308
+ hasFocus(): boolean;
309
+ protected onClick(): void;
310
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiMenuItem, never>;
311
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiMenuItem, "button[uiMenuItem]", never, { "destructive": { "alias": "destructive"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
312
+ }
313
+ /**
314
+ * Secondary actions menu, opened from its trigger through the native Popover API.
315
+ *
316
+ * @example
317
+ * <button ui-button size="icon" aria-label="More" [uiMenuTrigger]="menu">...</button>
318
+ * <ui-menu #menu label="Line actions">
319
+ * <button uiMenuItem (click)="edit()">Edit</button>
320
+ * <button uiMenuItem destructive (click)="remove()">Delete the line</button>
321
+ * </ui-menu>
322
+ */
323
+ declare class UiMenu {
324
+ #private;
325
+ readonly label: _angular_core.InputSignal<string>;
326
+ readonly menuId: string;
327
+ readonly isOpen: _angular_core.WritableSignal<boolean>;
328
+ protected readonly classes = "m-0 min-w-44 p-1 rounded-container bg-(--elevated) shadow-[0_8px_24px_rgb(0_0_0/0.16)] text-(--foreground) transition-[opacity,transform,overlay,display] transition-discrete duration-(--duration-fast) ease-out starting:open:opacity-0 starting:open:scale-(--enter-scale) opacity-0 scale-(--enter-scale) open:opacity-100 open:scale-100";
329
+ protected readonly items: _angular_core.Signal<readonly UiMenuItem[]>;
330
+ open(trigger: HTMLElement): void;
331
+ close(): void;
332
+ protected onToggle(event: Event): void;
333
+ protected onKeydown(event: KeyboardEvent): void;
334
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiMenu, never>;
335
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiMenu, "ui-menu", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; }, {}, ["items"], ["*"], true, never>;
336
+ }
337
+ /**
338
+ * Opens a `ui-menu` from a trigger element and wires `aria-haspopup`, `aria-expanded` and
339
+ * `aria-controls` on it.
340
+ *
341
+ * @example
342
+ * <button ui-button size="icon" aria-label="More" [uiMenuTrigger]="menu">...</button>
343
+ */
344
+ declare class UiMenuTrigger {
345
+ #private;
346
+ readonly uiMenuTrigger: _angular_core.InputSignal<UiMenu>;
347
+ protected toggle(): void;
348
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiMenuTrigger, never>;
349
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiMenuTrigger, "[uiMenuTrigger]", never, { "uiMenuTrigger": { "alias": "uiMenuTrigger"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
350
+ }
351
+
235
352
  /** Ramp step used to paint the fill. `MeterTone` is derived from this tuple. */
236
353
  declare const METER_TONES: readonly [1, 2, 3, 4, 5, 6];
237
354
  type MeterTone = (typeof METER_TONES)[number];
@@ -246,19 +363,51 @@ declare class UiMeter {
246
363
  readonly value: _angular_core.InputSignal<number>;
247
364
  readonly label: _angular_core.InputSignal<string>;
248
365
  readonly valueText: _angular_core.InputSignal<string>;
249
- readonly tone: _angular_core.InputSignal<2 | 4 | 5 | 3 | 1 | 6>;
366
+ readonly tone: _angular_core.InputSignal<2 | 1 | 3 | 4 | 5 | 6>;
250
367
  protected readonly percent: _angular_core.Signal<number>;
251
368
  protected readonly fillClasses: _angular_core.Signal<string>;
252
369
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiMeter, never>;
253
370
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiMeter, "ui-meter", never, { "value": { "alias": "value"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "valueText": { "alias": "valueText"; "required": true; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
254
371
  }
255
372
 
373
+ /**
374
+ * One destination of the sidebar navigation (Portfolio, Holdings, Allocation, Accounts).
375
+ *
376
+ * @example
377
+ * <a ui-nav-item [active]="url === '/'" routerLink="/">
378
+ * <lucide-icon navIcon name="house" />
379
+ * Portfolio
380
+ * </a>
381
+ */
382
+ declare class UiNavItem {
383
+ readonly active: _angular_core.InputSignalWithTransform<boolean, unknown>;
384
+ protected readonly classes: _angular_core.Signal<string>;
385
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiNavItem, never>;
386
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiNavItem, "a[ui-nav-item]", never, { "active": { "alias": "active"; "required": false; "isSignal": true; }; }, {}, never, ["[navIcon]", "*"], true, never>;
387
+ }
388
+
389
+ /**
390
+ * Clickable row of a list: a holding, an envelope, an account.
391
+ *
392
+ * @example
393
+ * <a ui-row [selected]="holding.id === openId()" [routerLink]="['/holdings', holding.id]">
394
+ * {{ holding.name }}
395
+ * </a>
396
+ */
397
+ declare class UiRow {
398
+ readonly selected: _angular_core.InputSignalWithTransform<boolean, unknown>;
399
+ protected readonly classes: _angular_core.Signal<string>;
400
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiRow, never>;
401
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiRow, "a[ui-row], button[ui-row]", never, { "selected": { "alias": "selected"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
402
+ }
403
+
256
404
  type SegmentedOption = {
257
405
  value: string;
258
406
  label: string;
259
407
  };
260
408
  /**
261
- * Exclusive choice within a small, known set: a time range, a unit, a mode.
409
+ * Exclusive choice within a small, known set: a time range, a unit, a mode. The selected option's
410
+ * background is a single thumb that slides under it in `transform`, instead of being redrawn.
262
411
  *
263
412
  * @example
264
413
  * <ui-segmented [options]="ranges" label="Time range" [(value)]="range" />
@@ -268,6 +417,7 @@ declare class UiSegmented {
268
417
  readonly label: _angular_core.InputSignal<string>;
269
418
  readonly value: _angular_core.ModelSignal<string>;
270
419
  protected readonly radios: _angular_core.Signal<readonly ElementRef<HTMLButtonElement>[]>;
420
+ protected readonly thumbIndex: _angular_core.Signal<number>;
271
421
  protected optionClasses(optionValue: string): string;
272
422
  protected tabIndexFor(optionValue: string): number;
273
423
  protected select(optionValue: string): void;
@@ -287,7 +437,7 @@ declare class UiSegmented {
287
437
  * <select uiSelect><option value="pea">PEA</option></select>
288
438
  */
289
439
  declare class UiSelect implements UiControl {
290
- readonly size: _angular_core.InputSignal<"sm" | "md">;
440
+ readonly size: _angular_core.InputSignal<"md" | "sm">;
291
441
  readonly errors: _angular_core.InputSignal<readonly UiControlError[]>;
292
442
  readonly touched: _angular_core.InputSignal<boolean>;
293
443
  protected readonly classes: _angular_core.Signal<string>;
@@ -296,19 +446,21 @@ declare class UiSelect implements UiControl {
296
446
  }
297
447
 
298
448
  /**
299
- * Decorative placeholder for content that is still loading. Deliberately hidden from
300
- * assistive technology: the calling screen keeps its own role="status" sentence.
449
+ * Decorative, pulsing placeholder for content that is still loading, at the shape of the content
450
+ * it stands in for.
301
451
  *
302
452
  * @example
453
+ * <ui-skeleton shape="figure" />
303
454
  * <ui-skeleton [lines]="3" />
304
- * <ui-skeleton [height]="196" />
305
455
  */
306
456
  declare class UiSkeleton {
457
+ readonly shape: _angular_core.InputSignal<"figure" | "text" | "row" | "chart" | "ring">;
307
458
  readonly lines: _angular_core.InputSignal<number>;
308
459
  readonly height: _angular_core.InputSignal<number>;
460
+ protected readonly animation = "cairn-pulse var(--pulse-duration) var(--ease-out) infinite alternate";
309
461
  protected readonly bars: _angular_core.Signal<("62%" | "100%")[]>;
310
462
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiSkeleton, never>;
311
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiSkeleton, "ui-skeleton", never, { "lines": { "alias": "lines"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
463
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiSkeleton, "ui-skeleton", never, { "shape": { "alias": "shape"; "required": false; "isSignal": true; }; "lines": { "alias": "lines"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
312
464
  }
313
465
 
314
466
  /** Available stat sizes. `StatSize` is derived from this tuple. */
@@ -332,12 +484,24 @@ type StatSize = (typeof STAT_SIZES)[number];
332
484
  declare class UiStat {
333
485
  readonly label: _angular_core.InputSignal<string>;
334
486
  readonly size: _angular_core.InputSignal<"hero" | "tile">;
335
- protected readonly labelClasses = "text-[13px] font-medium text-(--subtle-foreground)";
487
+ protected readonly labelClasses = "text-label font-medium text-(--subtle-foreground)";
336
488
  protected readonly valueClasses: _angular_core.Signal<string>;
337
489
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiStat, never>;
338
490
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiStat, "ui-stat", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, ["[uiStatValue]", "[uiStatDelta]", "[uiStatCaption]", "[uiStatAside]"], true, never>;
339
491
  }
340
492
 
493
+ /**
494
+ * Native checkbox styled as an iOS-style switch, exposed to assistive technology as `role="switch"`.
495
+ *
496
+ * @example
497
+ * <input type="checkbox" uiSwitch [checked]="hidden()" (change)="toggle()" aria-label="Hide amounts" />
498
+ */
499
+ declare class UiSwitch {
500
+ protected readonly classes = "relative shrink-0 w-[51px] h-[31px] appearance-none cursor-pointer rounded-pill bg-(--muted) checked:bg-(--primary) transition-colors duration-(--duration-fast) ease-out disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) before:absolute before:inset-x-0 before:top-1/2 before:-translate-y-1/2 before:h-(--row-min) before:content-[''] after:absolute after:top-0.5 after:left-0.5 after:size-[27px] after:rounded-pill after:bg-(--card) after:shadow-[0_1px_3px_rgb(0_0_0/0.2)] after:transition-transform after:duration-(--duration-fast) after:ease-out after:content-[''] checked:after:translate-x-[20px]";
501
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiSwitch, never>;
502
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiSwitch, "input[type=checkbox][uiSwitch]", never, {}, {}, never, never, true, never>;
503
+ }
504
+
341
505
  /** Breakpoints a secondary column can be held back until. */
342
506
  declare const CELL_BREAKPOINTS: readonly ["md", "lg"];
343
507
  type CellBreakpoint = (typeof CELL_BREAKPOINTS)[number];
@@ -346,6 +510,20 @@ declare class UiTable {
346
510
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiTable, never>;
347
511
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiTable, "table[uiTable]", never, {}, {}, never, never, true, never>;
348
512
  }
513
+ /**
514
+ * Body or group row. `group` draws the muted banner that opens each account inside a list of
515
+ * holdings: it carries no hover state of its own, unlike an ordinary row.
516
+ *
517
+ * @example
518
+ * <tr uiTr>...
519
+ * <tr uiTr group>...
520
+ */
521
+ declare class UiTr {
522
+ readonly group: _angular_core.InputSignalWithTransform<boolean, unknown>;
523
+ protected readonly classes: _angular_core.Signal<"min-h-(--row-min) rounded-control bg-(--muted) font-semibold" | "rounded-control hover:bg-(--glow)">;
524
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiTr, never>;
525
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiTr, "tr[uiTr]", never, { "group": { "alias": "group"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
526
+ }
349
527
  /**
350
528
  * Column header. `numeric` right-aligns and lines the digits up; `from` holds a
351
529
  * secondary column back until the viewport is wide enough for it.
@@ -369,5 +547,30 @@ declare class UiTd {
369
547
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiTd, "td[uiTd]", never, { "numeric": { "alias": "numeric"; "required": false; "isSignal": true; }; "from": { "alias": "from"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
370
548
  }
371
549
 
372
- export { AVATAR_SIZES, BADGE_VARIANTS, BUTTON_SIZES, BUTTON_VARIANTS, CARD_PADDINGS, CARD_VARIANTS, CELL_BREAKPOINTS, CONTROL_SIZES, DELTA_EMPHASES, DIALOG_WIDTHS, METER_TONES, STAT_SIZES, UiAvatar, UiBadge, UiButton, UiCard, UiDelta, UiDialog, UiField, UiInput, UiMeter, UiSegmented, UiSelect, UiSkeleton, UiStat, UiTable, UiTd, UiTextarea, UiTh };
373
- export type { AvatarSize, BadgeVariant, ButtonSize, ButtonVariant, CardPadding, CardVariant, CellBreakpoint, ControlSize, DeltaEmphasis, DialogWidth, MeterTone, SegmentedOption, StatSize, UiControlError };
550
+ /**
551
+ * Bottom navigation bar for iPhone: 52px of tabs plus the home indicator's safe area.
552
+ *
553
+ * @example
554
+ * <nav ui-tab-bar>
555
+ * <a ui-tab [active]="url === '/'" routerLink="/"><lucide-icon tabIcon name="house" />Portfolio</a>
556
+ * </nav>
557
+ */
558
+ declare class UiTabBar {
559
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiTabBar, never>;
560
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiTabBar, "nav[ui-tab-bar]", never, {}, {}, never, ["*"], true, never>;
561
+ }
562
+ /**
563
+ * One destination of `ui-tab-bar`.
564
+ *
565
+ * @example
566
+ * <a ui-tab active routerLink="/"><lucide-icon tabIcon name="house" />Portfolio</a>
567
+ */
568
+ declare class UiTab {
569
+ readonly active: _angular_core.InputSignalWithTransform<boolean, unknown>;
570
+ protected readonly classes: _angular_core.Signal<string>;
571
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiTab, never>;
572
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiTab, "a[ui-tab]", never, { "active": { "alias": "active"; "required": false; "isSignal": true; }; }, {}, never, ["[tabIcon]", "*"], true, never>;
573
+ }
574
+
575
+ export { AMOUNT_MASKINGS, AMOUNT_NUMERICS, ASYNC_STATES, AVATAR_SIZES, BADGE_VARIANTS, BUTTON_SIZES, BUTTON_VARIANTS, CARD_PADDINGS, CARD_VARIANTS, CELL_BREAKPOINTS, CONTROL_SIZES, DELTA_EMPHASES, DIALOG_WIDTHS, METER_TONES, STAT_SIZES, UI_AMOUNT_MASKED, UiAmount, UiAsync, UiAvatar, UiBadge, UiButton, UiCard, UiDelta, UiDialog, UiField, UiInput, UiMenu, UiMenuItem, UiMenuTrigger, UiMeter, UiNavItem, UiRow, UiSegmented, UiSelect, UiSkeleton, UiStat, UiSwitch, UiTab, UiTabBar, UiTable, UiTd, UiTextarea, UiTh, UiTr, formatAmount };
576
+ export type { AmountFormatOptions, AmountMasking, AmountNumeric, AsyncState, AvatarSize, BadgeVariant, ButtonSize, ButtonVariant, CardPadding, CardVariant, CellBreakpoint, ControlSize, DeltaEmphasis, DialogWidth, MeterTone, SegmentedOption, StatSize, UiControlError };