@ponchia/ui 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/@ponchia/ui?logo=npm)](https://www.npmjs.com/package/@ponchia/ui)
4
4
  [![npm provenance](https://img.shields.io/badge/npm-provenance-blue?logo=npm)](https://www.npmjs.com/package/@ponchia/ui#provenance)
5
5
  [![runtime deps](https://img.shields.io/badge/runtime%20deps-0-brightgreen)](package.json)
6
- [![bundle](https://img.shields.io/badge/dist-~54kB%20%2F%20~10kB%20gzip-informational)](scripts/check-dist.mjs)
6
+ [![bundle](https://img.shields.io/badge/dist-~64kB%20%2F%20~11kB%20gzip-informational)](scripts/check-dist.mjs)
7
7
  [![CI](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml)
8
8
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
9
9
 
@@ -12,7 +12,8 @@ monochrome surfaces, a single red accent, dot-matrix display type (Doto),
12
12
  flat hairline borders, restrained motion. CSS-first and framework-agnostic.
13
13
 
14
14
  **New here?** → [Getting started](#getting-started) ·
15
- [Reference](docs/reference.md) · [Theming](docs/theming.md) ·
15
+ [Reference](docs/reference.md) · [Usage](docs/usage.md) ·
16
+ [Theming](docs/theming.md) · [Contrast](docs/contrast.md) ·
16
17
  [Roadmap](ROADMAP.md) · [Contributing](CONTRIBUTING.md)
17
18
 
18
19
  > **Editor IntelliSense.** The package ships a VS Code CSS Custom Data
@@ -25,6 +26,12 @@ flat hairline borders, restrained motion. CSS-first and framework-agnostic.
25
26
  > }
26
27
  > ```
27
28
 
29
+ > **For AI agents / LLMs.** The package ships `llms.txt` at its root —
30
+ > point a coding agent at `node_modules/@ponchia/ui/llms.txt` for a
31
+ > self-contained orientation. The full class catalog
32
+ > (`docs/reference.md`) and the token contract (`docs/theming.md`) also
33
+ > ship in the tarball, so an offline agent never has to guess at the API.
34
+
28
35
  **[Live demo →](https://ponchia.github.io/bronto-ui/)** — the kitchen
29
36
  sink (every component, light/dark, RTL, theming) deployed from `demo/`.
30
37
 
@@ -59,7 +66,7 @@ chain:
59
66
  @import '@ponchia/ui'; /* → dist/bronto.css, the whole framework */
60
67
  ```
61
68
 
62
- ~54 kB raw / ~10 kB gzip, one request, same `@layer bronto`. (The
69
+ ~64 kB raw / ~11 kB gzip, one request, same `@layer bronto`. (The
63
70
  enforced ceiling lives in `scripts/check-dist.mjs`, not this prose —
64
71
  treat these figures as indicative.) Source CSS, tokens/classes/behaviors
65
72
  entrypoints are unchanged — use whichever fits.
@@ -108,11 +108,18 @@ export declare function initDialog(opts?: DelegateOpts): Cleanup;
108
108
 
109
109
  export interface ToastOpts {
110
110
  /** Status tone — maps to `ui-toast--<tone>`. */
111
- tone?: 'accent' | 'success' | 'warning' | 'danger';
111
+ tone?: 'accent' | 'success' | 'warning' | 'danger' | 'info';
112
112
  /** Optional uppercase label rendered above the message. */
113
113
  title?: string;
114
114
  /** Auto-dismiss delay in ms. 0 keeps it until dismissed. Default: 4000. */
115
115
  duration?: number;
116
+ /**
117
+ * Route to the assertive live region so AT interrupts immediately.
118
+ * Defaults to `true` when `tone === 'danger'`.
119
+ */
120
+ assertive?: boolean;
121
+ /** Render a dismiss button on the toast. */
122
+ closable?: boolean;
116
123
  }
117
124
 
118
125
  /**
@@ -319,7 +319,7 @@ export function initDialog({ root } = {}) {
319
319
  * that receives its first child in the same tick is not reliably
320
320
  * announced by VoiceOver/NVDA). On first creation the empty region is
321
321
  * inserted and the toast is appended on the next frame for the same
322
- * reason. `tone` is accent/success/warning/danger; `title` is an
322
+ * reason. `tone` is accent/success/warning/danger/info; `title` is an
323
323
  * optional uppercase label; `duration` ms before auto-dismiss (0 keeps
324
324
  * it until dismissed). Returns a function that dismisses the toast
325
325
  * early. SSR-safe (no-op).
@@ -607,11 +607,29 @@ export function initFormValidation({ root } = {}) {
607
607
  };
608
608
 
609
609
  return bindOnce(host, 'formValidation', () => {
610
+ // Suppress native bubbles UP FRONT for forms present at init. The
611
+ // in-handler `noValidate = true` only fires after the first
612
+ // submit/blur, so the very first invalid real-browser submit would
613
+ // otherwise show the native UA bubble instead of the Bronto
614
+ // summary — contradicting the documented contract. (Forms added
615
+ // after init are still covered by the in-handler set.)
616
+ // Feature-detect rather than `instanceof Element` — `Element` is not
617
+ // a guaranteed global (SSR / the no-DOM test env), and `host` is
618
+ // either `document` (no `.matches`) or a root Element.
619
+ const selfForm =
620
+ typeof host.matches === 'function' && host.matches('[data-bronto-validate]') ? [host] : [];
621
+ const forms = [...selfForm, ...(host.querySelectorAll?.('[data-bronto-validate]') ?? [])];
622
+ const priorNoValidate = new Map();
623
+ for (const f of forms) {
624
+ priorNoValidate.set(f, f.noValidate);
625
+ f.noValidate = true;
626
+ }
610
627
  host.addEventListener('submit', onSubmit, true);
611
628
  host.addEventListener('focusout', onBlur);
612
629
  return () => {
613
630
  host.removeEventListener('submit', onSubmit, true);
614
631
  host.removeEventListener('focusout', onBlur);
632
+ for (const [f, v] of priorNoValidate) f.noValidate = v;
615
633
  };
616
634
  });
617
635
  }
@@ -702,6 +720,12 @@ export function initCombobox({ root } = {}) {
702
720
  if (match) any = true;
703
721
  }
704
722
  if (empty) empty.hidden = any;
723
+ // The active option may have just been filtered out — drop the
724
+ // stale activedescendant so Enter can't select a hidden option.
725
+ if (active >= 0 && options[active]?.hidden) {
726
+ active = -1;
727
+ setActive(null);
728
+ }
705
729
  open();
706
730
  };
707
731
 
@@ -762,7 +786,7 @@ export function initCombobox({ root } = {}) {
762
786
  }
763
787
  break;
764
788
  case 'Enter':
765
- if (!list.hidden && active >= 0) {
789
+ if (!list.hidden && active >= 0 && !options[active].hidden) {
766
790
  e.preventDefault();
767
791
  select(options[active]);
768
792
  }
@@ -1,7 +1,16 @@
1
1
  /** @ponchia/ui — GENERATED from classes/index.js by scripts/gen-dts.mjs.
2
2
  * Do not edit by hand; run `npm run dts:build`. Drift-checked in CI. */
3
3
 
4
- export type ClassValue = string | false | null | undefined | ClassValue[];
4
+ // Mirrors clsx's permissive input: `number`/`boolean` accepted so the
5
+ // idiomatic React `reactNode && 'cls'` guard (where the node may be 0 or
6
+ // '') type-checks. The runtime `cx` skips every falsy value regardless.
7
+ export type ClassValue =
8
+ | string
9
+ | number
10
+ | boolean
11
+ | null
12
+ | undefined
13
+ | ClassValue[];
5
14
 
6
15
  /** The flat registry of every class @ponchia/ui defines (literal). */
7
16
  export declare const cls: {
@@ -10,6 +19,8 @@ export declare const cls: {
10
19
  readonly buttonSubtle: 'ui-button--subtle';
11
20
  readonly buttonDanger: 'ui-button--danger';
12
21
  readonly buttonIcon: 'ui-button--icon';
22
+ readonly buttonSm: 'ui-button--sm';
23
+ readonly buttonLg: 'ui-button--lg';
13
24
  readonly card: 'ui-card';
14
25
  readonly cardHead: 'ui-card__head';
15
26
  readonly cardAccent: 'ui-card--accent';
@@ -28,6 +39,7 @@ export declare const cls: {
28
39
  readonly badgeSuccess: 'ui-badge--success';
29
40
  readonly badgeWarning: 'ui-badge--warning';
30
41
  readonly badgeDanger: 'ui-badge--danger';
42
+ readonly badgeInfo: 'ui-badge--info';
31
43
  readonly badgeMuted: 'ui-badge--muted';
32
44
  readonly badgeDot: 'ui-badge--dot';
33
45
  readonly chip: 'ui-chip';
@@ -36,11 +48,13 @@ export declare const cls: {
36
48
  readonly linkArrow: 'ui-link--arrow';
37
49
  readonly linkCta: 'ui-link--cta';
38
50
  readonly keyValue: 'ui-key-value';
51
+ readonly emptyState: 'ui-empty-state';
39
52
  readonly dot: 'ui-dot';
40
53
  readonly dotAccent: 'ui-dot--accent';
41
54
  readonly dotSuccess: 'ui-dot--success';
42
55
  readonly dotWarning: 'ui-dot--warning';
43
56
  readonly dotDanger: 'ui-dot--danger';
57
+ readonly dotInfo: 'ui-dot--info';
44
58
  readonly dotLive: 'ui-dot--live';
45
59
  readonly dotgrid: 'ui-dotgrid';
46
60
  readonly dotgridAccent: 'ui-dotgrid--accent';
@@ -84,6 +98,7 @@ export declare const cls: {
84
98
  readonly alertSuccess: 'ui-alert--success';
85
99
  readonly alertWarning: 'ui-alert--warning';
86
100
  readonly alertDanger: 'ui-alert--danger';
101
+ readonly alertInfo: 'ui-alert--info';
87
102
  readonly toastStack: 'ui-toast-stack';
88
103
  readonly toastStackAssertive: 'ui-toast-stack--assertive';
89
104
  readonly toast: 'ui-toast';
@@ -93,6 +108,7 @@ export declare const cls: {
93
108
  readonly toastSuccess: 'ui-toast--success';
94
109
  readonly toastWarning: 'ui-toast--warning';
95
110
  readonly toastDanger: 'ui-toast--danger';
111
+ readonly toastInfo: 'ui-toast--info';
96
112
  readonly tooltip: 'ui-tooltip';
97
113
  readonly tooltipBubble: 'ui-tooltip__bubble';
98
114
  readonly popover: 'ui-popover';
@@ -205,6 +221,7 @@ export declare const cls: {
205
221
  readonly appRailBrand: 'ui-app-rail__brand';
206
222
  readonly appRailToggle: 'ui-app-rail__toggle';
207
223
  readonly appRailFoot: 'ui-app-rail__foot';
224
+ readonly appRailAccount: 'ui-app-rail__account';
208
225
  readonly appTopbar: 'ui-app-topbar';
209
226
  readonly appTopbarTitle: 'ui-app-topbar__title';
210
227
  readonly appToolbar: 'ui-app-toolbar';
@@ -235,13 +252,14 @@ export declare function cx(...parts: ClassValue[]): string;
235
252
  export interface ButtonOpts {
236
253
  variant?: 'ghost' | 'subtle' | 'danger';
237
254
  icon?: boolean;
255
+ size?: 'sm' | 'lg';
238
256
  }
239
257
  export interface CardOpts {
240
258
  accent?: boolean;
241
259
  interactive?: boolean;
242
260
  }
243
261
  export interface BadgeOpts {
244
- tone?: 'accent' | 'success' | 'warning' | 'danger' | 'muted';
262
+ tone?: 'accent' | 'success' | 'warning' | 'danger' | 'info' | 'muted';
245
263
  dot?: boolean;
246
264
  }
247
265
  export interface NumOpts {
@@ -255,7 +273,7 @@ export interface LinkOpts {
255
273
  cta?: boolean;
256
274
  }
257
275
  export interface DotOpts {
258
- tone?: 'accent' | 'success' | 'warning' | 'danger';
276
+ tone?: 'accent' | 'success' | 'warning' | 'danger' | 'info';
259
277
  live?: boolean;
260
278
  }
261
279
  export interface DotgridOpts {
@@ -279,7 +297,7 @@ export interface ClusterOpts {
279
297
  export interface StaggerOpts {
280
298
  auto?: boolean;
281
299
  }
282
- export type Tone = 'accent' | 'success' | 'warning' | 'danger';
300
+ export type Tone = 'accent' | 'success' | 'warning' | 'danger' | 'info';
283
301
  export interface AlertOpts {
284
302
  tone?: Tone;
285
303
  }
@@ -297,6 +315,8 @@ export interface DotbarOpts {
297
315
  }
298
316
  export interface ModalOpts {
299
317
  drawer?: boolean;
318
+ /** Controlled non-dialog usage — adds the is-open state (focus-trap is yours). */
319
+ open?: boolean;
300
320
  }
301
321
  export interface TabOpts {
302
322
  active?: boolean;
package/classes/index.js CHANGED
@@ -23,6 +23,8 @@ export const cls = Object.freeze({
23
23
  buttonSubtle: 'ui-button--subtle',
24
24
  buttonDanger: 'ui-button--danger',
25
25
  buttonIcon: 'ui-button--icon',
26
+ buttonSm: 'ui-button--sm',
27
+ buttonLg: 'ui-button--lg',
26
28
  card: 'ui-card',
27
29
  cardHead: 'ui-card__head',
28
30
  cardAccent: 'ui-card--accent',
@@ -41,6 +43,7 @@ export const cls = Object.freeze({
41
43
  badgeSuccess: 'ui-badge--success',
42
44
  badgeWarning: 'ui-badge--warning',
43
45
  badgeDanger: 'ui-badge--danger',
46
+ badgeInfo: 'ui-badge--info',
44
47
  badgeMuted: 'ui-badge--muted',
45
48
  badgeDot: 'ui-badge--dot',
46
49
  chip: 'ui-chip',
@@ -49,12 +52,14 @@ export const cls = Object.freeze({
49
52
  linkArrow: 'ui-link--arrow',
50
53
  linkCta: 'ui-link--cta',
51
54
  keyValue: 'ui-key-value',
55
+ emptyState: 'ui-empty-state',
52
56
  // dots
53
57
  dot: 'ui-dot',
54
58
  dotAccent: 'ui-dot--accent',
55
59
  dotSuccess: 'ui-dot--success',
56
60
  dotWarning: 'ui-dot--warning',
57
61
  dotDanger: 'ui-dot--danger',
62
+ dotInfo: 'ui-dot--info',
58
63
  dotLive: 'ui-dot--live',
59
64
  dotgrid: 'ui-dotgrid',
60
65
  dotgridAccent: 'ui-dotgrid--accent',
@@ -100,6 +105,7 @@ export const cls = Object.freeze({
100
105
  alertSuccess: 'ui-alert--success',
101
106
  alertWarning: 'ui-alert--warning',
102
107
  alertDanger: 'ui-alert--danger',
108
+ alertInfo: 'ui-alert--info',
103
109
  toastStack: 'ui-toast-stack',
104
110
  toastStackAssertive: 'ui-toast-stack--assertive',
105
111
  toast: 'ui-toast',
@@ -109,6 +115,7 @@ export const cls = Object.freeze({
109
115
  toastSuccess: 'ui-toast--success',
110
116
  toastWarning: 'ui-toast--warning',
111
117
  toastDanger: 'ui-toast--danger',
118
+ toastInfo: 'ui-toast--info',
112
119
  tooltip: 'ui-tooltip',
113
120
  tooltipBubble: 'ui-tooltip__bubble',
114
121
  popover: 'ui-popover',
@@ -229,6 +236,7 @@ export const cls = Object.freeze({
229
236
  appRailBrand: 'ui-app-rail__brand',
230
237
  appRailToggle: 'ui-app-rail__toggle',
231
238
  appRailFoot: 'ui-app-rail__foot',
239
+ appRailAccount: 'ui-app-rail__account',
232
240
  appTopbar: 'ui-app-topbar',
233
241
  appTopbarTitle: 'ui-app-topbar__title',
234
242
  appToolbar: 'ui-app-toolbar',
@@ -264,13 +272,15 @@ export function cx(...parts) {
264
272
  const j = (...p) => p.filter(Boolean).join(' ');
265
273
 
266
274
  export const ui = {
267
- button: ({ variant, icon } = {}) =>
275
+ button: ({ variant, icon, size } = {}) =>
268
276
  j(
269
277
  cls.button,
270
278
  variant === 'ghost' && cls.buttonGhost,
271
279
  variant === 'subtle' && cls.buttonSubtle,
272
280
  variant === 'danger' && cls.buttonDanger,
273
281
  icon && cls.buttonIcon,
282
+ size === 'sm' && cls.buttonSm,
283
+ size === 'lg' && cls.buttonLg,
274
284
  ),
275
285
  card: ({ accent, interactive } = {}) =>
276
286
  j(cls.card, accent && cls.cardAccent, interactive && cls.cardInteractive),
@@ -281,6 +291,7 @@ export const ui = {
281
291
  tone === 'success' && cls.badgeSuccess,
282
292
  tone === 'warning' && cls.badgeWarning,
283
293
  tone === 'danger' && cls.badgeDanger,
294
+ tone === 'info' && cls.badgeInfo,
284
295
  tone === 'muted' && cls.badgeMuted,
285
296
  dot && cls.badgeDot,
286
297
  ),
@@ -300,6 +311,7 @@ export const ui = {
300
311
  tone === 'success' && cls.dotSuccess,
301
312
  tone === 'warning' && cls.dotWarning,
302
313
  tone === 'danger' && cls.dotDanger,
314
+ tone === 'info' && cls.dotInfo,
303
315
  live && cls.dotLive,
304
316
  ),
305
317
  dotgrid: ({ accent, dense } = {}) =>
@@ -322,6 +334,7 @@ export const ui = {
322
334
  tone === 'success' && cls.alertSuccess,
323
335
  tone === 'warning' && cls.alertWarning,
324
336
  tone === 'danger' && cls.alertDanger,
337
+ tone === 'info' && cls.alertInfo,
325
338
  ),
326
339
  toast: ({ tone } = {}) =>
327
340
  j(
@@ -330,12 +343,13 @@ export const ui = {
330
343
  tone === 'success' && cls.toastSuccess,
331
344
  tone === 'warning' && cls.toastWarning,
332
345
  tone === 'danger' && cls.toastDanger,
346
+ tone === 'info' && cls.toastInfo,
333
347
  ),
334
348
  progress: ({ indeterminate } = {}) => j(cls.progress, indeterminate && cls.progressIndeterminate),
335
349
  dotspinner: ({ size } = {}) =>
336
350
  j(cls.dotspinner, size === 'sm' && cls.dotspinnerSm, size === 'lg' && cls.dotspinnerLg),
337
351
  dotbar: ({ indeterminate } = {}) => j(cls.dotbar, indeterminate && cls.dotbarIndeterminate),
338
- modal: ({ drawer } = {}) => j(cls.modal, drawer && cls.modalDrawer),
352
+ modal: ({ drawer, open } = {}) => j(cls.modal, drawer && cls.modalDrawer, open && 'is-open'),
339
353
  tab: ({ active } = {}) => j(cls.tab, active && 'is-active'),
340
354
  avatar: ({ size } = {}) =>
341
355
  j(cls.avatar, size === 'sm' && cls.avatarSm, size === 'lg' && cls.avatarLg),
@@ -85,6 +85,10 @@
85
85
  "name": "--bronto-color-focus",
86
86
  "description": "Global scale token. Value: `var(--focus-ring)`"
87
87
  },
88
+ {
89
+ "name": "--bronto-color-info",
90
+ "description": "Global scale token. Value: `var(--info)`"
91
+ },
88
92
  {
89
93
  "name": "--bronto-color-on-action",
90
94
  "description": "Global scale token. Value: `var(--button-text)`"
@@ -131,7 +135,7 @@
131
135
  },
132
136
  {
133
137
  "name": "--display",
134
- "description": "Global scale token. Value: `'Doto', var(--mono)`"
138
+ "description": "Global scale token. Value: `var(--dot-font)`"
135
139
  },
136
140
  {
137
141
  "name": "--dot-font",
@@ -185,6 +189,14 @@
185
189
  "name": "--focus-ring",
186
190
  "description": "Theme token. Light: `var(--accent)` · Dark: `var(--accent)`"
187
191
  },
192
+ {
193
+ "name": "--info",
194
+ "description": "Theme token. Light: `#1f63c4` · Dark: `#6fb0e6`"
195
+ },
196
+ {
197
+ "name": "--info-soft",
198
+ "description": "Theme token. Light: `rgb(31, 99, 196, 0.12)` · Dark: `rgb(111, 176, 230, 0.14)`"
199
+ },
188
200
  {
189
201
  "name": "--line",
190
202
  "description": "Theme token. Light: `#d8d8d4` · Dark: `#2a2a2a`"
package/css/app.css CHANGED
@@ -111,6 +111,22 @@
111
111
  padding-block-start: var(--space-sm);
112
112
  }
113
113
 
114
+ /* Account / identity slot — signed-in user + sign-out. A framework-
115
+ blessed home for what every shell otherwise hand-rolls. Pinned to the
116
+ rail end like __foot, but it stays visible when the rail collapses to
117
+ a horizontal bar on mobile (sign-out must not vanish). */
118
+ .ui-app-rail__account {
119
+ align-items: center;
120
+ display: flex;
121
+ gap: var(--space-xs);
122
+ margin-block-start: auto;
123
+ padding-block-start: var(--space-sm);
124
+ }
125
+
126
+ .ui-app-rail__account + .ui-app-rail__foot {
127
+ margin-block-start: 0;
128
+ }
129
+
114
130
  /* --- Main column --- */
115
131
 
116
132
  .ui-app-main {
@@ -196,8 +212,7 @@
196
212
  text-transform: uppercase;
197
213
  }
198
214
 
199
- .ui-app-panel__head p,
200
- .ui-app-empty-state p {
215
+ .ui-app-panel__head p {
201
216
  color: var(--text-dim);
202
217
  margin: 0.3rem 0 0;
203
218
  }
@@ -209,15 +224,11 @@
209
224
  the canonical rules there — identical output). Nothing app-specific
210
225
  left to define here. */
211
226
 
212
- /* --- Empty state --- */
213
-
214
- .ui-app-empty-state {
215
- border: 1px dashed var(--line-strong);
216
- border-radius: var(--radius-md);
217
- color: var(--text-dim);
218
- padding: var(--space-lg);
219
- text-align: center;
220
- }
227
+ /* --- Empty state ---
228
+ The empty-state primitive is shell-agnostic and now lives in
229
+ primitives.css as `.ui-empty-state`; `.ui-app-empty-state` remains a
230
+ permanent admin-shell alias (grouped on the canonical rules there —
231
+ identical output). Nothing app-specific left to define here. */
221
232
 
222
233
  /* --- Mobile rail collapse --- */
223
234
 
package/css/dots.css CHANGED
@@ -94,6 +94,10 @@
94
94
  background: var(--danger);
95
95
  }
96
96
 
97
+ .ui-dot--info {
98
+ background: var(--info);
99
+ }
100
+
97
101
  .ui-dot--live {
98
102
  background: var(--success);
99
103
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 70%, transparent);
@@ -229,7 +233,7 @@
229
233
 
230
234
  .ui-dotbar i {
231
235
  background: var(--field-dot);
232
- border-radius: 1px;
236
+ border-radius: var(--radius-sm);
233
237
  flex: 1;
234
238
  block-size: 0.5rem;
235
239
  }
package/css/feedback.css CHANGED
@@ -95,6 +95,14 @@
95
95
  background: var(--danger);
96
96
  }
97
97
 
98
+ .ui-alert--info {
99
+ border-inline-start-color: var(--info);
100
+ }
101
+
102
+ .ui-alert--info::before {
103
+ background: var(--info);
104
+ }
105
+
98
106
  @media (hover: hover) {
99
107
  .ui-alert__dismiss:hover {
100
108
  color: var(--text);
@@ -201,6 +209,10 @@
201
209
  background: var(--danger);
202
210
  }
203
211
 
212
+ .ui-toast--info::before {
213
+ background: var(--info);
214
+ }
215
+
204
216
  /* --- Tooltip — CSS-only, hover/focus, no JS --- */
205
217
 
206
218
  .ui-tooltip {
package/css/overlay.css CHANGED
@@ -31,6 +31,16 @@
31
31
  grid-template-rows: auto 1fr auto;
32
32
  }
33
33
 
34
+ /* Controlled (non-<dialog>) usage. A portal/React modal that can't be a
35
+ native <dialog> wears the same skin + open layout via `.is-open`.
36
+ Backdrop, top-layer stacking and focus-trapping are then the
37
+ consumer's responsibility (the native <dialog> path gets them free). */
38
+ .ui-modal.is-open {
39
+ animation: uiToastIn var(--duration-base) var(--ease-spring) both;
40
+ display: grid;
41
+ grid-template-rows: auto 1fr auto;
42
+ }
43
+
34
44
  .ui-modal__head {
35
45
  align-items: flex-start;
36
46
  border-block-end: 1px solid var(--line);
@@ -314,6 +314,24 @@
314
314
  padding: 0;
315
315
  }
316
316
 
317
+ /* Size scale — dense tooling (toolbars, pagination, table actions) wants
318
+ --sm; --lg for hero CTAs. Token-driven; the default is unchanged. */
319
+ .ui-button--sm {
320
+ font-size: var(--text-xs);
321
+ gap: 0.3rem;
322
+ min-block-size: 1.9rem;
323
+ min-inline-size: 1.9rem;
324
+ padding: 0.4rem 0.66rem;
325
+ }
326
+
327
+ .ui-button--lg {
328
+ font-size: var(--text-base);
329
+ gap: 0.55rem;
330
+ min-block-size: 2.9rem;
331
+ min-inline-size: 2.9rem;
332
+ padding: 0.85rem 1.4rem;
333
+ }
334
+
317
335
  .ui-button:disabled,
318
336
  .ui-button[aria-disabled='true'] {
319
337
  cursor: not-allowed;
@@ -459,6 +477,11 @@
459
477
  border-color: color-mix(in srgb, var(--danger) 40%, var(--line));
460
478
  }
461
479
 
480
+ .ui-badge--info {
481
+ background: var(--info-soft);
482
+ border-color: color-mix(in srgb, var(--info) 40%, var(--line));
483
+ }
484
+
462
485
  /* Idle / unknown — the honest "no signal yet" tone (distinct from the
463
486
  default tinted badge). Token-safe; no new hue. */
464
487
  .ui-badge--muted {
@@ -495,6 +518,10 @@
495
518
  background: var(--danger);
496
519
  }
497
520
 
521
+ .ui-badge--info.ui-badge--dot::before {
522
+ background: var(--info);
523
+ }
524
+
498
525
  /* --- Key/value list --- */
499
526
 
500
527
  .ui-key-value {
@@ -586,3 +613,21 @@
586
613
  min-inline-size: 2.9rem;
587
614
  }
588
615
  }
616
+
617
+ /* --- Empty state — shell-agnostic placeholder for "no data yet".
618
+ Freed from the admin shell; `.ui-app-empty-state` stays a permanent
619
+ grouped alias (identical output, no baseline drift). --- */
620
+ .ui-empty-state,
621
+ .ui-app-empty-state {
622
+ border: 1px dashed var(--line-strong);
623
+ border-radius: var(--radius-md);
624
+ color: var(--text-dim);
625
+ padding: var(--space-lg);
626
+ text-align: center;
627
+ }
628
+
629
+ .ui-empty-state p,
630
+ .ui-app-empty-state p {
631
+ color: var(--text-dim);
632
+ margin: 0.3rem 0 0;
633
+ }
package/css/tokens.css CHANGED
@@ -29,8 +29,8 @@
29
29
  --mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', ui-monospace, monospace;
30
30
  --sans:
31
31
  'Inter', 'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
32
- --display: 'Doto', var(--mono);
33
32
  --dot-font: 'Doto', var(--mono);
33
+ --display: var(--dot-font);
34
34
  --text-2xs: 0.68rem;
35
35
  --text-xs: 0.76rem;
36
36
  --text-sm: 0.86rem;
@@ -99,6 +99,7 @@
99
99
  --bronto-color-success: var(--success);
100
100
  --bronto-color-warning: var(--warning);
101
101
  --bronto-color-danger: var(--danger);
102
+ --bronto-color-info: var(--info);
102
103
 
103
104
  /* Aliases kept for back-compat with existing semantic classes */
104
105
  --surface: var(--panel);
@@ -138,6 +139,8 @@
138
139
  --orange-soft: rgb(168, 95, 50, 0.13);
139
140
  --danger: #c01622;
140
141
  --danger-soft: rgb(192, 22, 34, 0.1);
142
+ --info: #1f63c4;
143
+ --info-soft: rgb(31, 99, 196, 0.12);
141
144
  --code-bg: rgb(10, 10, 10, 0.05);
142
145
  --button-text: #ffffff;
143
146
  --field-dot: rgb(10, 10, 10, 0.16);
@@ -189,6 +192,8 @@
189
192
  --orange-soft: rgb(208, 140, 91, 0.15);
190
193
  --danger: #ff4d54;
191
194
  --danger-soft: rgb(255, 77, 84, 0.15);
195
+ --info: #6fb0e6;
196
+ --info-soft: rgb(111, 176, 230, 0.14);
192
197
  --code-bg: rgb(255, 255, 255, 0.05);
193
198
  --button-text: #000000;
194
199
  --field-dot: rgb(242, 242, 242, 0.14);
@@ -226,6 +231,8 @@
226
231
  --orange-soft: rgb(208, 140, 91, 0.15);
227
232
  --danger: #ff4d54;
228
233
  --danger-soft: rgb(255, 77, 84, 0.15);
234
+ --info: #6fb0e6;
235
+ --info-soft: rgb(111, 176, 230, 0.14);
229
236
  --code-bg: rgb(255, 255, 255, 0.05);
230
237
  --button-text: #000000;
231
238
  --field-dot: rgb(242, 242, 242, 0.14);