@ponchia/ui 0.13.0 → 0.15.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +4 -4
  3. package/behaviors/command.d.ts +34 -13
  4. package/behaviors/command.d.ts.map +1 -1
  5. package/behaviors/command.js +195 -129
  6. package/classes/classes.json +19 -2
  7. package/classes/index.d.ts +16 -0
  8. package/classes/index.js +21 -2
  9. package/css/blocknote.css +14 -0
  10. package/css/content.css +34 -1
  11. package/css/feedback.css +95 -3
  12. package/css/fonts-inter.css +35 -0
  13. package/css/fonts-jetbrains-mono.css +49 -0
  14. package/css/primitives.css +13 -0
  15. package/css/site.css +0 -31
  16. package/css/tool.css +29 -0
  17. package/dist/bronto.css +1 -1
  18. package/dist/css/blocknote.css +1 -1
  19. package/dist/css/content.css +1 -1
  20. package/dist/css/feedback.css +1 -1
  21. package/dist/css/fonts-inter.css +1 -0
  22. package/dist/css/fonts-jetbrains-mono.css +1 -0
  23. package/dist/css/primitives.css +1 -1
  24. package/dist/css/site.css +1 -1
  25. package/dist/css/tool.css +1 -0
  26. package/docs/command.md +47 -7
  27. package/docs/compositions.md +2 -2
  28. package/docs/interop/blocknote.md +5 -0
  29. package/docs/package-contract.md +6 -1
  30. package/docs/reference.md +12 -1
  31. package/docs/reporting.md +8 -8
  32. package/docs/stability.md +4 -2
  33. package/docs/theming.md +8 -1
  34. package/docs/usage.md +55 -0
  35. package/fonts/OFL-Inter.txt +92 -0
  36. package/fonts/OFL-JetBrainsMono.txt +93 -0
  37. package/fonts/inter-variable-italic.woff2 +0 -0
  38. package/fonts/inter-variable.woff2 +0 -0
  39. package/fonts/jetbrains-mono-400-italic.woff2 +0 -0
  40. package/fonts/jetbrains-mono-400.woff2 +0 -0
  41. package/fonts/jetbrains-mono-700-italic.woff2 +0 -0
  42. package/fonts/jetbrains-mono-700.woff2 +0 -0
  43. package/llms.txt +1 -1
  44. package/package.json +7 -2
