@ponchia/ui 0.3.0 → 0.3.1

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/css/forms.css CHANGED
@@ -92,6 +92,108 @@
92
92
  color: var(--danger);
93
93
  }
94
94
 
95
+ /* --- Input group: prefix/suffix affix around a control --- */
96
+ .ui-input-group {
97
+ display: flex;
98
+ isolation: isolate;
99
+ }
100
+
101
+ .ui-input-group > .ui-input,
102
+ .ui-input-group > .ui-select {
103
+ border-radius: 0;
104
+ flex: 1;
105
+ margin-inline-start: -1px;
106
+ }
107
+
108
+ .ui-input-group > :first-child {
109
+ border-start-start-radius: var(--radius-md);
110
+ border-end-start-radius: var(--radius-md);
111
+ margin-inline-start: 0;
112
+ }
113
+
114
+ .ui-input-group > :last-child {
115
+ border-start-end-radius: var(--radius-md);
116
+ border-end-end-radius: var(--radius-md);
117
+ }
118
+
119
+ .ui-input-group > .ui-input:focus-visible {
120
+ z-index: 1; /* keep the focus ring above the adjacent addon border */
121
+ }
122
+
123
+ .ui-input-group__addon {
124
+ align-items: center;
125
+ background: var(--panel-soft);
126
+ border: 1px solid var(--line);
127
+ color: var(--text-dim);
128
+ display: flex;
129
+ font-size: var(--text-sm);
130
+ padding-inline: var(--space-sm);
131
+ white-space: nowrap;
132
+ }
133
+
134
+ /* --- File input --- */
135
+ .ui-file {
136
+ color: var(--text-soft);
137
+ font-size: var(--text-sm);
138
+ }
139
+
140
+ .ui-file::file-selector-button {
141
+ background: var(--panel-soft);
142
+ border: 1px solid var(--line-strong);
143
+ border-radius: var(--radius-md);
144
+ color: var(--text);
145
+ cursor: pointer;
146
+ font: inherit;
147
+ margin-inline-end: var(--space-sm);
148
+ padding: 0.5rem 0.85rem;
149
+ }
150
+
151
+ .ui-file::file-selector-button:hover {
152
+ border-color: var(--accent);
153
+ }
154
+
155
+ /* --- Range --- */
156
+ .ui-range {
157
+ accent-color: var(--accent);
158
+ inline-size: 100%;
159
+ }
160
+
161
+ .ui-range:focus-visible {
162
+ outline: 2px solid var(--focus-ring);
163
+ outline-offset: 4px;
164
+ }
165
+
166
+ /* --- Error summary: one focusable, assertive list of form errors --- */
167
+ .ui-error-summary {
168
+ border: 1px solid var(--danger);
169
+ border-radius: var(--radius-md);
170
+ color: var(--text);
171
+ padding: var(--space-md);
172
+ }
173
+
174
+ .ui-error-summary[hidden] {
175
+ display: none;
176
+ }
177
+
178
+ .ui-error-summary__title {
179
+ color: var(--danger);
180
+ font-family: var(--display);
181
+ font-size: var(--text-sm);
182
+ font-weight: 700;
183
+ letter-spacing: var(--tracking-wide);
184
+ margin: 0 0 var(--space-xs);
185
+ text-transform: uppercase;
186
+ }
187
+
188
+ .ui-error-summary__list {
189
+ margin: 0;
190
+ padding-inline-start: var(--space-md);
191
+ }
192
+
193
+ .ui-error-summary__list a {
194
+ color: var(--danger);
195
+ }
196
+
95
197
  /* Search field with leading dot. */
96
198
  .ui-search {
97
199
  align-items: center;
@@ -187,6 +289,30 @@
187
289
  outline-offset: 2px;
188
290
  }
189
291
 
292
+ /* ARIA contract: `.ui-switch` is a visually-styled checkbox. Author
293
+ `role="switch"` on the `<input>` so AT announces "switch, on/off"
294
+ rather than "checkbox, checked" — the native `checked` state then
295
+ drives `aria-checked` automatically, so no JS is required. In
296
+ forced-colors mode the colour cues are dropped by the OS, so convey
297
+ on/off with system-colour border + thumb instead. */
298
+ @media (forced-colors: active) {
299
+ .ui-switch__track {
300
+ border-color: ButtonBorder;
301
+ }
302
+
303
+ .ui-switch__thumb {
304
+ background: ButtonText;
305
+ }
306
+
307
+ .ui-switch input:checked + .ui-switch__track {
308
+ border-color: Highlight;
309
+ }
310
+
311
+ .ui-switch input:checked + .ui-switch__track .ui-switch__thumb {
312
+ background: Highlight;
313
+ }
314
+ }
315
+
190
316
  /* Checkbox / radio — square accent. */
