@ps1ui/core 0.0.1 → 0.2.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 (34) hide show
  1. package/README.md +49 -0
  2. package/dist/base.css +56 -0
  3. package/dist/components/Anchor/Anchor.d.mts +2 -0
  4. package/dist/components/Anchor/Anchor.mjs.map +1 -1
  5. package/dist/components/Button/Button.d.mts +7 -1
  6. package/dist/components/Button/Button.mjs.map +1 -1
  7. package/dist/components/Checkbox/Checkbox.d.mts +1 -0
  8. package/dist/components/Checkbox/Checkbox.mjs.map +1 -1
  9. package/dist/components/CodeBlock/CodeBlock.d.mts +3 -0
  10. package/dist/components/CodeBlock/CodeBlock.mjs.map +1 -1
  11. package/dist/components/Container/Container.d.mts +8 -0
  12. package/dist/components/Container/Container.mjs.map +1 -1
  13. package/dist/components/Details/Details.d.mts +1 -0
  14. package/dist/components/Details/Details.mjs.map +1 -1
  15. package/dist/components/Grid/Grid.d.mts +8 -0
  16. package/dist/components/Grid/Grid.mjs.map +1 -1
  17. package/dist/components/GridItem/GridItem.d.mts +4 -0
  18. package/dist/components/GridItem/GridItem.mjs.map +1 -1
  19. package/dist/components/Heading/Heading.d.mts +4 -0
  20. package/dist/components/Heading/Heading.mjs.map +1 -1
  21. package/dist/components/List/List.d.mts +2 -0
  22. package/dist/components/List/List.mjs.map +1 -1
  23. package/dist/components/Stack/Stack.d.mts +14 -0
  24. package/dist/components/Stack/Stack.mjs.map +1 -1
  25. package/dist/components/Text/Text.d.mts +9 -1
  26. package/dist/components/Text/Text.mjs.map +1 -1
  27. package/dist/components/Textarea/Textarea.d.mts +7 -0
  28. package/dist/components/Textarea/Textarea.mjs +14 -0
  29. package/dist/components/Textarea/Textarea.mjs.map +1 -0
  30. package/dist/components.css +85 -159
  31. package/dist/index.d.mts +2 -1
  32. package/dist/index.mjs +2 -1
  33. package/dist/styles.css +206 -7
  34. package/package.json +4 -4
package/dist/styles.css CHANGED
@@ -19,6 +19,17 @@
19
19
  --ps1ui-color-primary-active: #3fb950;
20
20
  --ps1ui-color-primary-fg: #0b0f14;
21
21
  --ps1ui-color-accent: #ffa657;
22
+ /* Destructive / error signalling. Hue sits in the rose-coral band the code
23
+ palette already inhabits (--ps1ui-code-keyword / -tag / -deleted) so the
24
+ surface reads as one system; luminance is tuned so the base color hits
25
+ ≥ 4.5:1 vs BOTH --ps1ui-color-bg and --ps1ui-color-surface (verified by
26
+ Text.contrast.test.tsx) and danger-fg on each danger-* fill hits the
27
+ same threshold (verified by Button.contrast.test.tsx). Hover/active
28
+ progressively darken while staying above contrast. */
29
+ --ps1ui-color-danger: #ff7d8d;
30
+ --ps1ui-color-danger-hover: #f0637a;
31
+ --ps1ui-color-danger-active: #e04a60;
32
+ --ps1ui-color-danger-fg: #0b0f14;
22
33
  --ps1ui-color-focus-ring-soft: color-mix(in srgb, var(--ps1ui-color-primary) 14%, transparent);
23
34
  --ps1ui-shadow-focus-ring: 0 0 0 3px var(--ps1ui-color-focus-ring-soft);
24
35
 
@@ -88,6 +99,7 @@
88
99
 
89
100
  --ps1ui-transition-fast: 120ms ease;
90
101
  }
102
+
91
103
  /* ==========================================================================
92
104
  * Reset
93
105
  * --------------------------------------------------------------------------
@@ -107,6 +119,7 @@
107
119
  * layouts unless you set display:inline explicitly), <ul>/<ol> lose their
108
120
  * bullets. Locked in by src/styles/reset.test.tsx.
109
121
  * ========================================================================== */
122
+
110
123
  /* Box model + border baseline. Applied to `*` only — NOT to ::before/::after
111
124
  /::backdrop/::file-selector-button. Pseudo-elements with border-drawn
112
125
  glyphs (the classic rotated-L checkmark, tooltip carets, radio-dot rings)
@@ -120,6 +133,7 @@
120
133
  box-sizing: border-box;
121
134
  border: 0 solid;
122
135
  }
136
+
123
137
  body,
124
138
  blockquote,
125
139
  dl,
@@ -140,6 +154,7 @@ pre,
140
154
  ul {
141
155
  margin: 0;
142
156
  }
157
+
143
158
  fieldset,
144
159
  legend,
145
160
  menu,
@@ -147,6 +162,7 @@ ol,
147
162
  ul {
148
163
  padding: 0;
149
164
  }
165
+
150
166
  html {
151
167
  color-scheme: dark;
152
168
  line-height: 1.5;
@@ -157,11 +173,13 @@ html {
157
173
  tab-size: 4;
158
174
  text-rendering: optimizeLegibility;
159
175
  }
176
+
160
177
  body {
161
178
  min-height: 100dvh;
162
179
  -webkit-font-smoothing: antialiased;
163
180
  -moz-osx-font-smoothing: grayscale;
164
181
  }
182
+
165
183
  /* Heading responsibility belongs to <Heading>; raw h1..h6 inherit so a bare
166
184
  <h1> outside <Heading> doesn't blow up to the UA default. */
