@ponchia/ui 0.2.1 → 0.3.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 (48) hide show
  1. package/README.md +128 -54
  2. package/behaviors/index.d.ts +36 -0
  3. package/behaviors/index.js +321 -7
  4. package/classes/index.d.ts +228 -3
  5. package/classes/index.js +139 -4
  6. package/css/app.css +64 -64
  7. package/css/base.css +136 -24
  8. package/css/content.css +324 -0
  9. package/css/core.css +9 -5
  10. package/css/disclosure.css +323 -0
  11. package/css/dots.css +125 -8
  12. package/css/feedback.css +243 -0
  13. package/css/fonts.css +11 -0
  14. package/css/forms.css +41 -14
  15. package/css/motion.css +29 -5
  16. package/css/navigation.css +22 -86
  17. package/css/overlay.css +206 -0
  18. package/css/primitives.css +37 -18
  19. package/css/site.css +295 -0
  20. package/css/table.css +9 -9
  21. package/css/tokens.css +84 -20
  22. package/dist/bronto.css +1 -0
  23. package/dist/css/app.css +1 -0
  24. package/dist/css/base.css +1 -0
  25. package/dist/css/content.css +1 -0
  26. package/dist/css/disclosure.css +1 -0
  27. package/dist/css/dots.css +1 -0
  28. package/dist/css/feedback.css +1 -0
  29. package/dist/css/fonts.css +1 -0
  30. package/dist/css/forms.css +1 -0
  31. package/dist/css/motion.css +1 -0
  32. package/dist/css/navigation.css +1 -0
  33. package/dist/css/overlay.css +1 -0
  34. package/dist/css/primitives.css +1 -0
  35. package/dist/css/site.css +1 -0
  36. package/dist/css/table.css +1 -0
  37. package/dist/css/tokens.css +1 -0
  38. package/package.json +62 -21
  39. package/shiki/nothing.json +83 -0
  40. package/tokens/index.d.ts +18 -10
  41. package/tokens/index.js +16 -14
  42. package/tokens/index.json +32 -28
  43. package/tokens/tokens.dtcg.json +589 -0
  44. package/css/cards.css +0 -336
  45. package/css/index.css +0 -5
  46. package/css/layout.css +0 -219
  47. package/css/responsive.css +0 -157
  48. package/css/typography.css +0 -139
package/css/dots.css CHANGED
@@ -39,9 +39,9 @@
39
39
  background-position: 0 50%;
40
40
  background-size: 9px 9px;
41
41
  border: 0;
42
- height: 3px;
42
+ block-size: 3px;
43
43
  margin: var(--space-md) 0;
44
- width: 100%;
44
+ inline-size: 100%;
45
45
  }
46
46
 
47
47
  /* Status dot — the glyph-style state indicator. */
@@ -50,8 +50,8 @@
50
50
  border-radius: 50%;
51
51
  display: inline-block;
52
52
  flex: 0 0 auto;
53
- height: 0.55rem;
54
- width: 0.55rem;
53
+ block-size: 0.55rem;
54
+ inline-size: 0.55rem;
55
55
  }
56
56
 
57
57
  .ui-dot--accent {
@@ -108,8 +108,8 @@
108
108
  animation: pulseDot 1s var(--ease-standard) infinite;
109
109
  background: var(--accent);
110
110
  border-radius: 50%;
111
- height: 0.42rem;
112
- width: 0.42rem;
111
+ block-size: 0.42rem;
112
+ inline-size: 0.42rem;
113
113
  }
114
114
 
115
115
  .ui-dotloader span:nth-child(2) {
@@ -120,6 +120,83 @@
120
120
  animation-delay: 0.32s;
121
121
  }
122
122
 