191
317
  .ui-check {
192
318
  align-items: center;
package/css/overlay.css CHANGED
@@ -117,7 +117,7 @@
117
117
  position: absolute;
118
118
  inset-inline-end: 0;
119
119
  inset-block-start: calc(100% + 0.4rem);
120
- z-index: 30;
120
+ z-index: var(--z-overlay);
121
121
  }
122
122
 
123
123
  .ui-menu__label {
@@ -172,6 +172,69 @@
172
172
  }
173
173
  }
174
174
 
175
+ /* --- Combobox: an input with a filtered listbox popup (APG pattern,
176
+ wired by initCombobox). Reuses the menu surface tokens. --- */
177
+ .ui-combobox {
178
+ display: inline-block;
179
+ min-inline-size: 14rem;
180
+ position: relative;
181
+ }
182
+
183
+ .ui-combobox__input {
184
+ inline-size: 100%;
185
+ }
186
+
187
+ .ui-combobox__list {
188
+ background: var(--panel-strong);
189
+ border: 1px solid var(--line-strong);
190
+ border-radius: var(--radius-md);
191
+ box-shadow: var(--shadow-raised);
192
+ display: grid;
193
+ gap: 1px;
194
+ list-style: none;
195
+ margin: 0;
196
+ max-block-size: 16rem;
197
+ overflow-y: auto;
198
+ padding: 0.3rem;
199
+ position: absolute;
200
+ inset-block-start: calc(100% + 0.4rem);
201
+ inset-inline: 0;
202
+ z-index: var(--z-overlay);
203
+ }
204
+
205
+ .ui-combobox__list[hidden] {
206
+ display: none;
207
+ }
208
+
209
+ .ui-combobox__option {
210
+ border-radius: var(--radius-sm);
211
+ color: var(--text-soft);
212
+ cursor: pointer;
213
+ font-family: var(--mono);
214
+ font-size: var(--text-sm);
215
+ padding: 0.5rem 0.55rem;
216
+ }
217
+
218
+ .ui-combobox__option[hidden] {
219
+ display: none;
220
+ }
221
+
222
+ .ui-combobox__option.is-active,
223
+ .ui-combobox__option[aria-selected='true'] {
224
+ background: var(--bg-accent);
225
+ color: var(--text);
226
+ }
227
+
228
+ .ui-combobox__empty {
229
+ color: var(--text-dim);
230
+ font-size: var(--text-2xs);
231
+ padding: 0.5rem 0.55rem;
232
+ }
233
+
234
+ .ui-combobox__empty[hidden] {
235
+ display: none;
236
+ }
237
+
175
238
  /* --- Mobile: modal becomes a bottom sheet, drawer goes full-width --- */
176
239
 
177
240
  @media (max-width: 560px) {
@@ -27,6 +27,75 @@
27
27
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr));
28
28
  }
29
29
 
