@ponchia/ui 0.13.0 → 0.14.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/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',
@@ -875,6 +879,11 @@ const dotTone = (tone) =>
875
879
  tone,
876
880
  );
877
881
 
882
+ // A body state's tone. Empty and loading are the plain state (no option); a
883
+ // loading region says so with aria-busy, not a class.
884
+ const bodyStateTone = (state) =>
885
+ valueClass({ error: cls.bodyStateError, stale: cls.bodyStateStale }, state);
886
+
878
887
  const alertTone = (tone) =>
879
888
  toneClass(
880
889
  'alert',
@@ -1026,7 +1035,7 @@ export const ui = {
1026
1035
  hint: ({ error } = {}) => j(cls.hint, error && cls.hintError),
1027
1036
  cluster: ({ between } = {}) => j(cls.cluster, between && cls.clusterBetween),
1028
1037
  stagger: ({ auto } = {}) => j(cls.stagger, auto && cls.staggerAuto),
1029
- alert: ({ tone } = {}) => j(cls.alert, alertTone(tone)),
1038
+ alert: ({ tone, band } = {}) => j(cls.alert, alertTone(tone), band && cls.alertBand),
1030
1039
  toast: ({ tone } = {}) => j(cls.toast, toastTone(tone)),
1031
1040
  progress: ({ indeterminate } = {}) => j(cls.progress, indeterminate && cls.progressIndeterminate),
1032
1041
  meter: ({ tone } = {}) => j(cls.meter, meterTone(tone)),
@@ -1152,6 +1161,7 @@ export const ui = {
1152
1161
  job: ({ state, compact } = {}) => j(cls.job, jobTone(state), compact && cls.jobCompact),
1153
1162
  originLabel: ({ ai } = {}) => j(cls.originLabel, ai && cls.originLabelAi),
1154
1163
  emptyState: ({ invite } = {}) => j(cls.emptyState, invite && cls.emptyStateInvite),
1164
+ bodyState: ({ state } = {}) => j(cls.bodyState, bodyStateTone(state)),
1155
1165
  toolstrip: ({ variant, anchor } = {}) =>
1156
1166
  j(
1157
1167
  cls.toolstrip,
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
+ }
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);