167
185
  h1,
@@ -175,15 +193,18 @@ h6 {
175
193
  text-wrap: balance;
176
194
  overflow-wrap: break-word;
177
195
  }
196
+
178
197
  p {
179
198
  text-wrap: pretty;
180
199
  overflow-wrap: break-word;
181
200
  }
201
+
182
202
  ol,
183
203
  ul,
184
204
  menu {
185
205
  list-style: none;
186
206
  }
207
+
187
208
  img,
188
209
  svg,
189
210
  video,
@@ -195,17 +216,20 @@ object {
195
216
  display: block;
196
217
  vertical-align: middle;
197
218
  }
219
+
198
220
  img,
199
221
  video {
200
222
  max-width: 100%;
201
223
  height: auto;
202
224
  }
225
+
203
226
  /* Anchor responsibility belongs to <Anchor>; raw <a> inherits color/decoration
204
227
  so it doesn't paint as UA blue underline against the dark canvas. */
205
228
  a {
206
229
  color: inherit;
207
230
  text-decoration: inherit;
208
231
  }
232
+
209
233
  /* UA still refuses to inherit font/color into form controls in 2026.
210
234
  Inherit font-feature-settings / font-variation-settings too so JetBrains
211
235
  Mono Variable's axes stay live inside <input> / <textarea>. */
@@ -224,13 +248,16 @@ textarea,
224
248
  border-radius: 0;
225
249
  opacity: 1;
226
250
  }
251
+
227
252
  :where(select:is([multiple], [size])) optgroup {
228
253
  font-weight: bolder;
229
254
  }
255
+
230
256
  button,
231
257
  select {
232
258
  text-transform: none;
233
259
  }
260
+
234
261
  button,
235
262
  input:where([type="button"], [type="reset"], [type="submit"]),
236
263
  ::file-selector-button {
@@ -238,12 +265,15 @@ input:where([type="button"], [type="reset"], [type="submit"]),
238
265
  appearance: button;
239
266
  -webkit-appearance: button;
240
267
  }
268
+
241
269
  button {
242
270
  cursor: pointer;
243
271
  }
272
+
244
273
  textarea {
245
274
  resize: vertical;
246
275
  }
276
+
247
277
  /* `in srgb` for consistency with the library's other color-mix calls. The
248
278
  space is irrelevant when mixing with `transparent` (premultiplied-alpha
249
279
  interpolation, CSS Color 4), so diverging from Tailwind Preflight's oklab
@@ -256,23 +286,28 @@ textarea {
256
286
  opacity: 1;
257
287
  color: color-mix(in srgb, currentcolor 55%, transparent);
258
288
  }
289
+
259
290
  ::-webkit-date-and-time-value {
260
291
  min-height: 1lh;
261
292
  text-align: inherit;
262
293
  }
294
+
263
295
  ::-webkit-datetime-edit {
264
296
  display: inline-flex;
265
297
  padding: 0;
266
298
  }
299
+
267
300
  ::-webkit-datetime-edit-fields-wrapper {
268
301
  padding: 0;
269
302
  }
303
+
270
304
  ::-webkit-inner-spin-button,
271
305
  ::-webkit-outer-spin-button {
272
306
  -webkit-appearance: none;
273
307
  margin: 0;
274
308
  height: auto;
275
309
  }
310
+
276
311
  /* number spinner: Chromium is already handled by ::-webkit-inner/outer-spin-button
277
312
  { appearance: none } above. Firefox needs its own vendor prefix — the unprefixed
278
313
  `appearance: textfield` is deprecated per CSS UI Level 4 (moved to compatibility
@@ -280,27 +315,34 @@ textarea {
280
315
  input[type="number"] {
281
316
  -moz-appearance: textfield;
282
317
  }
318
+
283
319
  [type="search"] {
284
320
  -webkit-appearance: textfield;
285
321
  outline-offset: -2px;
286
322
  }
323
+
287
324
  ::-webkit-search-decoration,
288
325
  ::-webkit-search-cancel-button {
289
326
  -webkit-appearance: none;
290
327
  }
328
+
291
329
  :-moz-ui-invalid {
292
330
  box-shadow: none;
293
331
  }
332
+
294
333
  :-moz-focusring:where(:not(iframe)) {
295
334
  outline: auto;
296
335
  }
336
+
297
337
  b,
298
338
  strong {
299
339
  font-weight: bolder;
300
340
  }
341
+
301
342
  small {
302
343
  font-size: 80%;
303
344
  }
345
+
304
346
  code,
305
347
  kbd,
306
348
  samp,
@@ -308,14 +350,17 @@ pre {
308
350
  font-family: var(--ps1ui-font-mono);
309
351
  font-size: 1em;
310
352
  }
353
+
311
354
  abbr:where([title]) {
312
355
  -webkit-text-decoration: underline dotted;
313
356
  text-decoration: underline dotted;
314
357
  }
358
+
315
359
  q::before,
316
360
  q::after {
317
361
  content: "";
318
362
  }
363
+
319
364
  /* Keeps sub/sup from expanding the parent line-height. */