30
+ /* Every-Layout set — the layouts consumers otherwise hand-roll. All
31
+ intrinsic (no media queries): they respond to content + available
32
+ space, so they work the same in a viewport or a container. */
33
+
34
+ /* Sidebar + main; the sidebar is its natural width, main takes the
35
+ rest, and they stack once main would go below --sidebar-min. */
36
+ .ui-sidebar {
37
+ display: flex;
38
+ flex-wrap: wrap;
39
+ gap: var(--sidebar-gap, var(--space-md));
40
+ }
41
+
42
+ .ui-sidebar > :first-child {
43
+ flex-basis: var(--sidebar-width, 16rem);
44
+ flex-grow: 1;
45
+ }
46
+
47
+ .ui-sidebar > :last-child {
48
+ flex-basis: 0;
49
+ flex-grow: 999;
50
+ min-inline-size: var(--sidebar-min, 60%);
51
+ }
52
+
53
+ /* All children in one row until they no longer fit --switcher-min,
54
+ then all switch to a column together (no half-states). */
55
+ .ui-switcher {
56
+ display: flex;
57
+ flex-wrap: wrap;
58
+ gap: var(--switcher-gap, var(--space-md));
59
+ }
60
+
61
+ .ui-switcher > * {
62
+ flex-basis: calc((var(--switcher-min, 24rem) - 100%) * 999);
63
+ flex-grow: 1;
64
+ }
65
+
66
+ /* Centered measure-bounded column. */
67
+ .ui-center {
68
+ box-sizing: content-box;
69
+ margin-inline: auto;
70
+ max-inline-size: var(--center-max, 64rem);
71
+ padding-inline: var(--center-gutter, var(--space-md));
72
+ }
73
+
74
+ /* Intrinsic aspect-ratio box; the media child fills it. */
75
+ .ui-ratio {
76
+ aspect-ratio: var(--ratio, 16 / 9);
77
+ }
78
+
79
+ .ui-ratio > * {
80
+ block-size: 100%;
81
+ inline-size: 100%;
82
+ object-fit: cover;
83
+ }
84
+
85
+ /* Opt-in containment: add .ui-cq to a wrapper to make descendant
86
+ .ui-grid / .ui-app-metrics adapt to THIS box, not the viewport
87
+ (island-safe). Inert until applied, so existing layouts/baselines
88
+ are unaffected. */
89
+ .ui-cq {
90
+ container: var(--cq-name, bronto) / inline-size;
91
+ }
92
+
93
+ @container bronto (max-width: 34rem) {
94
+ .ui-grid {
95
+ --grid-min: 100%;
96
+ }
97
+ }
98
+
30
99
  /* --- Surfaces --- */
31
100
 
32
101
  .ui-surface,
@@ -153,6 +222,32 @@
153
222
  transform: translateY(1px);
154
223
  }
155
224
 
225
+ /* Async/pending state — set aria-busy="true" (and disable) on submit.
226
+ A leading ring spinner is injected with no extra markup; the label
227
+ stays for context. Inert until aria-busy is set, so default buttons
228
+ and visual baselines are unaffected. */
229
+ .ui-button[aria-busy='true'] {
230
+ cursor: progress;
231
+ pointer-events: none;
232
+ }
233
+
234
+ .ui-button[aria-busy='true']::before {
235
+ animation: uiSpin 0.6s linear infinite;
236
+ block-size: 0.9em;
237
+ border: 2px solid currentcolor;
238
+ border-block-start-color: transparent;
239
+ border-radius: var(--radius-pill);
240
+ content: '';
241
+ flex: none;
242
+ inline-size: 0.9em;
243
+ }
244
+
245
+ @media (prefers-reduced-motion: reduce) {
246
+ .ui-button[aria-busy='true']::before {
247
+ animation-duration: 1.4s;
248
+ }
249
+ }
250
+
156
251
  /* --- Link --- */
157
252
 
158
253
  .ui-link {
package/css/site.css CHANGED
@@ -32,7 +32,7 @@
32
32
  inset-inline-start: var(--space-md);
33
33
  padding: 0.7rem 1rem;
34
34
  position: fixed;
35
- z-index: 60;
35
+ z-index: var(--z-toast);
36
36
  }
37
37
 
38
38
  .ui-skiplink:focus {
@@ -182,7 +182,7 @@
182
182
  min-inline-size: 12rem;
183
183
  padding: 0.4rem;
184
184
  position: absolute;
185
- z-index: 30;
185
+ z-index: var(--z-overlay);
186
186
  }
187
187
 
188
188
  /* Below the breakpoint the inline nav collapses into the menu. */
package/css/table.css CHANGED
@@ -100,3 +100,62 @@
100
100
  padding: 2rem;
101
101
  text-align: center;
102
102
  }