123
+ /* Dot spinner — the Nothing-signature loader: a ring of dots, the lit
124
+ one stepping around the circle. Pure CSS: a steps() rotation of the
125
+ container plus a static opacity ramp on the dots reads as a comet.
126
+ Markup: <span class="ui-dotspinner"><i></i> ×8</span>. Size via
127
+ --ds-box / --ds-dot. */
128
+ .ui-dotspinner {
129
+ --ds-box: 1.6rem;
130
+ --ds-dot: 0.26rem;
131
+
132
+ animation: uiSpin 1s steps(8) infinite;
133
+ display: inline-block;
134
+ flex: 0 0 auto;
135
+ block-size: var(--ds-box);
136
+ position: relative;
137
+ inline-size: var(--ds-box);
138
+ }
139
+
140
+ .ui-dotspinner i {
141
+ background: var(--accent);
142
+ border-radius: 50%;
143
+ block-size: var(--ds-dot);
144
+ inset: 0;
145
+ margin: auto;
146
+ position: absolute;
147
+ inline-size: var(--ds-dot);
148
+ }
149
+
150
+ .ui-dotspinner i:nth-child(1) {
151
+ opacity: 1;
152
+ transform: rotate(0deg) translateY(calc(var(--ds-box) / -2 + var(--ds-dot) / 2));
153
+ }
154
+
155
+ .ui-dotspinner i:nth-child(2) {
156
+ opacity: 0.85;
157
+ transform: rotate(45deg) translateY(calc(var(--ds-box) / -2 + var(--ds-dot) / 2));
158
+ }
159
+
160
+ .ui-dotspinner i:nth-child(3) {
161
+ opacity: 0.7;
162
+ transform: rotate(90deg) translateY(calc(var(--ds-box) / -2 + var(--ds-dot) / 2));
163
+ }
164
+
165
+ .ui-dotspinner i:nth-child(4) {
166
+ opacity: 0.56;
167
+ transform: rotate(135deg) translateY(calc(var(--ds-box) / -2 + var(--ds-dot) / 2));
168
+ }
169
+
170
+ .ui-dotspinner i:nth-child(5) {
171
+ opacity: 0.44;
172
+ transform: rotate(180deg) translateY(calc(var(--ds-box) / -2 + var(--ds-dot) / 2));
173
+ }
174
+
175
+ .ui-dotspinner i:nth-child(6) {
176
+ opacity: 0.34;
177
+ transform: rotate(225deg) translateY(calc(var(--ds-box) / -2 + var(--ds-dot) / 2));
178
+ }
179
+
180
+ .ui-dotspinner i:nth-child(7) {
181
+ opacity: 0.25;
182
+ transform: rotate(270deg) translateY(calc(var(--ds-box) / -2 + var(--ds-dot) / 2));
183
+ }
184
+
185
+ .ui-dotspinner i:nth-child(8) {
186
+ opacity: 0.16;
187
+ transform: rotate(315deg) translateY(calc(var(--ds-box) / -2 + var(--ds-dot) / 2));
188
+ }
189
+
190
+ .ui-dotspinner--sm {
191
+ --ds-box: 1.05rem;
192
+ --ds-dot: 0.18rem;
193
+ }
194
+
195
+ .ui-dotspinner--lg {
196
+ --ds-box: 2.6rem;
197
+ --ds-dot: 0.42rem;
198
+ }
199
+
123
200
  /* Dot progress meter — segmented LED bar. */
124
201
  .ui-dotbar {
125
202
  display: flex;
@@ -130,13 +207,46 @@
130
207
  background: var(--field-dot);
131
208
  border-radius: 1px;
132
209
  flex: 1;
133
- height: 0.5rem;
210
+ block-size: 0.5rem;
134
211
  }
135
212
 
136
213
  .ui-dotbar i.is-on {
137
214
  background: var(--accent);
138
215
  }
139
216
 
217
+ /* Indeterminate: the lit cell sweeps left→right across the segments. */
218
+ .ui-dotbar--indeterminate i {
219
+ animation: pulseDot 1.1s var(--ease-standard) infinite;
220
+ }
221
+
222
+ .ui-dotbar--indeterminate i:nth-child(2) {
223
+ animation-delay: 0.09s;
224
+ }
225
+
226
+ .ui-dotbar--indeterminate i:nth-child(3) {
227
+ animation-delay: 0.18s;
228
+ }
229
+
230
+ .ui-dotbar--indeterminate i:nth-child(4) {
231
+ animation-delay: 0.27s;
232
+ }
233
+
234
+ .ui-dotbar--indeterminate i:nth-child(5) {
235
+ animation-delay: 0.36s;
236
+ }
237
+
238
+ .ui-dotbar--indeterminate i:nth-child(6) {
239
+ animation-delay: 0.45s;
240
+ }
241
+
242
+ .ui-dotbar--indeterminate i:nth-child(7) {
243
+ animation-delay: 0.54s;
244
+ }
245
+
246
+ .ui-dotbar--indeterminate i:nth-child(n + 8) {
247
+ animation-delay: 0.63s;
248
+ }
249
+
140
250
  /* Matrix-reveal wrapper — content wipes in left→right on .is-in. */