@@ -133,6 +133,10 @@ export declare const cls: {
133
133
  readonly alertWarning: 'ui-alert--warning';
134
134
  readonly alertDanger: 'ui-alert--danger';
135
135
  readonly alertInfo: 'ui-alert--info';
136
+ readonly alertBand: 'ui-alert--band';
137
+ readonly bodyState: 'ui-body-state';
138
+ readonly bodyStateError: 'ui-body-state--error';
139
+ readonly bodyStateStale: 'ui-body-state--stale';
136
140
  readonly toastStack: 'ui-toast-stack';
137
141
  readonly toastStackAssertive: 'ui-toast-stack--assertive';
138
142
  readonly toast: 'ui-toast';
@@ -239,6 +243,8 @@ export declare const cls: {
239
243
  readonly eyebrow: 'ui-eyebrow';
240
244
  readonly eyebrowMuted: 'ui-eyebrow--muted';
241
245
  readonly eyebrowSm: 'ui-eyebrow--sm';
246
+ readonly eyebrowCaps: 'ui-eyebrow--caps';
247
+ readonly eyebrowMono: 'ui-eyebrow--mono';
242
248
  readonly prose: 'ui-prose';
243
249
  readonly proseCompact: 'ui-prose--compact';
244
250
  readonly proseBlocks: 'ui-prose--blocks';
@@ -723,6 +729,10 @@ export interface ButtonOpts {
723
729
  export interface EmptyStateOpts {
724
730
  invite?: boolean;
725
731
  }
732
+ export interface BodyStateOpts {
733
+ /** Omit for empty and loading (mark a loading region aria-busy). */
734
+ state?: 'error' | 'stale';
735
+ }
726
736
  export interface ToolstripOpts {
727
737
  variant?: 'floating' | 'compact' | 'pane';
728
738
  anchor?: 'block-start' | 'block-end';
@@ -783,6 +793,10 @@ export interface TableOpts {
783
793
  export interface EyebrowOpts {
784
794
  muted?: boolean;
785
795
  sm?: boolean;
796
+ /** Capitals on the wide tracking step: a label that names a region. */
797
+ caps?: boolean;
798
+ /** The mono face: a label over data, code or a readout. */
799
+ mono?: boolean;
786
800
  }
787
801
  export interface HintOpts {
788
802
  error?: boolean;
@@ -796,6 +810,7 @@ export interface StaggerOpts {
796
810
  export type Tone = 'accent' | 'success' | 'warning' | 'danger' | 'info';
797
811
  export interface AlertOpts {
798
812
  tone?: Tone;
813
+ band?: boolean;
799
814
  }
800
815
  export interface ToastOpts {
801
816
  tone?: Tone;
@@ -1015,6 +1030,7 @@ export interface Ui {
1015
1030
  job(opts?: JobOpts): string;
1016
1031
  originLabel(opts?: OriginLabelOpts): string;
1017
1032
  emptyState(opts?: EmptyStateOpts): string;
1033
+ bodyState(opts?: BodyStateOpts): string;
1018
1034
  toolstrip(opts?: ToolstripOpts): string;
1019
1035
  selectionbar(opts?: SelectionbarOpts): string;
1020
1036
  }
package/classes/index.js CHANGED
@@ -141,6 +141,10 @@ export const cls = Object.freeze({
141
141
  alertWarning: 'ui-alert--warning',
142
142
  alertDanger: 'ui-alert--danger',
143
143
  alertInfo: 'ui-alert--info',
144
+ alertBand: 'ui-alert--band',
145
+ bodyState: 'ui-body-state',
146
+ bodyStateError: 'ui-body-state--error',
147
+ bodyStateStale: 'ui-body-state--stale',
144
148
  toastStack: 'ui-toast-stack',
145
149
  toastStackAssertive: 'ui-toast-stack--assertive',
146
150
  toast: 'ui-toast',
@@ -253,6 +257,8 @@ export const cls = Object.freeze({
253
257
  eyebrow: 'ui-eyebrow',
254
258
  eyebrowMuted: 'ui-eyebrow--muted',
255
259
  eyebrowSm: 'ui-eyebrow--sm',
260
+ eyebrowCaps: 'ui-eyebrow--caps',
261
+ eyebrowMono: 'ui-eyebrow--mono',
256
262
  prose: 'ui-prose',
257
263
  proseCompact: 'ui-prose--compact',
258
264
  proseBlocks: 'ui-prose--blocks',
@@ -875,6 +881,11 @@ const dotTone = (tone) =>
875
881
  tone,
876
882
  );
877
883
 
884
+ // A body state's tone. Empty and loading are the plain state (no option); a
885
+ // loading region says so with aria-busy, not a class.
886
+ const bodyStateTone = (state) =>
887
+ valueClass({ error: cls.bodyStateError, stale: cls.bodyStateStale }, state);
888
+
878
889
  const alertTone = (tone) =>
879
890
  toneClass(
880
891
  'alert',
@@ -1022,11 +1033,18 @@ export const ui = {
1022
1033
  lined && cls.tableLined,
1023
1034
  breakAnywhere && cls.tableBreakAnywhere,
1024
1035
  ),
1025
- eyebrow: ({ muted, sm } = {}) => j(cls.eyebrow, muted && cls.eyebrowMuted, sm && cls.eyebrowSm),
1036
+ eyebrow: ({ muted, sm, caps, mono } = {}) =>
1037
+ j(
1038
+ cls.eyebrow,
1039
+ muted && cls.eyebrowMuted,
1040
+ sm && cls.eyebrowSm,
1041
+ caps && cls.eyebrowCaps,
1042
+ mono && cls.eyebrowMono,
1043
+ ),
1026
1044
  hint: ({ error } = {}) => j(cls.hint, error && cls.hintError),
1027
1045
  cluster: ({ between } = {}) => j(cls.cluster, between && cls.clusterBetween),
1028
1046
  stagger: ({ auto } = {}) => j(cls.stagger, auto && cls.staggerAuto),
1029
- alert: ({ tone } = {}) => j(cls.alert, alertTone(tone)),
1047
+ alert: ({ tone, band } = {}) => j(cls.alert, alertTone(tone), band && cls.alertBand),
1030
1048
  toast: ({ tone } = {}) => j(cls.toast, toastTone(tone)),
1031
1049
  progress: ({ indeterminate } = {}) => j(cls.progress, indeterminate && cls.progressIndeterminate),
1032
1050
  meter: ({ tone } = {}) => j(cls.meter, meterTone(tone)),
@@ -1152,6 +1170,7 @@ export const ui = {
1152
1170
  job: ({ state, compact } = {}) => j(cls.job, jobTone(state), compact && cls.jobCompact),
1153
1171
  originLabel: ({ ai } = {}) => j(cls.originLabel, ai && cls.originLabelAi),
1154
1172
  emptyState: ({ invite } = {}) => j(cls.emptyState, invite && cls.emptyStateInvite),
1173
+ bodyState: ({ state } = {}) => j(cls.bodyState, bodyStateTone(state)),
1155
1174
  toolstrip: ({ variant, anchor } = {}) =>
1156
1175
  j(
1157
1176
  cls.toolstrip,
package/css/blocknote.css CHANGED
@@ -59,3 +59,17 @@
59
59
  --bn-colors-highlights-pink-text: var(--cat-5-ink, var(--danger));
60
60
  --bn-colors-highlights-pink-background: var(--cat-5-tint, var(--panel-soft));
61
61
  }
62
+
63
+ /* The rule beside a nested block. BlockNote draws it in --bn-colors-side-menu,
64
+ the drag handle's ink, which is --text-dim here: a structural hairline as
65
+ dark as a control. The guide takes the border token instead and the handle
66
+ keeps its colour. The selector is BlockNote 0.54's own (packages/react
67
+ editor/styles.css) under .bn-root, so it outranks it in either order. */
68
+ .bn-root
69
+ .bn-block-group
70
+ .bn-block:not(:has(.bn-toggle-wrapper))
71
+ .bn-block-group
72
+ .bn-block-outer:not([data-prev-depth-changed])::before {
73
+ /* stylelint-disable-next-line csstools/use-logical -- BlockNote draws a physical border-left; a logical override would miss it in RTL. */
74
+ border-left-color: var(--line);
75
+ }
package/css/content.css CHANGED
@@ -5,9 +5,42 @@
5
5
  and the document is styled in the Nothing aesthetic while the markup
6
6
  stays semantic and machine-readable (no class-soup). Everything here
7
7
  is an element selector scoped under `.ui-prose`, so it never leaks
8
- into app UI.
8
+ into app UI. The one exception is `.ui-meta`, the date · author line
9
+ that sits beside long-form text, a tool's remarks included (it lived in
10
+ site.css until 0.14, so css/tool.css can keep it without the site shell).
9
11
  ========================================================================== */
10
12
 
13
+ /* --- Meta row — date · author · read-time, dot-separated --- */
14
+
15
+ .ui-meta {
16
+ align-items: center;
17
+ color: var(--text-dim);
18
+ display: flex;
19
+ flex-wrap: wrap;
20
+ font-family: var(--mono);
21
+ font-size: var(--text-2xs);
22
+ gap: 0.55rem;
23
+ letter-spacing: var(--tracking-wide);
24
+ list-style: none;
25
+ margin: 0;
26
+ padding: 0;
27
+ text-transform: uppercase;
28
+ }
29
+
30
+ .ui-meta__item {
31
+ align-items: center;
32
+ display: inline-flex;
33
+ gap: 0.55rem;
34
+ }
35
+
36
+ .ui-meta__item:not(:last-child)::after {
37
+ background: var(--line-strong);
38
+ border-radius: 50%;
39
+ block-size: 0.22rem;
40
+ content: '';
41
+ inline-size: 0.22rem;
42
+ }
43
+
11
44
  .ui-prose {
12
45
  /* A prose block must shrink as a grid/flex item; its pre blocks scroll locally. */
13
46
  min-inline-size: 0;
package/css/feedback.css CHANGED
@@ -79,6 +79,8 @@
79
79
  }
80
80
 
81
81
  .ui-alert--accent {
82
+ --alert-tone: var(--accent);
83
+
82
84
  border-inline-start-color: var(--accent);
83
85
  }
84
86
 
@@ -87,6 +89,8 @@
87
89
  }
88
90
 
89
91
  .ui-alert--success {
92
+ --alert-tone: var(--success);
93
+
90
94
  border-inline-start-color: var(--success);
91
95
  }
92
96
 
@@ -95,6 +99,8 @@
95
99
  }
96
100
 
97
101
  .ui-alert--warning {
102
+ --alert-tone: var(--warning);
103
+
98
104
  border-inline-start-color: var(--warning);
99
105
  }
100
106
 
@@ -103,6 +109,8 @@
103
109
  }
104
110
 
105
111
  .ui-alert--danger {
112
+ --alert-tone: var(--danger);
113
+
106
114
  border-inline-start-color: var(--danger);
107
115
  }
108
116
 
@@ -111,6 +119,8 @@
111
119
  }
112
120
 
113
121
  .ui-alert--info {
122
+ --alert-tone: var(--info);
123
+
114
124
  border-inline-start-color: var(--info);
115
125
  }
116
126
 
@@ -118,6 +128,84 @@
118
128
  background: var(--info);
119
129
  }
120
130
 
131
+ /* --- Band: an alert across the edge of a small surface ---
132
+ A node, panel or card body that must say "this failed" or "this is out of
133
+ date" above content it still shows. Full-bleed, one line of small type, the
134
+ tone as a tint: at 200–400px a boxed alert spends a third of the body on its
135
+ frame. Put it first in the body; the tone modifiers work as on any alert. */
136
+ .ui-alert--band {
137
+ background: color-mix(in oklch, var(--alert-tone, var(--text-dim)) 10%, var(--panel));
138
+ border: 0;
139
+ border-block-end: 1px solid var(--line);
140
+ border-radius: 0;
141
+ font-size: var(--text-2xs);
142
+ gap: var(--space-1-5);
143
+ padding: var(--space-1-5) var(--space-sm);
144
+ }
145
+
146
+ .ui-alert--band::before {
147
+ block-size: 0.4rem;
148
+ inline-size: 0.4rem;
149
+ margin-block-start: 0.3em;
150
+ }
151
+
152
+ /* --- Body state: what a small surface shows instead of its content ---
153
+ Empty, loading, failed or out of date, for a node, panel or card body of
154
+ 200–400px. It fills the body and centres one short sentence (with an
155
+ optional `ui-empty-state__hint` or an action), without the dashed frame a
156
+ page-level `ui-empty-state` draws. Error and stale lead with a tone dot;
157
+ loading takes `aria-busy="true"` on the region and an optional
158
+ `ui-dotspinner` or `ui-skeleton` inside. In a `ui-cq` body narrower than
159
+ 15rem the state tightens to the smallest type. */
160
+ .ui-body-state {
161
+ align-items: center;
162
+ box-sizing: border-box;
163
+ color: var(--text-dim);
164
+ display: flex;
165
+ flex: 1 1 auto;
166
+ flex-direction: column;
167
+ font-size: var(--text-xs);
168
+ gap: var(--space-2xs);
169
+ justify-content: center;
170
+ margin: 0;
171
+ min-block-size: 100%;
172
+ padding: var(--space-md);
173
+ text-align: center;
174
+ text-wrap: balance;
175
+ }
176
+
177
+ .ui-body-state > * {
178
+ margin: 0;
179
+ }
180
+
181
+ .ui-body-state--error,
182
+ .ui-body-state--stale {
183
+ --body-state-tone: var(--danger);
184
+
185
+ color: var(--text-soft);
186
+ }
187
+
188
+ .ui-body-state--stale {
189
+ --body-state-tone: var(--warning);
190
+ }
191
+
192
+ .ui-body-state--error::before,
193
+ .ui-body-state--stale::before {
194
+ background: var(--body-state-tone);
195
+ border-radius: 50%;
196
+ block-size: 0.5rem;
197
+ content: '';
198
+ inline-size: 0.5rem;
199
+ print-color-adjust: exact;
200
+ }
201
+
202
+ @container bronto (max-inline-size: 15rem) {
203
+ .ui-body-state {
204
+ font-size: var(--text-2xs);
205
+ padding: var(--space-xs);
206
+ }
207
+ }
208
+
121
209
  @media (hover: hover) {
122
210
  .ui-alert__close:hover {
123
211
  color: var(--text);
@@ -277,7 +365,9 @@
277
365
  inherited via --tone-glyph so one rule covers both alert and toast. */
278
366
  @media (forced-colors: active) {
279
367
  .ui-alert::before,
280
- .ui-toast::before {
368
+ .ui-toast::before,
369
+ .ui-body-state--error::before,
370
+ .ui-body-state--stale::before {
281
371
  background: none;
282
372
  border-radius: 0;
283
373
  content: var(--tone-glyph, '\2022'); /* • neutral default */
@@ -300,12 +390,14 @@
300
390
  }
301
391
 
302
392
  .ui-alert--warning,
303
- .ui-toast--warning {
393
+ .ui-toast--warning,
394
+ .ui-body-state--stale {
304
395
  --tone-glyph: '\0021'; /* ! */
305
396
  }
306
397
 
307
398
  .ui-alert--danger,
308
- .ui-toast--danger {
399
+ .ui-toast--danger,
400
+ .ui-body-state--error {
309
401
  --tone-glyph: '\2715'; /* ✕ */
310
402
  }
311
403
 
@@ -0,0 +1,35 @@
1
+ /* ==========================================================================
2
+ fonts-inter — Inter, the face `--sans` names first.
3
+
4
+ OPT-IN LEAF. Since 0.10 ordinary text is `--sans`, and the stack names
5
+ Inter first, but nothing shipped it: each OS fell through to its own
6
+ system face, so the same screen set differently on every device. Import
7
+ this leaf and Inter is what renders everywhere.
8
+
9
+ @import '@ponchia/ui/css/fonts-inter.css';
10
+
11
+ One variable face per style covers every weight the tokens use (400 to 700,
12
+ and the in-between ones a heading or a readout asks for). A browser
13
+ downloads a face only when text on the page needs it, so the italic costs
14
+ nothing until something is italic.
15
+
16
+ Inter 4.1 (rsms/inter, web/InterVariable*.woff2), unmodified. License: SIL
17
+ OFL 1.1, fonts/OFL-Inter.txt. URLs are relative to this file; a bundler
18
+ rewrites them and emits the files.
19
+ ========================================================================== */
20
+
21
+ @font-face {
22
+ font-family: Inter;
23
+ font-style: normal;
24
+ font-weight: 100 900;
25
+ font-display: swap;
26
+ src: url('../fonts/inter-variable.woff2') format('woff2');
27
+ }
28
+
29
+ @font-face {
30
+ font-family: Inter;
31
+ font-style: italic;
32
+ font-weight: 100 900;
33
+ font-display: swap;
34
+ src: url('../fonts/inter-variable-italic.woff2') format('woff2');
35
+ }
@@ -0,0 +1,49 @@
1
+ /* ==========================================================================
2
+ fonts-jetbrains-mono — JetBrains Mono, the face `--mono` names first.
3
+
4
+ OPT-IN LEAF. Code, terminals, numbers in tables and readouts are `--mono`,
5
+ and a terminal measures its cells in it, so a fallback monospace changes
6
+ layout as well as looks. Import this leaf and JetBrains Mono is what
7
+ renders everywhere.
8
+
9
+ @import '@ponchia/ui/css/fonts-jetbrains-mono.css';
10
+
11
+ Four faces: regular and bold, upright and italic, the ones terminal output
12
+ uses. A browser downloads a face only when text on the page needs it.
13
+
14
+ JetBrains Mono 2.304 (JetBrains/JetBrainsMono, fonts/webfonts), unmodified.
15
+ License: SIL OFL 1.1, fonts/OFL-JetBrainsMono.txt. URLs are relative to
16
+ this file; a bundler rewrites them and emits the files.
17
+ ========================================================================== */
18
+
19
+ @font-face {
20
+ font-family: 'JetBrains Mono';
21
+ font-style: normal;
22
+ font-weight: 400;
23
+ font-display: swap;
24
+ src: url('../fonts/jetbrains-mono-400.woff2') format('woff2');
25
+ }
26
+
27
+ @font-face {
28
+ font-family: 'JetBrains Mono';
29
+ font-style: normal;
30
+ font-weight: 700;
31
+ font-display: swap;
32
+ src: url('../fonts/jetbrains-mono-700.woff2') format('woff2');
33
+ }
34
+
35
+ @font-face {
36
+ font-family: 'JetBrains Mono';
37
+ font-style: italic;
38
+ font-weight: 400;
39
+ font-display: swap;
40
+ src: url('../fonts/jetbrains-mono-400-italic.woff2') format('woff2');
41
+ }
42
+
43
+ @font-face {
44
+ font-family: 'JetBrains Mono';
45
+ font-style: italic;
46
+ font-weight: 700;
47
+ font-display: swap;
48
+ src: url('../fonts/jetbrains-mono-700-italic.woff2') format('woff2');
49
+ }
@@ -355,6 +355,19 @@
355
355
  font-size: var(--text-2xs);
356
356
  }
357
357
 
358
+ /* The label voice of a tool surface: capitals on the wide tracking step, for a
359
+ section or panel label that names a region rather than introducing prose.
360
+ Composes with --muted, --sm and --mono. */
361
+ .ui-eyebrow--caps {
362
+ letter-spacing: var(--tracking-wide);
363
+ text-transform: uppercase;
364
+ }
365
+
366
+ /* The same label in the mono face, for a label over data, code or a readout. */
367
+ .ui-eyebrow--mono {
368
+ font-family: var(--mono);
369
+ }
370
+
358
371
  /* --- Buttons --- */
359
372
 
360
373
  .ui-button {
package/css/site.css CHANGED
@@ -340,34 +340,3 @@
340
340
  border-color: color-mix(in srgb, var(--accent) 42%, var(--line));
341
341
  color: var(--text-soft);
342
342
  }
343
-
344
- /* --- Meta row — date · author · read-time, dot-separated --- */
345
-
346
- .ui-meta {
347
- align-items: center;
348
- color: var(--text-dim);
349
- display: flex;
350
- flex-wrap: wrap;
351
- font-family: var(--mono);
352
- font-size: var(--text-2xs);
353
- gap: 0.55rem;
354
- letter-spacing: var(--tracking-wide);
355
- list-style: none;
356
- margin: 0;
357
- padding: 0;
358
- text-transform: uppercase;
359
- }
360
-
361
- .ui-meta__item {
362
- align-items: center;
363
- display: inline-flex;
364
- gap: 0.55rem;
365
- }
366
-
367
- .ui-meta__item:not(:last-child)::after {
368
- background: var(--line-strong);
369
- border-radius: 50%;
370
- block-size: 0.22rem;
371
- content: '';
372
- inline-size: 0.22rem;
373
- }
package/css/tool.css ADDED
@@ -0,0 +1,29 @@
1
+ /* ==========================================================================
2
+ tool — the default bundle for a tool, without site and app chrome.
3
+
4
+ Import it INSTEAD of the default bundle, not after it:
5
+
6
+ @import '@ponchia/ui/css/tool.css';
7
+
8
+ Everything in the default bundle except four leaves a tool on its own
9
+ canvas never renders: navigation (the theme toggle), site (the
10
+ marketing/content shell), table (data tables) and app (the admin service
11
+ shell). Tokens, fonts, base, motion, the dot-matrix and glyph primitives,
12
+ content, primitives, rows, forms, feedback, overlay and disclosure stay, in
13
+ the default bundle's cascade order. Need one of the four after all? Import
14
+ its leaf as well (`@ponchia/ui/css/table.css`).
15
+ ========================================================================== */
16
+ @layer bronto;
17
+
18
+ @import url('./tokens.css') layer(bronto);
19
+ @import url('./fonts.css') layer(bronto);
20
+ @import url('./base.css') layer(bronto);
21
+ @import url('./motion.css') layer(bronto);
22
+ @import url('./dots.css') layer(bronto);
23
+ @import url('./content.css') layer(bronto);
24
+ @import url('./primitives.css') layer(bronto);
25
+ @import url('./row.css') layer(bronto);
26
+ @import url('./forms.css') layer(bronto);
27
+ @import url('./feedback.css') layer(bronto);
28
+ @import url('./overlay.css') layer(bronto);
29
+ @import url('./disclosure.css') layer(bronto);