@ponchia/ui 0.2.2 → 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.
Files changed (48) hide show
  1. package/README.md +108 -79
  2. package/behaviors/index.d.ts +53 -0
  3. package/behaviors/index.js +745 -34
  4. package/classes/index.d.ts +216 -3
  5. package/classes/index.js +85 -10
  6. package/classes/vscode.css-custom-data.json +407 -0
  7. package/css/app.css +53 -44
  8. package/css/base.css +14 -23
  9. package/css/content.css +37 -2
  10. package/css/core.css +5 -5
  11. package/css/disclosure.css +17 -5
  12. package/css/dots.css +1 -1
  13. package/css/feedback.css +87 -2
  14. package/css/forms.css +128 -2
  15. package/css/navigation.css +16 -85
  16. package/css/overlay.css +73 -2
  17. package/css/primitives.css +100 -3
  18. package/css/site.css +295 -0
  19. package/css/table.css +59 -0
  20. package/css/tokens.css +79 -24
  21. package/dist/bronto.css +1 -1
  22. package/dist/css/app.css +1 -0
  23. package/dist/css/base.css +1 -0
  24. package/dist/css/content.css +1 -0
  25. package/dist/css/disclosure.css +1 -0
  26. package/dist/css/dots.css +1 -0
  27. package/dist/css/feedback.css +1 -0
  28. package/dist/css/fonts.css +1 -0
  29. package/dist/css/forms.css +1 -0
  30. package/dist/css/motion.css +1 -0
  31. package/dist/css/navigation.css +1 -0
  32. package/dist/css/overlay.css +1 -0
  33. package/dist/css/primitives.css +1 -0
  34. package/dist/css/site.css +1 -0
  35. package/dist/css/table.css +1 -0
  36. package/dist/css/tokens.css +1 -0
  37. package/package.json +60 -30
  38. package/shiki/nothing.json +83 -0
  39. package/tokens/index.d.ts +18 -10
  40. package/tokens/index.js +49 -16
  41. package/tokens/index.json +98 -32
  42. package/tokens/tokens.dtcg.json +241 -22
  43. package/css/cards.css +0 -336
  44. package/css/index.css +0 -5
  45. package/css/layout.css +0 -219
  46. package/css/responsive.css +0 -157
  47. package/css/typography.css +0 -139
  48. package/dist/bronto-core.css +0 -1
package/css/forms.css CHANGED
@@ -66,7 +66,7 @@
66
66
  .ui-input:focus-visible,
67
67
  .ui-select:focus-visible,
68
68
  .ui-textarea:focus-visible {
69
- outline: 2px solid var(--accent);
69
+ outline: 2px solid var(--focus-ring);
70
70
  outline-offset: 1px;
71
71
  }
72
72
 
@@ -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;
@@ -183,10 +285,34 @@
183
285
  }
184
286
 