141
251
  .ui-matrix {
142
252
  clip-path: inset(0 100% 0 0);
@@ -149,10 +259,17 @@
149
259
 
150
260
  @media (prefers-reduced-motion: reduce) {
151
261
  .ui-dot--live::after,
152
- .ui-dotloader span {
262
+ .ui-dotloader span,
263
+ .ui-dotspinner,
264
+ .ui-dotbar--indeterminate i {
153
265
  animation: none;
154
266
  }
155
267
 
268
+ /* Keep the spinner legible while static. */
269
+ .ui-dotspinner i {
270
+ opacity: 0.6;
271
+ }
272
+
156
273
  .ui-matrix {
157
274
  clip-path: none;
158
275
  }
@@ -0,0 +1,243 @@
1
+ /* ==========================================================================
2
+ feedback — alert / toast / tooltip / progress
3
+ Status is carried by a single leading dot + a rationed tone colour,
4
+ never a fill. Nothing-flat, hairline, sharp.
5
+ ========================================================================== */
6
+
7
+ /* --- Alert / callout — inline, dismissible-compatible --- */
8
+
9
+ .ui-alert {
10
+ background: var(--panel-soft);
11
+ border: 1px solid var(--line);
12
+ border-inline-start: 2px solid var(--line-strong);
13
+ border-radius: var(--radius-md);
14
+ color: var(--text-soft);
15
+ display: grid;
16
+ font-size: var(--text-sm);
17
+ gap: 0.3rem;
18
+ grid-template-columns: auto 1fr;
19
+ padding: 0.8rem 0.9rem;
20
+ }
21
+
22
+ /* Leading status glyph — the Nothing state cue. */
23
+ .ui-alert::before {
24
+ background: var(--text-dim);
25
+ border-radius: 50%;
26
+ content: '';
27
+ block-size: 0.5rem;
28
+ margin-block-start: 0.32rem;
29
+ inline-size: 0.5rem;
30
+ }
31
+
32
+ .ui-alert > * {
33
+ grid-column: 2;
34
+ }
35
+
36
+ .ui-alert__title {
37
+ color: var(--text);
38
+ font-family: var(--mono);
39
+ font-size: var(--text-2xs);
40
+ font-weight: 700;
41
+ letter-spacing: var(--tracking-wide);
42
+ margin: 0;
43
+ text-transform: uppercase;
44
+ }
45
+
46
+ .ui-alert__body {
47
+ margin: 0;
48
+ }
49
+
50
+ .ui-alert__dismiss {
51
+ background: transparent;
52
+ border: 0;
53
+ color: var(--text-dim);
54
+ cursor: pointer;
55
+ font-family: var(--mono);
56
+ grid-column: 3;
57
+ grid-row: 1;
58
+ line-height: 1;
59
+ padding: 0.1rem 0.3rem;
60
+ }
61
+
62
+ .ui-alert:has(.ui-alert__dismiss) {
63
+ grid-template-columns: auto 1fr auto;
64
+ }
65
+
66
+ .ui-alert--accent {
67
+ border-inline-start-color: var(--accent);
68
+ }
69
+
70
+ .ui-alert--accent::before {
71
+ background: var(--accent);
72
+ }
73
+
74
+ .ui-alert--success {
75
+ border-inline-start-color: var(--success);
76
+ }
77
+
78
+ .ui-alert--success::before {
79
+ background: var(--success);
80
+ }
81
+
82
+ .ui-alert--warning {
83
+ border-inline-start-color: var(--warning);
84
+ }
85
+
86
+ .ui-alert--warning::before {
87
+ background: var(--warning);
88
+ }
89
+
90
+ .ui-alert--danger {
91
+ border-inline-start-color: var(--danger);
92
+ }
93
+
94
+ .ui-alert--danger::before {
95
+ background: var(--danger);
96
+ }
97
+
98
+ @media (hover: hover) {
99
+ .ui-alert__dismiss:hover {
100
+ color: var(--text);
101
+ }
102
+ }
103
+
104
+ /* --- Toast — transient, stacked, screen-anchored --- */
105
+
106
+ .ui-toast-stack {
107
+ display: grid;
108
+ gap: 0.5rem;
109
+
110
+ /* Logical so the stack anchors bottom inline-end and mirrors in RTL. */
111
+ inset-block: auto 1.25rem;
112
+ inset-inline: auto 1.25rem;
113
+ justify-items: end;
114
+ max-inline-size: min(22rem, calc(100vw - 2rem));
115
+ pointer-events: none;
116
+ position: fixed;
117
+ z-index: 60;
118
+ }
119
+
120
+ .ui-toast {
121
+ align-items: flex-start;
122
+ animation: uiToastIn var(--duration-base) var(--ease-spring) both;
123
+ background: var(--panel-strong);
124
+ border: 1px solid var(--line-strong);
125
+ border-radius: var(--radius-md);
126
+ box-shadow: var(--shadow-raised);
127
+ color: var(--text-soft);
128
+ display: grid;
129
+ font-size: var(--text-sm);
130
+ gap: 0.2rem 0.55rem;
131
+ grid-template-columns: auto 1fr;
132
+ padding: 0.7rem 0.85rem;
133
+ pointer-events: auto;
134
+ inline-size: 100%;
135
+ }
136
+
137
+ .ui-toast::before {
138
+ background: var(--text-dim);
139
+ border-radius: 50%;
140
+ content: '';
141
+ grid-row: 1 / -1;
142
+ block-size: 0.5rem;
143
+ margin-block-start: 0.3rem;
144
+ inline-size: 0.5rem;
145
+ }
146
+
147
+ .ui-toast__title {
148
+ color: var(--text);
149
+ font-family: var(--mono);
150
+ font-size: var(--text-2xs);
151
+ font-weight: 700;
152
+ letter-spacing: var(--tracking-wide);
153
+ margin: 0;
154
+ text-transform: uppercase;
155
+ }
156
+
157
+ .ui-toast--accent::before {
158
+ background: var(--accent);
159
+ }
160
+
161
+ .ui-toast--success::before {
162
+ background: var(--success);
163
+ }
164
+
165
+ .ui-toast--warning::before {
166
+ background: var(--warning);
167
+ }
168
+
169
+ .ui-toast--danger::before {
170
+ background: var(--danger);
171
+ }
172
+
173
+ /* --- Tooltip — CSS-only, hover/focus, no JS --- */
174
+
175
+ .ui-tooltip {
176
+ display: inline-flex;
177
+ position: relative;
178
+ }
179
+
180
+ .ui-tooltip__bubble {
181
+ background: var(--text);
182
+ border-radius: var(--radius-sm);
183
+ inset-block-end: calc(100% + 0.5rem);
184
+ color: var(--bg);
185
+ font-family: var(--mono);
186
+ font-size: var(--text-2xs);
187
+ inset-inline-start: 50%;
188
+ letter-spacing: var(--tracking-wide);
189
+ opacity: 0;
190
+ padding: 0.34rem 0.5rem;
191
+ pointer-events: none;
192
+ position: absolute;
193
+ text-transform: uppercase;
194
+ transform: translate(-50%, 4px);
195
+ transition:
196
+ opacity var(--duration-fast) var(--ease-standard),
197
+ transform var(--duration-fast) var(--ease-standard);
198
+ white-space: nowrap;
199
+ z-index: 50;
200
+ }
201
+
202
+ .ui-tooltip:hover .ui-tooltip__bubble,
203
+ .ui-tooltip:focus-within .ui-tooltip__bubble {
204
+ opacity: 1;
205
+ transform: translate(-50%, 0);
206
+ }
207
+
208
+ /* --- Progress — linear, determinate or indeterminate --- */
209
+
210
+ .ui-progress {
211
+ background: var(--panel-soft);
212
+ border: 1px solid var(--line);
213
+ border-radius: var(--radius-pill);
214
+ block-size: 0.5rem;
215
+ overflow: hidden;
216
+ position: relative;
217
+ inline-size: 100%;
218
+ }
219
+
220
+ .ui-progress__bar {
221
+ background: var(--accent);
222
+ block-size: 100%;
223
+ transition: inline-size var(--duration-base) var(--ease-out);
224
+ inline-size: calc(clamp(0, var(--value, 0), 100) * 1%);
225
+ }
226
+
227
+ .ui-progress--indeterminate .ui-progress__bar {
228
+ animation: uiTrack 1.1s var(--ease-standard) infinite;
229
+ position: absolute;
230
+ inline-size: 45%;
231
+ }
232
+
233
+ @media (prefers-reduced-motion: reduce) {
234
+ .ui-progress--indeterminate .ui-progress__bar {
235
+ animation-duration: 0.01ms;
236
+ inset-inline-start: 0;
237
+ inline-size: 100%;
238
+ }
239
+
240
+ .ui-toast {
241
+ animation: none;
242
+ }
243
+ }
package/css/fonts.css CHANGED
@@ -58,3 +58,14 @@
58
58
  font-display: swap;
59
59
  src: url('../fonts/doto-900.ttf') format('truetype');
60
60
  }
61
+
62
+ /* Data-saver: never *use* Doto, so the browser never fetches it. The
63
+ webfont is only requested when matched to rendered text, so pointing
64
+ --display / --dot-font at the system mono stack opts the whole
65
+ payload out under prefers-reduced-data (same lever as self-hosting). */
66
+ @media (prefers-reduced-data: reduce) {
67
+ :root {
68
+ --display: var(--mono);
69
+ --dot-font: var(--mono);
70
+ }
71
+ }
package/css/forms.css CHANGED
@@ -29,7 +29,7 @@
29
29
  transition:
30
30
  border-color var(--duration-fast) var(--ease-standard),
31
31
  background-color var(--duration-fast) var(--ease-standard);
32
- width: 100%;
32
+ inline-size: 100%;
33
33
  }
34
34
 
35
35
  .ui-input::placeholder,
@@ -38,7 +38,7 @@
38
38
  }
