@ponchia/ui 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,6 +25,12 @@ flat hairline borders, restrained motion. CSS-first and framework-agnostic.
25
25
  > }
26
26
  > ```
27
27
 
28
+ > **For AI agents / LLMs.** The package ships `llms.txt` at its root —
29
+ > point a coding agent at `node_modules/@ponchia/ui/llms.txt` for a
30
+ > self-contained orientation. The full class catalog
31
+ > (`docs/reference.md`) and the token contract (`docs/theming.md`) also
32
+ > ship in the tarball, so an offline agent never has to guess at the API.
33
+
28
34
  **[Live demo →](https://ponchia.github.io/bronto-ui/)** — the kitchen
29
35
  sink (every component, light/dark, RTL, theming) deployed from `demo/`.
30
36
 
@@ -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,20 +19,35 @@ 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';
16
27
  readonly cardInteractive: 'ui-card--interactive';
28
+ readonly stat: 'ui-stat';
29
+ readonly statgrid: 'ui-statgrid';
30
+ readonly statLabel: 'ui-stat__label';
31
+ readonly statValue: 'ui-stat__value';
32
+ readonly statDelta: 'ui-stat__delta';
33
+ readonly num: 'ui-num';
34
+ readonly numPos: 'ui-num--pos';
35
+ readonly numNeg: 'ui-num--neg';
36
+ readonly numMuted: 'ui-num--muted';
17
37
  readonly badge: 'ui-badge';
18
38
  readonly badgeAccent: 'ui-badge--accent';
19
39
  readonly badgeSuccess: 'ui-badge--success';
20
40
  readonly badgeWarning: 'ui-badge--warning';
21
41
  readonly badgeDanger: 'ui-badge--danger';
42
+ readonly badgeMuted: 'ui-badge--muted';
43
+ readonly badgeDot: 'ui-badge--dot';
22
44
  readonly chip: 'ui-chip';
23
45
  readonly chipAccent: 'ui-chip--accent';
24
46
  readonly link: 'ui-link';
25
47
  readonly linkArrow: 'ui-link--arrow';
48
+ readonly linkCta: 'ui-link--cta';
26
49
  readonly keyValue: 'ui-key-value';
50
+ readonly emptyState: 'ui-empty-state';
27
51
  readonly dot: 'ui-dot';
28
52
  readonly dotAccent: 'ui-dot--accent';
29
53
  readonly dotSuccess: 'ui-dot--success';
@@ -33,6 +57,10 @@ export declare const cls: {
33
57
  readonly dotgrid: 'ui-dotgrid';
34
58
  readonly dotgridAccent: 'ui-dotgrid--accent';
35
59
  readonly dotgridDense: 'ui-dotgrid--dense';
60
+ readonly dotmatrix: 'ui-dotmatrix';
61
+ readonly dotmatrixCell: 'ui-dotmatrix__cell';
62
+ readonly dotmatrixCellHot: 'ui-dotmatrix__cell--hot';
63
+ readonly dotmatrixCellAccent: 'ui-dotmatrix__cell--accent';
36
64
  readonly dotfield: 'ui-dotfield';
37
65
  readonly dotrule: 'ui-dotrule';
38
66
  readonly dotbar: 'ui-dotbar';
@@ -145,14 +173,17 @@ export declare const cls: {
145
173
  readonly status: 'ui-status';
146
174
  readonly eyebrow: 'ui-eyebrow';
147
175
  readonly eyebrowMuted: 'ui-eyebrow--muted';
176
+ readonly eyebrowSm: 'ui-eyebrow--sm';
148
177
  readonly prose: 'ui-prose';
149
178
  readonly proseCompact: 'ui-prose--compact';
150
179
  readonly quote: 'ui-quote';
151
180
  readonly quoteCite: 'ui-quote__cite';
152
181
  readonly container: 'ui-container';
153
182
  readonly containerNarrow: 'ui-container--narrow';
183
+ readonly containerWide: 'ui-container--wide';
154
184
  readonly skiplink: 'ui-skiplink';
155
185
  readonly siteheader: 'ui-siteheader';
186
+ readonly siteheaderSticky: 'ui-siteheader--sticky';
156
187
  readonly siteheaderBrand: 'ui-siteheader__brand';
157
188
  readonly siteheaderActions: 'ui-siteheader__actions';
158
189
  readonly sitenav: 'ui-sitenav';
@@ -186,6 +217,7 @@ export declare const cls: {
186
217
  readonly appRailBrand: 'ui-app-rail__brand';
187
218
  readonly appRailToggle: 'ui-app-rail__toggle';
188
219
  readonly appRailFoot: 'ui-app-rail__foot';
220
+ readonly appRailAccount: 'ui-app-rail__account';
189
221
  readonly appTopbar: 'ui-app-topbar';
190
222
  readonly appTopbarTitle: 'ui-app-topbar__title';
191
223
  readonly appToolbar: 'ui-app-toolbar';
@@ -216,19 +248,25 @@ export declare function cx(...parts: ClassValue[]): string;
216
248
  export interface ButtonOpts {
217
249
  variant?: 'ghost' | 'subtle' | 'danger';
218
250
  icon?: boolean;
251
+ size?: 'sm' | 'lg';
219
252
  }
220
253
  export interface CardOpts {
221
254
  accent?: boolean;
222
255
  interactive?: boolean;
223
256
  }
224
257
  export interface BadgeOpts {
225
- tone?: 'accent' | 'success' | 'warning' | 'danger';
258
+ tone?: 'accent' | 'success' | 'warning' | 'danger' | 'muted';
259
+ dot?: boolean;
260
+ }
261
+ export interface NumOpts {
262
+ tone?: 'pos' | 'neg' | 'muted';
226
263
  }
227
264
  export interface ChipOpts {
228
265
  accent?: boolean;
229
266
  }
230
267
  export interface LinkOpts {
231
268
  arrow?: boolean;
269
+ cta?: boolean;
232
270
  }
233
271
  export interface DotOpts {
234
272
  tone?: 'accent' | 'success' | 'warning' | 'danger';
@@ -244,6 +282,7 @@ export interface TableOpts {
244
282
  }
245
283
  export interface EyebrowOpts {
246
284
  muted?: boolean;
285
+ sm?: boolean;
247
286
  }
248
287
  export interface HintOpts {
249
288
  error?: boolean;
@@ -272,6 +311,8 @@ export interface DotbarOpts {
272
311
  }
273
312
  export interface ModalOpts {
274
313
  drawer?: boolean;
314
+ /** Controlled non-dialog usage — adds the is-open state (focus-trap is yours). */
315
+ open?: boolean;
275
316
  }
276
317
  export interface TabOpts {
277
318
  active?: boolean;
@@ -284,6 +325,7 @@ export interface ProseOpts {
284
325
  }
285
326
  export interface ContainerOpts {
286
327
  narrow?: boolean;
328
+ wide?: boolean;
287
329
  }
288
330
  export interface TagOpts {
289
331
  accent?: boolean;
@@ -293,6 +335,7 @@ export interface Ui {
293
335
  button(opts?: ButtonOpts): string;
294
336
  card(opts?: CardOpts): string;
295
337
  badge(opts?: BadgeOpts): string;
338
+ num(opts?: NumOpts): string;
296
339
  chip(opts?: ChipOpts): string;
297
340
  link(opts?: LinkOpts): string;
298
341
  dot(opts?: DotOpts): string;
package/classes/index.js CHANGED
@@ -23,20 +23,35 @@ 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',
29
31
  cardInteractive: 'ui-card--interactive',
32
+ stat: 'ui-stat',
33
+ statgrid: 'ui-statgrid',
34
+ statLabel: 'ui-stat__label',
35
+ statValue: 'ui-stat__value',
36
+ statDelta: 'ui-stat__delta',
37
+ num: 'ui-num',
38
+ numPos: 'ui-num--pos',
39
+ numNeg: 'ui-num--neg',
40
+ numMuted: 'ui-num--muted',
30
41
  badge: 'ui-badge',
31
42
  badgeAccent: 'ui-badge--accent',
32
43
  badgeSuccess: 'ui-badge--success',
33
44
  badgeWarning: 'ui-badge--warning',
34
45
  badgeDanger: 'ui-badge--danger',
46
+ badgeMuted: 'ui-badge--muted',
47
+ badgeDot: 'ui-badge--dot',
35
48
  chip: 'ui-chip',
36
49
  chipAccent: 'ui-chip--accent',
37
50
  link: 'ui-link',
38
51
  linkArrow: 'ui-link--arrow',
52
+ linkCta: 'ui-link--cta',
39
53
  keyValue: 'ui-key-value',
54
+ emptyState: 'ui-empty-state',
40
55
  // dots
41
56
  dot: 'ui-dot',
42
57
  dotAccent: 'ui-dot--accent',
@@ -47,6 +62,10 @@ export const cls = Object.freeze({
47
62
  dotgrid: 'ui-dotgrid',
48
63
  dotgridAccent: 'ui-dotgrid--accent',
49
64
  dotgridDense: 'ui-dotgrid--dense',
65
+ dotmatrix: 'ui-dotmatrix',
66
+ dotmatrixCell: 'ui-dotmatrix__cell',
67
+ dotmatrixCellHot: 'ui-dotmatrix__cell--hot',
68
+ dotmatrixCellAccent: 'ui-dotmatrix__cell--accent',
50
69
  dotfield: 'ui-dotfield',
51
70
  dotrule: 'ui-dotrule',
52
71
  dotbar: 'ui-dotbar',
@@ -166,6 +185,7 @@ export const cls = Object.freeze({
166
185
  // typography / utilities
167
186
  eyebrow: 'ui-eyebrow',
168
187
  eyebrowMuted: 'ui-eyebrow--muted',
188
+ eyebrowSm: 'ui-eyebrow--sm',
169
189
  prose: 'ui-prose',
170
190
  proseCompact: 'ui-prose--compact',
171
191
  quote: 'ui-quote',
@@ -173,8 +193,10 @@ export const cls = Object.freeze({
173
193
  // site shell
174
194
  container: 'ui-container',
175
195
  containerNarrow: 'ui-container--narrow',
196
+ containerWide: 'ui-container--wide',
176
197
  skiplink: 'ui-skiplink',
177
198
  siteheader: 'ui-siteheader',
199
+ siteheaderSticky: 'ui-siteheader--sticky',
178
200
  siteheaderBrand: 'ui-siteheader__brand',
179
201
  siteheaderActions: 'ui-siteheader__actions',
180
202
  sitenav: 'ui-sitenav',
@@ -210,6 +232,7 @@ export const cls = Object.freeze({
210
232
  appRailBrand: 'ui-app-rail__brand',
211
233
  appRailToggle: 'ui-app-rail__toggle',
212
234
  appRailFoot: 'ui-app-rail__foot',
235
+ appRailAccount: 'ui-app-rail__account',
213
236
  appTopbar: 'ui-app-topbar',
214
237
  appTopbarTitle: 'ui-app-topbar__title',
215
238
  appToolbar: 'ui-app-toolbar',
@@ -245,26 +268,37 @@ export function cx(...parts) {
245
268
  const j = (...p) => p.filter(Boolean).join(' ');
246
269
 
247
270
  export const ui = {
248
- button: ({ variant, icon } = {}) =>
271
+ button: ({ variant, icon, size } = {}) =>
249
272
  j(
250
273
  cls.button,
251
274
  variant === 'ghost' && cls.buttonGhost,
252
275
  variant === 'subtle' && cls.buttonSubtle,
253
276
  variant === 'danger' && cls.buttonDanger,
254
277
  icon && cls.buttonIcon,
278
+ size === 'sm' && cls.buttonSm,
279
+ size === 'lg' && cls.buttonLg,
255
280
  ),
256
281
  card: ({ accent, interactive } = {}) =>
257
282
  j(cls.card, accent && cls.cardAccent, interactive && cls.cardInteractive),
258
- badge: ({ tone } = {}) =>
283
+ badge: ({ tone, dot } = {}) =>
259
284
  j(
260
285
  cls.badge,
261
286
  tone === 'accent' && cls.badgeAccent,
262
287
  tone === 'success' && cls.badgeSuccess,
263
288
  tone === 'warning' && cls.badgeWarning,
264
289
  tone === 'danger' && cls.badgeDanger,
290
+ tone === 'muted' && cls.badgeMuted,
291
+ dot && cls.badgeDot,
292
+ ),
293
+ num: ({ tone } = {}) =>
294
+ j(
295
+ cls.num,
296
+ tone === 'pos' && cls.numPos,
297
+ tone === 'neg' && cls.numNeg,
298
+ tone === 'muted' && cls.numMuted,
265
299
  ),
266
300
  chip: ({ accent } = {}) => j(cls.chip, accent && cls.chipAccent),
267
- link: ({ arrow } = {}) => j(cls.link, arrow && cls.linkArrow),
301
+ link: ({ arrow, cta } = {}) => j(cls.link, arrow && cls.linkArrow, cta && cls.linkCta),
268
302
  dot: ({ tone, live } = {}) =>
269
303
  j(
270
304
  cls.dot,
@@ -283,7 +317,7 @@ export const ui = {
283
317
  density === 'comfortable' && cls.tableComfortable,
284
318
  lined && cls.tableLined,
285
319
  ),
286
- eyebrow: ({ muted } = {}) => j(cls.eyebrow, muted && cls.eyebrowMuted),
320
+ eyebrow: ({ muted, sm } = {}) => j(cls.eyebrow, muted && cls.eyebrowMuted, sm && cls.eyebrowSm),
287
321
  hint: ({ error } = {}) => j(cls.hint, error && cls.hintError),
288
322
  cluster: ({ between } = {}) => j(cls.cluster, between && cls.clusterBetween),
289
323
  stagger: ({ auto } = {}) => j(cls.stagger, auto && cls.staggerAuto),
@@ -307,12 +341,13 @@ export const ui = {
307
341
  dotspinner: ({ size } = {}) =>
308
342
  j(cls.dotspinner, size === 'sm' && cls.dotspinnerSm, size === 'lg' && cls.dotspinnerLg),
309
343
  dotbar: ({ indeterminate } = {}) => j(cls.dotbar, indeterminate && cls.dotbarIndeterminate),
310
- modal: ({ drawer } = {}) => j(cls.modal, drawer && cls.modalDrawer),
344
+ modal: ({ drawer, open } = {}) => j(cls.modal, drawer && cls.modalDrawer, open && 'is-open'),
311
345
  tab: ({ active } = {}) => j(cls.tab, active && 'is-active'),
312
346
  avatar: ({ size } = {}) =>
313
347
  j(cls.avatar, size === 'sm' && cls.avatarSm, size === 'lg' && cls.avatarLg),
314
348
  prose: ({ compact } = {}) => j(cls.prose, compact && cls.proseCompact),
315
- container: ({ narrow } = {}) => j(cls.container, narrow && cls.containerNarrow),
349
+ container: ({ narrow, wide } = {}) =>
350
+ j(cls.container, narrow && cls.containerNarrow, wide && cls.containerWide),
316
351
  tag: ({ accent } = {}) => j(cls.tag, accent && cls.tagAccent),
317
352
  };
318
353
 
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,77 +212,23 @@
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
  }
204
219
 
205
- /* --- Metric tiles --- */
206
-
207
- .ui-app-metrics {
208
- display: grid;
209
- gap: var(--space-md);
210
- grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
211
- }
220
+ /* --- Metric tiles ---
221
+ The metric primitive itself is shell-agnostic and now lives in
222
+ primitives.css as `.ui-stat` / `.ui-statgrid`; `.ui-app-metric*` and
223
+ `.ui-app-metrics` remain as permanent admin-shell aliases (grouped on
224
+ the canonical rules there — identical output). Nothing app-specific
225
+ left to define here. */
212
226
 
213
- /* Inside an opt-in .ui-cq container, collapse to one column when the
214
- container (not the viewport) is narrow keeps metric tiles usable
215
- in a slim panel. Inert without .ui-cq, so baselines are unaffected. */
216
- @container bronto (max-width: 30rem) {
217
- .ui-app-metrics {
218
- grid-template-columns: 1fr;
219
- }
220
- }
221
-
222
- .ui-app-metric {
223
- background: var(--panel);
224
- border: 1px solid var(--line);
225
- border-radius: var(--radius-md);
226
- display: grid;
227
- gap: 0.4rem;
228
- padding: var(--space-md);
229
- }
230
-
231
- .ui-app-metric__label {
232
- color: var(--text-dim);
233
- font-family: var(--mono);
234
- font-size: var(--text-2xs);
235
- letter-spacing: var(--tracking-wide);
236
- text-transform: uppercase;
237
- }
238
-
239
- .ui-app-metric__value {
240
- color: var(--text);
241
- font-family: var(--display);
242
- font-size: 1.9rem;
243
- font-variant-numeric: tabular-nums;
244
- letter-spacing: 0.01em;
245
- line-height: 1;
246
- }
247
-
248
- .ui-app-metric__delta {
249
- font-family: var(--mono);
250
- font-size: var(--text-xs);
251
- }
252
-
253
- .ui-app-metric__delta.is-pos {
254
- color: var(--success);
255
- }
256
-
257
- .ui-app-metric__delta.is-neg {
258
- color: var(--danger);
259
- }
260
-
261
- /* --- Empty state --- */
262
-
263
- .ui-app-empty-state {
264
- border: 1px dashed var(--line-strong);
265
- border-radius: var(--radius-md);
266
- color: var(--text-dim);
267
- padding: var(--space-lg);
268
- text-align: center;
269
- }
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. */
270
232
 
271
233
  /* --- Mobile rail collapse --- */
272
234
 
package/css/dots.css CHANGED
@@ -44,6 +44,30 @@
44
44
  inline-size: 100%;
45
45
  }
46
46
 
47
+ /* Data-bound dot matrix — a grid of state cells (activity / heatmap),
48
+ the on-brand counterpart to the decorative .ui-dotgrid background.
49
+ This is the grid + cell tones only; the data→cell mapping is the
50
+ consumer's. Tune density via --dotmatrix-cols / --dotmatrix-gap. */
51
+ .ui-dotmatrix {
52
+ display: grid;
53
+ gap: var(--dotmatrix-gap, 0.5rem);
54
+ grid-template-columns: repeat(var(--dotmatrix-cols, 12), minmax(0, 1fr));
55
+ }
56
+
57
+ .ui-dotmatrix__cell {
58
+ aspect-ratio: 1;
59
+ background: var(--field-dot);
60
+ border-radius: 50%;
61
+ }
62
+
63
+ .ui-dotmatrix__cell--hot {
64
+ background: var(--field-dot-hot);
65
+ }
66
+
67
+ .ui-dotmatrix__cell--accent {
68
+ background: var(--field-dot-accent);
69
+ }
70
+
47
71
  /* Status dot — the glyph-style state indicator. */
48
72
  .ui-dot {
49
73
  background: var(--text-dim);
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);