320
365
  sub,
321
366
  sup {
@@ -324,34 +369,42 @@ sup {
324
369
  position: relative;
325
370
  vertical-align: baseline;
326
371
  }
372
+
327
373
  sub {
328
374
  bottom: -0.25em;
329
375
  }
376
+
330
377
  sup {
331
378
  top: -0.5em;
332
379
  }
380
+
333
381
  table {
334
382
  text-indent: 0;
335
383
  border-color: inherit;
336
384
  border-collapse: collapse;
337
385
  }
386
+
338
387
  hr {
339
388
  height: 0;
340
389
  color: inherit;
341
390
  border-top: 1px solid;
342
391
  }
392
+
343
393
  summary {
344
394
  display: list-item;
345
395
  }
396
+
346
397
  /* [hidden] would lose to `display: flex` etc. without this. */
347
398
  [hidden]:where(:not([hidden="until-found"])) {
348
399
  display: none !important;
349
400
  }
401
+
350
402
  @media (prefers-reduced-motion: no-preference) {
351
403
  html {
352
404
  interpolate-size: allow-keywords;
353
405
  }
354
406
  }
407
+
355
408
  /* ==========================================================================
356
409
  * Ambient environment
357
410
  * --------------------------------------------------------------------------
@@ -363,15 +416,18 @@ summary {
363
416
  * area would reveal the light UA default and flash white on scroll. `color`
364
417
  * and `font-family` cascade down to `body` and descendants naturally.
365
418
  * ========================================================================== */
419
+
366
420
  html {
367
421
  background: var(--ps1ui-color-bg);
368
422
  color: var(--ps1ui-color-fg);
369
423
  font-family: var(--ps1ui-font-mono);
370
424
  }
425
+
371
426
  ::-moz-selection {
372
427
  background: var(--ps1ui-color-primary);
373
428
  color: var(--ps1ui-color-primary-fg);
374
429
  }
430
+
375
431
  ::selection {
376
432
  background: var(--ps1ui-color-primary);
377
433
  color: var(--ps1ui-color-primary-fg);
@@ -418,12 +474,14 @@ html {
418
474
  color var(--ps1ui-transition-fast),
419
475
  text-decoration-color var(--ps1ui-transition-fast);
420
476
  }
477
+
421
478
  /* outline (not box-shadow) so a wrapping inline anchor renders as connected fragments
422
479
  per line-box instead of two disjoint rectangles. Modern browsers follow border-radius. */
423
480
  .ps1ui-anchor:focus-visible {
424
481
  outline: 2px solid var(--ps1ui-color-primary);
425
482
  outline-offset: 2px;
426
483
  }
484
+
427
485
  /* text-decoration-color defaults to currentColor, so setting only `color` per state
428
486
  also shifts the underline. subtle's default intentionally decouples the two — the
429
487
  underline is dimmer than the text — hence the explicit override there. */
@@ -436,6 +494,7 @@ html {
436
494
  .ps1ui-anchor--primary:active {
437
495
  color: var(--ps1ui-color-primary-active);
438
496
  }
497
+
439
498
  .ps1ui-anchor--subtle {
440
499
  color: inherit;
441
500
  text-decoration-color: var(--ps1ui-color-fg-subtle);
@@ -477,14 +536,17 @@ html {
477
536
  border-color var(--ps1ui-transition-fast),
478
537
  color var(--ps1ui-transition-fast);
479
538
  }
539
+
480
540
  .ps1ui-button:focus-visible {
481
541
  outline: none;
482
542
  border-color: var(--ps1ui-color-primary);
483
543
  box-shadow: var(--ps1ui-shadow-focus-ring);
484
544
  }
545
+
485
546
  .ps1ui-button:disabled {
486
547
  cursor: not-allowed;
487
548
  }
549
+
488
550
  .ps1ui-button--primary {
489
551
  background: var(--ps1ui-color-primary);
490
552
  color: var(--ps1ui-color-primary-fg);
@@ -503,6 +565,7 @@ html {
503
565
  color: var(--ps1ui-color-fg-subtle);
504
566
  border-color: var(--ps1ui-color-border);
505
567
  }
568
+
506
569
  .ps1ui-button--secondary {
507
570
  background: transparent;
508
571
  color: var(--ps1ui-color-fg);
@@ -524,6 +587,28 @@ html {
524
587
  color: var(--ps1ui-color-fg-subtle);
525
588
  border-color: var(--ps1ui-color-border);
526
589
  }
590
+
591
+ /* Same filled shape as --primary — variant only shifts hue, not typographic
592
+ weight or footprint, so a danger button reads as an equal-weight sibling
593
+ of a primary one and the visual difference is purely semantic colour. */
594
+ .ps1ui-button--danger {
595
+ background: var(--ps1ui-color-danger);
596
+ color: var(--ps1ui-color-danger-fg);
597
+ border-color: var(--ps1ui-color-danger);
598
+ }
599
+ .ps1ui-button--danger:hover:not(:disabled) {
600
+ background: var(--ps1ui-color-danger-hover);
601
+ border-color: var(--ps1ui-color-danger-hover);
602
+ }
603
+ .ps1ui-button--danger:active:not(:disabled) {
604
+ background: var(--ps1ui-color-danger-active);
605
+ border-color: var(--ps1ui-color-danger-active);
606
+ }
607
+ .ps1ui-button--danger:disabled {
608
+ background: transparent;
609
+ color: var(--ps1ui-color-fg-subtle);
610
+ border-color: var(--ps1ui-color-border);
611
+ }
527
612
  .ps1ui-checkbox {
528
613
  -moz-appearance: none;
529
614
  appearance: none;
@@ -548,29 +633,35 @@ html {
548
633
  border-color var(--ps1ui-transition-fast),
549
634
  box-shadow var(--ps1ui-transition-fast);
550
635
  }
636
+
551
637
  .ps1ui-checkbox:hover:not(:disabled) {
552
638
  border-color: var(--ps1ui-color-primary);
553
639
  }
640
+
554
641
  .ps1ui-checkbox:focus-visible {
555
642
  outline: none;
556
643
  border-color: var(--ps1ui-color-primary);
557
644
  box-shadow: var(--ps1ui-shadow-focus-ring);
558
645
  }
646
+
559
647
  .ps1ui-checkbox:checked,
560
648
  .ps1ui-checkbox:indeterminate {
561
649
  background: var(--ps1ui-color-primary);
562
650
  border-color: var(--ps1ui-color-primary);
563
651
  }
652
+
564
653
  .ps1ui-checkbox:checked:hover:not(:disabled),
565
654
  .ps1ui-checkbox:indeterminate:hover:not(:disabled) {
566
655
  background: var(--ps1ui-color-primary-hover);
567
656
  border-color: var(--ps1ui-color-primary-hover);
568
657
  }
658
+
569
659
  .ps1ui-checkbox:checked:active:not(:disabled),
570
660
  .ps1ui-checkbox:indeterminate:active:not(:disabled) {
571
661
  background: var(--ps1ui-color-primary-active);
572
662
  border-color: var(--ps1ui-color-primary-active);
573
663
  }
664
+
574
665
  /* Checkmark: classic rotated-L trick — draws the right + bottom border of a small
575
666
  rectangle, then rotates 45deg so it reads as a ✓. `:not(:indeterminate)` makes
576
667
  precedence explicit for the mixed DOM state (both .checked and .indeterminate
@@ -592,6 +683,7 @@ html {
592
683
  border-width: 0 2px 2px 0;
593
684
  transform: rotate(45deg);
594
685
  }
686
+
595
687
  .ps1ui-checkbox:indeterminate::after {
596
688
  content: "";
597
689
  position: absolute;
@@ -602,6 +694,7 @@ html {
602
694
  background: currentColor;
603
695
  border-radius: 1px;
604
696
  }
697
+
605
698
  /* forced-colors force-replaces author backgrounds, so the background-drawn
606
699
  indeterminate bar would vanish — redraw it as a border, which survives as
607
700
  system ink (same 6–8px band; the checked ✓ is already border-drawn and
@@ -616,12 +709,14 @@ html {
616
709
  border-radius: 0;
617
710
  }
618
711
  }
712
+
619
713
  .ps1ui-checkbox:disabled {
620
714
  cursor: not-allowed;
621
715
  color: var(--ps1ui-color-fg-subtle);
622
716
  background: var(--ps1ui-color-surface);
623
717
  border-color: var(--ps1ui-color-border);
624
718
  }
719
+
625
720
  .ps1ui-checkbox:disabled:checked,
626
721
  .ps1ui-checkbox:disabled:indeterminate {
627
722
  background: var(--ps1ui-color-surface);
@@ -674,6 +769,7 @@ html {
674
769
  syntax: "*";
675
770
  inherits: false;
676
771
  }
772
+
677
773
  /* Container — max-width + horizontal padding + auto centering.
678
774
  Establishes an inline-size containment context so descendants respond to
679
775
  Container's width via `@container` queries. Named `ps1ui-container` for
@@ -715,6 +811,7 @@ html {
715
811
  max-width: var(--_size);
716
812
  padding-inline: var(--_px);
717
813
  }
814
+
718
815
  @container (min-width: 40rem) {
719
816
  .ps1ui-container {
720
817
  --_size: var(--_size-sm);
@@ -784,6 +881,7 @@ html {
784
881
  syntax: "*";
785
882
  inherits: false;
786
883
  }
884
+
787
885
  /* Grid — display:grid + N-column tracks + gap.
788
886
  Establishes an inline-size containment context so descendants respond to
789
887
  Grid's width via `@container` queries. Named `ps1ui-grid` for future
@@ -824,6 +922,7 @@ html {
824
922
  grid-template-columns: repeat(var(--_columns), minmax(0, 1fr));
825
923
  gap: var(--_gap);
826
924
  }
925
+
827
926
  @container (min-width: 40rem) {
828
927
  .ps1ui-grid {
829
928
  --_columns: var(--_columns-sm);
@@ -873,6 +972,7 @@ html {
873
972
  syntax: "*";
874
973
  inherits: false;
875
974
  }
975
+
876
976
  /* GridItem — child of Grid that spans multiple column tracks.
877
977
  GridItem is a leaf: it queries an ancestor containment context (typically
878
978
  the parent Grid, which establishes `container: ps1ui-grid / inline-size`),
@@ -911,6 +1011,7 @@ html {
911
1011
 
912
1012
  grid-column: span var(--_col-span);
913
1013
  }
1014
+
914
1015
  @container (min-width: 40rem) {
915
1016
  .ps1ui-griditem {
916
1017
  --_col-span: var(--_col-span-sm);
@@ -947,20 +1048,25 @@ html {
947
1048
  border-color var(--ps1ui-transition-fast),
948
1049
  box-shadow var(--ps1ui-transition-fast);
949
1050
  }
1051
+
950
1052
  .ps1ui-input::-moz-placeholder {
951
1053
  color: var(--ps1ui-color-fg-subtle);
952
1054
  }
1055
+
953
1056
  .ps1ui-input::placeholder {
954
1057
  color: var(--ps1ui-color-fg-subtle);
955
1058
  }
1059
+
956
1060
  .ps1ui-input:hover:not(:disabled) {
957
1061
  border-color: var(--ps1ui-color-primary);
958
1062
  }
1063
+
959
1064
  .ps1ui-input:focus {
960
1065
  outline: none;
961
1066
  border-color: var(--ps1ui-color-primary);
962
1067
  box-shadow: var(--ps1ui-shadow-focus-ring);
963
1068
  }
1069
+
964
1070
  .ps1ui-input:disabled {
965
1071
  cursor: not-allowed;
966
1072
  color: var(--ps1ui-color-fg-subtle);
@@ -975,13 +1081,13 @@ html {
975
1081
  line-height: 1.6;
976
1082
  color: var(--ps1ui-color-fg);
977
1083
  }
978
- /* List — semantic <ul>/<ol> wrapper with terminal-style markers.
1084
+ /* List — semantic <ul>/<ol> wrapper with plain-text markers.
979
1085
 
980
1086
  The global reset in base.css sets `list-style: none` on every <ul>/<ol>,
981
1087
  which strips the UA bullet/number glyph. List re-introduces a marker via
982
1088
  `::before` on each direct <li> — `-` for unordered, `NN.` for ordered —
983
1089
  so the "reset removes bullets" side effect turns into an on-brand
984
- terminal glyph rather than a hole.
1090
+ plain-text glyph rather than a hole.
985
1091
 
986
1092
  The ordered marker uses `counter(list-item)` — the browser-maintained
987
1093
  list-item counter that already honours `<ol start>`, `<ol reversed>`,
@@ -1001,6 +1107,7 @@ html {
1001
1107
  Forced-colors (Windows High Contrast) repaints the `::before` glyph
1002
1108
  with `CanvasText` — the marker survives as system-foreground ink; no
1003
1109
  forced-colors-specific defense is needed. */
1110
+
1004
1111
  .ps1ui-list {
1005
1112
  display: flex;
1006
1113
  flex-direction: column;
@@ -1012,6 +1119,7 @@ html {
1012
1119
  font-size: var(--ps1ui-font-size-sm);
1013
1120
  line-height: 1.6;
1014
1121
  }
1122
+
1015
1123
  /* Hanging indent — `padding-left` reserves the marker column; the ::before
1016
1124
  is inline-block with a negative `margin-left` that pulls it into that
1017
1125
  reservation. Wrapped item text continues from the padding-left position
@@ -1031,12 +1139,15 @@ html {
1031
1139
  Ordered's `3ch` marker column fits `1.` through `99.`; lists past 99
1032
1140
  items visually break the right-align column — an acceptable trade for
1033
1141
  the common < 100 case. */
1142
+
1034
1143
  .ps1ui-list--unordered > li {
1035
1144
  padding-left: calc(1ch + var(--ps1ui-space-sm));
1036
1145
  }
1146
+
1037
1147
  .ps1ui-list--ordered > li {
1038
1148
  padding-left: calc(3ch + var(--ps1ui-space-sm));
1039
1149
  }
1150
+
1040
1151
  .ps1ui-list > li::before {
1041
1152
  display: inline-block;
1042
1153
  padding-right: var(--ps1ui-space-sm);
@@ -1044,16 +1155,19 @@ html {
1044
1155
  font-variant-numeric: tabular-nums;
1045
1156
  text-align: right;
1046
1157
  }
1158
+
1047
1159
  .ps1ui-list--unordered > li::before {
1048
1160
  content: "-";
1049
1161
  width: 1ch;
1050
1162
  margin-left: calc((1ch + var(--ps1ui-space-sm)) * -1);
1051
1163
  }
1164
+
1052
1165
  .ps1ui-list--ordered > li::before {
1053
1166
  content: counter(list-item) ".";
1054
1167
  width: 3ch;
1055
1168
  margin-left: calc((3ch + var(--ps1ui-space-sm)) * -1);
1056
1169
  }
1170
+
1057
1171
  /* Breathing room above a nested <List>. Descendant combinator (not child)
1058
1172
  so the rule still applies when a consumer wraps the sublist in a layout
1059
1173
  primitive — `<li><Stack><List>…</List></Stack></li>` gets the same gap
@@ -1156,17 +1270,20 @@ html {
1156
1270
  to wrapping is still possible via `style={{ whiteSpace: "pre-wrap" }}`. */
1157
1271
  white-space: pre;
1158
1272
  }
1273
+
1159
1274
  .ps1ui-codeblock:focus-visible {
1160
1275
  outline: none;
1161
1276
  border-color: var(--ps1ui-color-primary);
1162
1277
  box-shadow: var(--ps1ui-shadow-focus-ring);
1163
1278
  }
1279
+
1164
1280
  .ps1ui-codeblock code {
1165
1281
  font-family: inherit;
1166
1282
  font-size: inherit;
1167
1283
  color: inherit;
1168
1284
  background: none;
1169
1285
  }
1286
+
1170
1287
  /* Prism token classes, mapped to ps1ui code palette (see tokens.css).
1171
1288
  Refractor emits hast <span class="token <type>"> nodes; keep this list
1172
1289
  in sync with the token types produced by Prism grammars. */
@@ -1177,18 +1294,22 @@ html {
1177
1294
  color: var(--ps1ui-code-comment);
1178
1295
  font-style: italic;
1179
1296
  }
1297
+
1180
1298
  .ps1ui-codeblock .token.punctuation,
1181
1299
  .ps1ui-codeblock .token.operator {
1182
1300
  color: var(--ps1ui-code-punctuation);
1183
1301
  }
1302
+
1184
1303
  .ps1ui-codeblock .token.property,
1185
1304
  .ps1ui-codeblock .token.tag,
1186
1305
  .ps1ui-codeblock .token.symbol {
1187
1306
  color: var(--ps1ui-code-tag);
1188
1307
  }
1308
+
1189
1309
  .ps1ui-codeblock .token.attr-name {
1190
1310
  color: var(--ps1ui-code-attr);
1191
1311
  }
1312
+
1192
1313
  .ps1ui-codeblock .token.string,
1193
1314
  .ps1ui-codeblock .token.char,
1194
1315
  .ps1ui-codeblock .token.url,
@@ -1196,6 +1317,7 @@ html {
1196
1317
  .ps1ui-codeblock .token.regex {
1197
1318
  color: var(--ps1ui-code-string);
1198
1319
  }
1320
+
1199
1321
  /* diff: line-level markers get their own hue so "+" reads as added (green) and
1200
1322
  "-" reads as removed (rose). Distinct from tag/string groups on purpose. */
1201
1323
  .ps1ui-codeblock .token.inserted {
@@ -1204,30 +1326,36 @@ html {
1204
1326
  .ps1ui-codeblock .token.deleted {
1205
1327
  color: var(--ps1ui-code-deleted);
1206
1328
  }
1329
+
1207
1330
  .ps1ui-codeblock .token.number,
1208
1331
  .ps1ui-codeblock .token.boolean,
1209
1332
  .ps1ui-codeblock .token.constant,
1210
1333
  .ps1ui-codeblock .token.entity {
1211
1334
  color: var(--ps1ui-code-constant);
1212
1335
  }
1336
+
1213
1337
  .ps1ui-codeblock .token.class-name,
1214
1338
  .ps1ui-codeblock .token.builtin {
1215
1339
  color: var(--ps1ui-code-type);
1216
1340
  }
1341
+
1217
1342
  .ps1ui-codeblock .token.function,
1218
1343
  .ps1ui-codeblock .token.method {
1219
1344
  color: var(--ps1ui-code-function);
1220
1345
  }
1346
+
1221
1347
  .ps1ui-codeblock .token.keyword,
1222
1348
  .ps1ui-codeblock .token.selector,
1223
1349
  .ps1ui-codeblock .token.atrule,
1224
1350
  .ps1ui-codeblock .token.important {
1225
1351
  color: var(--ps1ui-code-keyword);
1226
1352
  }
1353
+
1227
1354
  .ps1ui-codeblock .token.important,
1228
1355
  .ps1ui-codeblock .token.bold {
1229
1356
  font-weight: 600;
1230
1357
  }
1358
+
1231
1359
  .ps1ui-codeblock .token.italic {
1232
1360
  font-style: italic;
1233
1361
  }
@@ -1240,6 +1368,7 @@ html {
1240
1368
  font-size: var(--ps1ui-font-size-sm);
1241
1369
  line-height: 1.6;
1242
1370
  }
1371
+
1243
1372
  .ps1ui-details__summary {
1244
1373
  padding: var(--ps1ui-control-padding-y) var(--ps1ui-space-md);
1245
1374
  cursor: pointer;
@@ -1255,13 +1384,15 @@ html {
1255
1384
  marker so our ::before glyph is the only disclosure indicator. */
1256
1385
  list-style: none;
1257
1386
  }
1387
+
1258
1388
  /* Older WebKit (Safari < 17) renders a native disclosure triangle via its
1259
1389
  private ::-webkit-details-marker pseudo — `list-style: none` alone doesn't
1260
1390
  suppress it there. Explicitly hide it so ::before is the only glyph. */
1261
1391
  .ps1ui-details__summary::-webkit-details-marker {
1262
1392
  display: none;
1263
1393
  }
1264
- /* Terminal-style disclosure marker: `▸` when closed, `▾` when open. Fixed
1394
+
1395
+ /* Glyph disclosure marker: `▸` when closed, `▾` when open. Fixed
1265
1396
  inline-block width so the summary text baseline stays put across states —
1266
1397
  the two glyphs don't measure identically in the JetBrains Mono fallback
1267
1398
  monospace stack. Colored with fg-muted so the arrow reads as chrome, not
@@ -1273,12 +1404,15 @@ html {
1273
1404
  margin-right: var(--ps1ui-space-sm);
1274
1405
  color: var(--ps1ui-color-fg-muted);
1275
1406
  }
1407
+
1276
1408
  .ps1ui-details[open] > .ps1ui-details__summary::before {
1277
1409
  content: "▾";
1278
1410
  }
1411
+
1279
1412
  .ps1ui-details__summary:hover {
1280
1413
  color: var(--ps1ui-color-primary);
1281
1414
  }
1415
+
1282
1416
  /* Focus ring lives on the summary (the interactive element), not on details
1283
1417
  itself — .ps1ui-details__summary:focus-visible is registered in
1284
1418
  styles/components.css's forced-colors grouped selector so the box-shadow
@@ -1287,6 +1421,7 @@ html {
1287
1421
  outline: none;
1288
1422
  box-shadow: var(--ps1ui-shadow-focus-ring);
1289
1423
  }
1424
+
1290
1425
  .ps1ui-details__body {
1291
1426
  padding: var(--ps1ui-space-md);
1292
1427
  border-top: 1px solid var(--ps1ui-color-border);
@@ -1408,6 +1543,7 @@ html {
1408
1543
  syntax: "*";
1409
1544
  inherits: false;
1410
1545
  }
1546
+
1411
1547
  /* Stack — flexbox composition of children along one axis.
1412
1548
  Establishes an inline-size containment context so descendants respond to
1413
1549
  Stack's width via `@container` queries. Named `ps1ui-stack` for future
@@ -1483,6 +1619,7 @@ html {
1483
1619
  justify-content: var(--_justify);
1484
1620
  flex-wrap: var(--_wrap);
1485
1621
  }
1622
+
1486
1623
  @container (min-width: 40rem) {
1487
1624
  .ps1ui-stack {
1488
1625
  --_direction: var(--_direction-sm);
@@ -1519,7 +1656,7 @@ html {
1519
1656
  --_wrap: var(--_wrap-xl);
1520
1657
  }
1521
1658
  }
1522
- /* Table — semantic <table> with terminal-style grid borders, wrapped in a
1659
+ /* Table — semantic <table> with box-drawing-style grid borders, wrapped in a
1523
1660
  horizontal-scroll container.
1524
1661
 
1525
1662
  Scroll wrapper: `display: table` boxes don't honor overflow, so Table.tsx
@@ -1531,8 +1668,8 @@ html {
1531
1668
  high-contrast mode; the grouped rule restores a real outline).
1532
1669
 
1533
1670
  Grid borders: `border-collapse: collapse` + a uniform 1px cell border in
1534
- --ps1ui-color-border draw the closed single-stroke grid a terminal
1535
- box-drawing table (┌─┬─┐) renders — collapsing is what merges adjacent
1671
+ --ps1ui-color-border draw the closed single-stroke grid a box-drawing
1672
+ table (┌─┬─┐) renders — collapsing is what merges adjacent
1536
1673
  cell edges into one shared rule. The header row separates on two axes:
1537
1674
  a surface background and a border-strong bottom rule. That rule is
1538
1675
  declared on the <th> (cell origin) deliberately — in border-collapse
@@ -1551,13 +1688,16 @@ html {
1551
1688
  neutralizations are declared here even though base.css's reset also
1552
1689
  collapses table borders — the components.css entry ships without the
1553
1690
  reset, and UA <th> defaults (bold, center) would leak through. */
1691
+
1554
1692
  .ps1ui-table__scroller {
1555
1693
  overflow-x: auto;
1556
1694
  }
1695
+
1557
1696
  .ps1ui-table__scroller:focus-visible {
1558
1697
  outline: none;
1559
1698
  box-shadow: var(--ps1ui-shadow-focus-ring);
1560
1699
  }
1700
+
1561
1701
  /* No width override — the UA's shrink-to-fit keeps the grid hugging its
1562
1702
  content like a box-drawing table. Call sites that want a full-bleed table
1563
1703
  (e.g. a props table filling a docs column) opt in with `width: 100%`. */
@@ -1568,6 +1708,7 @@ html {
1568
1708
  font-size: var(--ps1ui-font-size-sm);
1569
1709
  line-height: 1.6;
1570
1710
  }
1711
+
1571
1712
  .ps1ui-table th,
1572
1713
  .ps1ui-table td {
1573
1714
  border: 1px solid var(--ps1ui-color-border);
@@ -1581,10 +1722,12 @@ html {
1581
1722
  vertical centers of unequal-height neighbors. */
1582
1723
  vertical-align: top;
1583
1724
  }
1725
+
1584
1726
  .ps1ui-table th {
1585
1727
  background: var(--ps1ui-color-surface);
1586
1728
  font-weight: 600;
1587
1729
  }
1730
+
1588
1731
  .ps1ui-table thead th {
1589
1732
  border-bottom-color: var(--ps1ui-color-border-strong);
1590
1733
  }
@@ -1672,6 +1815,7 @@ html {
1672
1815
  syntax: "*";
1673
1816
  inherits: false;
1674
1817
  }
1818
+
1675
1819
  /* Text — typography primitive.
1676
1820
  Text is a leaf component: it queries an ancestor containment context (a
1677
1821
  PS1Root / Container / Grid / Stack), but does NOT establish one itself.
@@ -1716,6 +1860,7 @@ html {
1716
1860
  font-size: var(--_size);
1717
1861
  font-weight: var(--_weight);
1718
1862
  }
1863
+
1719
1864
  @container (min-width: 40rem) {
1720
1865
  .ps1ui-text {
1721
1866
  --_size: var(--_size-sm);
@@ -1740,15 +1885,18 @@ html {
1740
1885
  --_weight: var(--_weight-xl);
1741
1886
  }
1742
1887
  }
1888
+
1743
1889
  .ps1ui-text--truncate {
1744
1890
  overflow: hidden;
1745
1891
  white-space: nowrap;
1746
1892
  text-overflow: ellipsis;
1747
1893
  max-width: 100%;
1748
1894
  }
1895
+
1749
1896
  .ps1ui-text--truncate-inline {
1750
1897
  display: inline-block;
1751
1898
  }
1899
+
1752
1900
  .ps1ui-text--body {
1753
1901
  color: var(--ps1ui-color-fg);
1754
1902
  }
@@ -1764,6 +1912,52 @@ html {
1764
1912
  .ps1ui-text--accent {
1765
1913
  color: var(--ps1ui-color-accent);
1766
1914
  }
1915
+ .ps1ui-text--danger {
1916
+ color: var(--ps1ui-color-danger);
1917
+ }
1918
+ .ps1ui-textarea {
1919
+ display: block;
1920
+ width: 100%;
1921
+ box-sizing: border-box;
1922
+ font-family: var(--ps1ui-font-mono);
1923
+ font-size: var(--ps1ui-font-size-sm);
1924
+ line-height: 1.5;
1925
+ color: var(--ps1ui-color-fg);
1926
+ background: var(--ps1ui-color-bg);
1927
+ border: 1px solid var(--ps1ui-color-border-strong);
1928
+ border-radius: var(--ps1ui-radius-md);
1929
+ padding: var(--ps1ui-control-padding-y) var(--ps1ui-space-md);
1930
+ resize: vertical;
1931
+ transition:
1932
+ border-color var(--ps1ui-transition-fast),
1933
+ box-shadow var(--ps1ui-transition-fast);
1934
+ }
1935
+
1936
+ .ps1ui-textarea::-moz-placeholder {
1937
+ color: var(--ps1ui-color-fg-subtle);
1938
+ }
1939
+
1940
+ .ps1ui-textarea::placeholder {
1941
+ color: var(--ps1ui-color-fg-subtle);
1942
+ }
1943
+
1944
+ .ps1ui-textarea:hover:not(:disabled) {
1945
+ border-color: var(--ps1ui-color-primary);
1946
+ }
1947
+
1948
+ .ps1ui-textarea:focus {
1949
+ outline: none;
1950
+ border-color: var(--ps1ui-color-primary);
1951
+ box-shadow: var(--ps1ui-shadow-focus-ring);
1952
+ }
1953
+
1954
+ .ps1ui-textarea:disabled {
1955
+ cursor: not-allowed;
1956
+ color: var(--ps1ui-color-fg-subtle);
1957
+ border-color: var(--ps1ui-color-border);
1958
+ background: var(--ps1ui-color-surface);
1959
+ resize: none;
1960
+ }
1767
1961
  /* Input custom properties emitted by Heading.tsx per set responsive-prop
1768
1962
  breakpoint (`--_heading-<axis>-<bp>`). Registered as non-inheriting for
1769
1963
  the same reason as Stack — see Stack.css's @property block for the full
@@ -1808,6 +2002,7 @@ html {
1808
2002
  syntax: "*";
1809
2003
  inherits: false;
1810
2004
  }
2005
+
1811
2006
  /* Heading — semantic heading primitive with level-driven typography.
1812
2007
  Like Text, Heading is a leaf: it queries an ancestor containment context
1813
2008
  but does not establish one itself. See Text.css for the rationale.
@@ -1858,6 +2053,7 @@ html {
1858
2053
  font-size: var(--_size);
1859
2054
  font-weight: var(--_weight);
1860
2055
  }
2056
+
1861
2057
  @container (min-width: 40rem) {
1862
2058
  .ps1ui-heading {
1863
2059
  --_size: var(--_size-sm);
@@ -1882,6 +2078,7 @@ html {
1882
2078
  --_weight: var(--_weight-xl);
1883
2079
  }
1884
2080
  }
2081
+
1885
2082
  /* Containment side-effect defense — applied uniformly to every primitive that
1886
2083
  establishes a `container-type: inline-size` context (i.e. every layout
1887
2084
  primitive whose responsive props drive `@container` queries).
@@ -1911,6 +2108,7 @@ html {
1911
2108
  justify-self: stretch;
1912
2109
  min-width: 0;
1913
2110
  }
2111
+
1914
2112
  /* forced-colors (Windows High Contrast) strips box-shadow, so every component
1915
2113
  whose focus ring is box-shadow-based needs a real outline there — one rule,
1916
2114
  one owner, same precedent as the containment grouped selector above. Add new
@@ -1923,7 +2121,8 @@ html {
1923
2121
  .ps1ui-codeblock:focus-visible,
1924
2122
  .ps1ui-details__summary:focus-visible,
1925
2123
  .ps1ui-input:focus,
1926
- .ps1ui-table__scroller:focus-visible {
2124
+ .ps1ui-table__scroller:focus-visible,
2125
+ .ps1ui-textarea:focus {
1927
2126
  outline: 2px solid CanvasText;
1928
2127
  outline-offset: 2px;
1929
2128
  }