39
39
 
40
40
  .ui-textarea {
41
- min-height: 6rem;
41
+ min-block-size: 6rem;
42
42
  resize: vertical;
43
43
  }
44
44
 
@@ -48,7 +48,12 @@
48
48
  background-position: right 0.75rem center;
49
49
  background-repeat: no-repeat;
50
50
  background-size: 5px 5px;
51
- padding-right: 1.8rem;
51
+ padding-inline-end: 1.8rem;
52
+ }
53
+
54
+ /* background-position has no logical form — mirror the marker for RTL. */
55
+ [dir='rtl'] .ui-select {
56
+ background-position: left 0.75rem center;
52
57
  }
53
58
 
54
59
  .ui-input:focus,
@@ -61,7 +66,7 @@
61
66
  .ui-input:focus-visible,
62
67
  .ui-select:focus-visible,
63
68
  .ui-textarea:focus-visible {
64
- outline: 2px solid var(--accent);
69
+ outline: 2px solid var(--focus-ring);
65
70
  outline-offset: 1px;
66
71
  }
67
72
 
@@ -103,8 +108,8 @@
103
108
  border-radius: 50%;
104
109
  content: '';
105
110
  flex: 0 0 auto;
106
- height: 0.42rem;
107
- width: 0.42rem;
111
+ block-size: 0.42rem;
112
+ inline-size: 0.42rem;
108
113
  }