103
+
104
+ /* --- Sortable headers (wired by initTableSort) --- */
105
+ .ui-table__sort {
106
+ align-items: center;
107
+ background: none;
108
+ border: 0;
109
+ color: inherit;
110
+ cursor: pointer;
111
+ display: flex;
112
+ font: inherit;
113
+ gap: 0.4rem;
114
+ letter-spacing: inherit;
115
+ padding: 0;
116
+ text-transform: inherit;
117
+ inline-size: 100%;
118
+ }
119
+
120
+ .ui-table__sort::after {
121
+ content: '↕';
122
+ opacity: 0.4;
123
+ }
124
+
125
+ .ui-table th[aria-sort='ascending'] .ui-table__sort::after {
126
+ content: '↑';
127
+ opacity: 1;
128
+ }
129
+
130
+ .ui-table th[aria-sort='descending'] .ui-table__sort::after {
131
+ content: '↓';
132
+ opacity: 1;
133
+ }
134
+
135
+ /* --- Row selection --- */
136
+ .ui-table__select {
137
+ inline-size: 1px; /* shrink-to-fit checkbox column */
138
+ white-space: nowrap;
139
+ }
140
+
141
+ .ui-table--selectable tbody tr[aria-selected='true'] td {
142
+ background: var(--bg-accent);
143
+ }
144
+
145
+ /* --- Bulk-action toolbar --- */
146
+ .ui-table__toolbar {
147
+ align-items: center;
148
+ border: 1px solid var(--line);
149
+ border-block-end: 0;
150
+ border-start-start-radius: var(--radius-md);
151
+ border-start-end-radius: var(--radius-md);
152
+ display: flex;
153
+ gap: var(--space-sm);
154
+ padding: var(--space-sm) var(--space-md);
155
+ }
156
+
157
+ /* --- Loading state: set aria-busy + .ui-table--loading on the wrap --- */
158
+ .ui-table--loading {
159
+ opacity: 0.6;
160
+ pointer-events: none;
161
+ }
package/css/tokens.css CHANGED
@@ -52,6 +52,54 @@
52
52
  --dot-size: 2px;
53
53
  --dot-gap: 14px;
54
54
 
55
+ /* Stacking-context scale. Replaces scattered magic z-indexes so
56
+ consumers can reason about (and override) layering coherently.
57
+ Values match the prior literals — purely a refactor, no visual
58
+ change. */
59
+ --z-base: 0;
60
+ --z-raised: 10;
61
+ --z-sticky: 20;
62
+ --z-overlay: 30;
63
+ --z-popover: 50;
64
+ --z-toast: 60;
65
+
66
+ /* Accent ramp — a stepped family for charts / data-viz / multi-state
67
+ surfaces (the use case tokens/index.js advertises). Derived from the
68
+ single --accent knob via color-mix against the theme bg, so it
69
+ re-brands and theme-adapts automatically; no per-theme duplication. */
70
+ --accent-1: color-mix(in srgb, var(--accent) 8%, var(--bg));
71
+ --accent-2: color-mix(in srgb, var(--accent) 16%, var(--bg));
72
+ --accent-3: color-mix(in srgb, var(--accent) 32%, var(--bg));
73
+ --accent-4: color-mix(in srgb, var(--accent) 60%, var(--bg));
74
+ --accent-5: var(--accent);
75
+ --accent-6: var(--accent-strong);
76
+
77
+ /* Neutral surface ramp (low → high contrast against --bg). */
78
+ --surface-1: var(--bg);
79
+ --surface-2: var(--bg-elevated);
80
+ --surface-3: var(--panel);
81
+ --surface-4: var(--panel-soft);
82
+ --surface-5: var(--line);
83
+ --surface-6: var(--line-strong);
84
+
85
+ /* Semantic tier — stable, prefixed aliases over the (shorter,
86
+ legacy) primitive names. New consumer code should target these:
87
+ re-skinning one role no longer means chasing component internals.
88
+ The short names below stay as permanent back-compat aliases. */
89
+ --bronto-color-bg: var(--bg);
90
+ --bronto-color-surface: var(--panel);
91
+ --bronto-color-surface-raised: var(--panel-strong);
92
+ --bronto-color-border: var(--line);
93
+ --bronto-color-border-strong: var(--line-strong);
94
+ --bronto-color-text: var(--text);
95
+ --bronto-color-text-muted: var(--text-dim);
96
+ --bronto-color-action: var(--accent);
97
+ --bronto-color-on-action: var(--button-text);
98
+ --bronto-color-focus: var(--focus-ring);
99
+ --bronto-color-success: var(--success);
100
+ --bronto-color-warning: var(--warning);
101
+ --bronto-color-danger: var(--danger);
102
+
55
103
  /* Aliases kept for back-compat with existing semantic classes */
56
104
  --surface: var(--panel);
57
105
  --surface-raised: var(--panel-strong);
@@ -83,17 +131,17 @@
83
131
  --accent-text: var(--accent-strong);
84
132
  --accent-soft: color-mix(in srgb, var(--accent) 10%, transparent);