185
287
  .ui-switch input:focus-visible + .ui-switch__track {
186
- outline: 2px solid var(--accent);
288
+ outline: 2px solid var(--focus-ring);
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;
@@ -1,15 +1,10 @@
1
1
  /* ==========================================================================
2
- navigation — site nav, menu, theme toggle (Nothing flat, dot indicator)
3
- Class names kept stable for site-nav compatibility.
2
+ navigation — theme toggle (Nothing flat, dot-thumb switch).
3
+ Site nav/menu live in site.css as ui-sitenav / ui-sitemenu; the legacy
4
+ .site-nav / .site-menu vocabulary was removed in 0.3.0.
4
5
  ========================================================================== */
5
6
 
6
- .site-menu {
7
- display: none;
8
- position: relative;
9
- }
10
-
11
- .site-menu__toggle,
12
- .theme-toggle__button {
7
+ .ui-themetoggle__button {
13
8
  align-items: center;
14
9
  background: transparent;
15
10
  border: 1px solid var(--line-strong);
@@ -21,9 +16,10 @@
21
16
  font-size: var(--text-xs);
22
17
  font-weight: 600;
23
18
  gap: 0.4rem;
19
+ justify-content: center;
24
20
  letter-spacing: var(--tracking-wide);
25
- list-style: none;
26
21
  min-block-size: 2rem;
22
+ min-inline-size: 7rem;
27
23
  padding: 0.42rem 0.66rem;
28
24
  text-transform: uppercase;
29
25
  transition:
@@ -33,32 +29,21 @@
33
29
  white-space: nowrap;
34
30
  }
35
31
 
36
- .theme-toggle__button {
37
- justify-content: center;
38
- min-inline-size: 7rem;
39
- }
40
-
41
- .site-menu__toggle::-webkit-details-marker {
42
- display: none;
43
- }
44
-
45
- .site-menu[open] .site-menu__toggle,
46
- .site-menu__toggle:hover,
47
- .theme-toggle__button:hover {
32
+ .ui-themetoggle__button:hover {
48
33
  border-color: var(--accent);
49
- color: var(--accent);
34
+ color: var(--accent-text);
50
35
  }
51
36
 
52
- .theme-toggle__prefix {
37
+ .ui-themetoggle__prefix {
53
38
  color: var(--text-dim);
54
39
  }
55
40
 
56
- .theme-toggle__label {
41
+ .ui-themetoggle__label {
57
42
  color: var(--text);
58
43
  font-weight: 700;
59
44
  }
60
45
 
61
- .theme-toggle__track {
46
+ .ui-themetoggle__track {
62
47
  background: var(--panel-soft);
63
48
  border: 1px solid var(--line-strong);
64
49
  border-radius: var(--radius-pill);
@@ -68,7 +53,7 @@
68
53
  inline-size: 1.5rem;
69
54
  }
70
55
 
71
- .theme-toggle__thumb {
56
+ .ui-themetoggle__thumb {
72
57
  background: var(--text-dim);
73
58
  border-radius: 50%;
74
59
  display: block;
@@ -80,72 +65,18 @@
80
65
  inline-size: 0.56rem;
81
66
  }
82
67
 
83
- [data-theme='dark'] .theme-toggle__thumb {
68
+ [data-theme='dark'] .ui-themetoggle__thumb {
84
69
  background: var(--accent);
85
70
  transform: translateX(0.6rem);
86
71
  }
87
72
 
88
- [dir='rtl'][data-theme='dark'] .theme-toggle__thumb,
89
- [dir='rtl'] [data-theme='dark'] .theme-toggle__thumb {
73
+ [dir='rtl'][data-theme='dark'] .ui-themetoggle__thumb,
74
+ [dir='rtl'] [data-theme='dark'] .ui-themetoggle__thumb {
90
75
  transform: translateX(-0.6rem);
91
76
  }
92
77
 
93
- .site-nav {
94
- display: inline-flex;
95
- gap: 0.15rem;
96
- }
97
-
98
- .site-nav a {
99
- align-items: center;
100
- border: 1px solid transparent;
101
- border-radius: var(--radius-md);
102
- color: var(--text-soft);
103
- display: inline-flex;
104
- font-family: var(--mono);
105
- font-size: var(--text-xs);
106
- gap: 0.45rem;
107
- letter-spacing: var(--tracking-wide);
108
- padding: 0.4rem 0.6rem;
109
- position: relative;
110
- text-transform: uppercase;
111
- transition:
112
- border-color var(--duration-fast) var(--ease-standard),
113
- background-color var(--duration-fast) var(--ease-standard),
114
- color var(--duration-fast) var(--ease-standard);
115
- }
116
-
117
- /* Leading dot is the active/hover indicator — Nothing glyph cue. */
118
- .site-nav a::before {
119
- background: currentColor;
120
- border-radius: 50%;
121
- content: '';
122
- block-size: 0.32rem;
123
- opacity: 0;
124
- transform: scale(0.5);
125
- transition:
126
- opacity var(--duration-fast) var(--ease-standard),
127
- transform var(--duration-fast) var(--ease-spring);
128
- inline-size: 0.32rem;
129
- }
130
-
131
- .site-nav a:hover,
132
- .site-nav a.is-active {
133
- color: var(--text);
134
- }
135
-
136
- .site-nav a.is-active {
137
- color: var(--accent);
138
- }
139
-
140
- .site-nav a:hover::before,
141
- .site-nav a.is-active::before {
142
- opacity: 1;
143
- transform: scale(1);
144
- }
145
-
146
78
  @media (hover: hover) {
147
- .site-menu__toggle:active,
148
- .theme-toggle__button:active {
79
+ .ui-themetoggle__button:active {
149
80
  transform: translateY(1px);
150
81
  }
151
82
  }
package/css/overlay.css CHANGED
@@ -91,12 +91,20 @@
91
91
  @media (hover: hover) {
92
92
  .ui-modal__close:hover {
93
93
  border-color: var(--accent);
94
- color: var(--accent);
94
+ color: var(--accent-text);
95
95
  }
96
96
  }
97
97
 
98
98
  /* --- Dropdown menu — pairs with native <details> --- */
99
99
 
100
+ /* The positioned host for a <details> dropdown: .ui-menu is absolute, so
101
+ its <details> wrapper must establish a containing block. (Replaces the
102
+ role the legacy .site-menu wrapper served, now a first-class ui-*.) */
103
+ .ui-menu-host {
104
+ display: inline-block;
105
+ position: relative;
106
+ }
107
+
100
108
  .ui-menu {
101
109
  background: var(--panel-strong);
102
110
  border: 1px solid var(--line-strong);
@@ -109,7 +117,7 @@
109
117
  position: absolute;
110
118
  inset-inline-end: 0;
111
119
  inset-block-start: calc(100% + 0.4rem);
112
- z-index: 30;
120
+ z-index: var(--z-overlay);
113
121
  }
114
122
 
115
123
  .ui-menu__label {
@@ -164,6 +172,69 @@
164
172
  }
165
173
  }
166
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
+
167
238
  /* --- Mobile: modal becomes a bottom sheet, drawer goes full-width --- */
168
239
 
169
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,
@@ -78,7 +147,7 @@
78
147
  .ui-eyebrow {
79
148
  /* accent-strong, not accent: this is small bold text and must clear
80
149
  WCAG AA 4.5:1 on the soft/tinted surfaces eyebrows sit on too. */
81
- color: var(--accent-strong);
150
+ color: var(--accent-text);
82
151
  font-family: var(--display);
83
152
  font-size: var(--text-xs);
84
153
  font-weight: 700;
@@ -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 {
@@ -203,8 +298,10 @@
203
298
  }
204
299
 
205
300
  .ui-chip--accent {
301
+ /* accent-strong, not accent: chip text is small uppercase mono and must
302
+ clear WCAG AA 4.5:1 — same rule as .ui-eyebrow above. */
303
+ color: var(--accent-text);
206
304
  border-color: var(--accent);
207
- color: var(--accent);
208
305
  }
209
306
 
210
307
  /* --- Badge / status pill --- */
@@ -307,7 +404,7 @@
307
404
  }
308
405
 
309
406
  .ui-link:hover {
310
- color: var(--accent);
407
+ color: var(--accent-text);
311
408
  text-decoration-color: var(--accent);
312
409
  }
313
410