109
114
 
110
115
  .ui-search:focus-within {
@@ -123,7 +128,7 @@
123
128
  font-size: var(--text-sm);
124
129
  outline: none;
125
130
  padding: 0.6rem 0;
126
- width: 100%;
131
+ inline-size: 100%;
127
132
  }
128
133
 
129
134
  /* Toggle switch — square Nothing-style track. */
@@ -145,22 +150,22 @@
145
150
  border: 1px solid var(--line-strong);
146
151
  border-radius: var(--radius-pill);
147
152
  display: inline-flex;
148
- height: 1.05rem;
153
+ block-size: 1.05rem;
149
154
  padding: 2px;
150
155
  transition:
151
156
  background-color var(--duration-fast) var(--ease-standard),
152
157
  border-color var(--duration-fast) var(--ease-standard);
153
- width: 1.95rem;
158
+ inline-size: 1.95rem;
154
159
  }
155
160
 
156
161
  .ui-switch__thumb {
157
162
  background: var(--text-dim);
158
163
  border-radius: 50%;
159
- height: 100%;
164
+ block-size: 100%;
160
165
  transition:
161
166
  transform var(--duration-fast) var(--ease-spring),
162
167
  background-color var(--duration-fast) var(--ease-standard);
163
- width: 0.78rem;
168
+ inline-size: 0.78rem;
164
169
  }