85
133
  --success: #2f7d4f;
86
- --success-soft: rgba(47, 125, 79, 0.12);
134
+ --success-soft: rgb(47, 125, 79, 0.12);
87
135
  --warning: #806414;
88
- --warning-soft: rgba(128, 100, 20, 0.13);
136
+ --warning-soft: rgb(128, 100, 20, 0.13);
89
137
  --orange: #a85f32;
90
- --orange-soft: rgba(168, 95, 50, 0.13);
138
+ --orange-soft: rgb(168, 95, 50, 0.13);
91
139
  --danger: #c01622;
92
- --danger-soft: rgba(192, 22, 34, 0.1);
93
- --code-bg: rgba(10, 10, 10, 0.05);
140
+ --danger-soft: rgb(192, 22, 34, 0.1);
141
+ --code-bg: rgb(10, 10, 10, 0.05);
94
142
  --button-text: #ffffff;
95
- --field-dot: rgba(10, 10, 10, 0.16);
96
- --field-dot-hot: rgba(10, 10, 10, 0.4);
143
+ --field-dot: rgb(10, 10, 10, 0.16);
144
+ --field-dot-hot: rgb(10, 10, 10, 0.4);
97
145
  --field-dot-accent: color-mix(in srgb, var(--accent) 78%, transparent);
98
146
 
99
147
  /* Solid by default: this token drives every focus outline, so it must
@@ -134,17 +182,17 @@
134
182
  --accent-text: var(--accent-strong);
135
183
  --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
136
184
  --success: #4ec27e;
137
- --success-soft: rgba(78, 194, 126, 0.14);
185
+ --success-soft: rgb(78, 194, 126, 0.14);
138
186
  --warning: #d8bd72;
139
- --warning-soft: rgba(216, 189, 114, 0.14);
187
+ --warning-soft: rgb(216, 189, 114, 0.14);
140
188
  --orange: #d08c5b;
141
- --orange-soft: rgba(208, 140, 91, 0.15);
189
+ --orange-soft: rgb(208, 140, 91, 0.15);
142
190
  --danger: #ff4d54;
143
- --danger-soft: rgba(255, 77, 84, 0.15);
144
- --code-bg: rgba(255, 255, 255, 0.05);
191
+ --danger-soft: rgb(255, 77, 84, 0.15);
192
+ --code-bg: rgb(255, 255, 255, 0.05);
145
193
  --button-text: #000000;
146
- --field-dot: rgba(242, 242, 242, 0.14);
147
- --field-dot-hot: rgba(242, 242, 242, 0.36);
194
+ --field-dot: rgb(242, 242, 242, 0.14);
195
+ --field-dot-hot: rgb(242, 242, 242, 0.36);
148
196
  --field-dot-accent: color-mix(in srgb, var(--accent) 82%, transparent);
149
197
  --focus-ring: var(--accent);
150
198
  --shadow: none;
@@ -171,17 +219,17 @@
171
219
  --accent-text: var(--accent-strong);
172
220
  --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
173
221
  --success: #4ec27e;
174
- --success-soft: rgba(78, 194, 126, 0.14);
222
+ --success-soft: rgb(78, 194, 126, 0.14);
175
223
  --warning: #d8bd72;
176
- --warning-soft: rgba(216, 189, 114, 0.14);
224
+ --warning-soft: rgb(216, 189, 114, 0.14);
177
225
  --orange: #d08c5b;
178
- --orange-soft: rgba(208, 140, 91, 0.15);
226
+ --orange-soft: rgb(208, 140, 91, 0.15);
179
227
  --danger: #ff4d54;
180
- --danger-soft: rgba(255, 77, 84, 0.15);
181
- --code-bg: rgba(255, 255, 255, 0.05);
228
+ --danger-soft: rgb(255, 77, 84, 0.15);
229
+ --code-bg: rgb(255, 255, 255, 0.05);
182
230
  --button-text: #000000;
183
- --field-dot: rgba(242, 242, 242, 0.14);
184
- --field-dot-hot: rgba(242, 242, 242, 0.36);
231
+ --field-dot: rgb(242, 242, 242, 0.14);
232
+ --field-dot-hot: rgb(242, 242, 242, 0.36);
185
233
  --field-dot-accent: color-mix(in srgb, var(--accent) 82%, transparent);
186
234
  --focus-ring: var(--accent);
187
235
  --shadow: none;