165
170
 
166
171
  .ui-switch input:checked + .ui-switch__track {
@@ -173,8 +178,12 @@
173
178
  transform: translateX(0.9rem);
174
179
  }
175
180
 
181
+ [dir='rtl'] .ui-switch input:checked + .ui-switch__track .ui-switch__thumb {
182
+ transform: translateX(-0.9rem);
183
+ }
184
+
176
185
  .ui-switch input:focus-visible + .ui-switch__track {
177
- outline: 2px solid var(--accent);
186
+ outline: 2px solid var(--focus-ring);
178
187
  outline-offset: 2px;
179
188
  }
180
189
 
@@ -189,6 +198,24 @@
189
198
 
190
199
  .ui-check input {
191
200
  accent-color: var(--accent);
192
- height: 1rem;
193
- width: 1rem;
201
+ block-size: 1rem;
202
+ inline-size: 1rem;
203
+ }
204
+
205
+ /* Touch: comfortable hit targets on coarse pointers. */
206
+ @media (pointer: coarse) {
207
+ .ui-input,
208
+ .ui-select,
209
+ .ui-textarea {
210
+ min-block-size: 2.9rem;
211
+ }
212
+
213
+ .ui-search {
214
+ min-block-size: 2.9rem;
215
+ }
216
+
217
+ .ui-check input {
218
+ block-size: 1.15rem;
219
+ inline-size: 1.15rem;
220
+ }
194
221
  }
package/css/motion.css CHANGED
@@ -148,6 +148,30 @@
148
148
  }
149
149
  }
150
150
 
151
+ /* Indeterminate progress: a short segment sweeps the track. */
152
+ @keyframes uiTrack {
153
+ 0% {
154
+ inset-inline-start: -45%;
155
+ }
156
+
157
+ 100% {
158
+ inset-inline-start: 100%;
159
+ }
160
+ }
161
+
162
+ /* Toast / overlay entrance: rise + fade, spring-tipped. */
163
+ @keyframes uiToastIn {
164
+ from {
165
+ opacity: 0;
166
+ transform: translateY(12px) scale(0.98);
167
+ }
168
+
169
+ to {
170
+ opacity: 1;
171
+ transform: translateY(0) scale(1);
172
+ }
173
+ }
174
+
151
175
  /* --- Utilities --- */
152
176
 
153
177
  .ui-animate-in {
@@ -229,23 +253,23 @@
229
253
  animation: uiShimmer 1.4s linear infinite;
230
254
  border-radius: var(--radius-sm);
231
255
  color: transparent;
232
- min-height: 0.8em;
256
+ min-block-size: 0.8em;
233
257
  }
234
258
 
235
259
  .ui-spinner {
236
260
  animation: uiSpin 0.7s linear infinite;
237
261
  border: 2px solid var(--line);
238
262
  border-radius: 50%;
239
- border-top-color: var(--accent);
263
+ border-block-start-color: var(--accent);
240
264
  display: inline-block;
241
- height: 1.1rem;
242
- width: 1.1rem;
265
+ block-size: 1.1rem;
266
+ inline-size: 1.1rem;
243
267
  }
244
268
 
245
269
  .ui-caret::after {
246
270
  animation: uiBlink 1.1s steps(1) infinite;
247
271
  content: '▮';
248
- margin-left: 0.1em;
272
+ margin-inline-start: 0.1em;
249
273
  }
250
274
 
251
275
  @media (prefers-reduced-motion: reduce) {