@blakesteve/roster 4.8.0 → 4.8.2
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/README.md +116 -16
- package/dist/components/atoms/Select/Select.d.ts +17 -1
- package/dist/components/atoms/Tooltip/Tooltip.d.ts +9 -1
- package/dist/meta.json +2 -2
- package/dist/roster.cjs.js +2 -2
- package/dist/roster.css +1 -1
- package/dist/roster.es.js +1548 -1424
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,6 +152,12 @@ makes no difference, because the portal is still a descendant. If you scope
|
|
|
152
152
|
the field and carries it across the portal, so the menu follows the same scope
|
|
153
153
|
the trigger is in.
|
|
154
154
|
|
|
155
|
+
That mechanism carries the `.dark` CLASS across the portal. It does not carry
|
|
156
|
+
custom properties: `--roster-popover-*` and any other variable you scope to a
|
|
157
|
+
container reach the trigger and stop at the portal boundary, because the panel
|
|
158
|
+
is not a descendant of your container. Set those at `:root`. See
|
|
159
|
+
[Floating surface colors](#floating-surface-colors).
|
|
160
|
+
|
|
155
161
|
`Navbar`'s `themeMode` prop is a different thing: it describes what palette the
|
|
156
162
|
bar paints _itself_ with. Pair them with `themeMode="auto"` and the nav follows
|
|
157
163
|
whatever `ThemeToggle` sets.
|
|
@@ -254,6 +260,9 @@ Note also that a solid fill's contrast against the surface _behind_ it is a
|
|
|
254
260
|
separate requirement (WCAG 1.4.11, 3:1) and is not covered here. Solid amber on
|
|
255
261
|
a white page is 1.67:1, so it has effectively no visible edge.
|
|
256
262
|
|
|
263
|
+
Form controls **are** covered for that requirement, separately: see
|
|
264
|
+
`--roster-control-border` under [Control surface colors](#control-surface-colors).
|
|
265
|
+
|
|
257
266
|
### Focus
|
|
258
267
|
|
|
259
268
|
Every focusable control draws the same ring: `--roster-ring` for the ring
|
|
@@ -262,6 +271,24 @@ control's own fill. Both flip with the theme — `primary-500` on white in light
|
|
|
262
271
|
`primary-400` on `gray-950` in dark — which puts the indicator at 6.37:1 and
|
|
263
272
|
7.31:1 against the surface either side of it, where WCAG 1.4.11 asks for 3:1.
|
|
264
273
|
|
|
274
|
+
Both tokens follow the **page**, which is right until a component deliberately
|
|
275
|
+
inverts against it. `Dialog`'s `slate` and `primary` are dark whatever the page
|
|
276
|
+
is doing, so on a light page they were handing their contents the light-mode
|
|
277
|
+
ring: `primary-500` on `gray-700` is **1.61:1**, and the white offset behind it
|
|
278
|
+
was 10.27:1, making the gap the most visible part of the indicator. Those two
|
|
279
|
+
variants now set both tokens on their own subtree, so a field inside them gets
|
|
280
|
+
3.80:1 and 4.04:1 instead. `white` and `glass` are left alone because they
|
|
281
|
+
follow the page like everything else.
|
|
282
|
+
|
|
283
|
+
If you build your own inverted surface, do the same on it:
|
|
284
|
+
|
|
285
|
+
```css
|
|
286
|
+
.my-dark-panel {
|
|
287
|
+
--roster-ring: var(--roster-primary-400);
|
|
288
|
+
--roster-ring-offset: /* that panel's own background */;
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
265
292
|
Retint it for your own accent:
|
|
266
293
|
|
|
267
294
|
```css
|
|
@@ -359,40 +386,55 @@ pseudo-elements for any element that sets either of them, so on anything modern
|
|
|
359
386
|
|
|
360
387
|
### Control surface colors
|
|
361
388
|
|
|
362
|
-
`Input`, `PasswordInput` and `Select` read these variables
|
|
363
|
-
|
|
364
|
-
|
|
389
|
+
`Input`, `PasswordInput`, `Textarea` and `Select` read these variables, so a
|
|
390
|
+
consumer can repaint a form control without restating the class list. Both
|
|
391
|
+
`outline` and `soft` read the border: `soft` used to be borderless and lean on
|
|
392
|
+
its fill alone, which fails the moment the fill matches the surface — a `soft`
|
|
393
|
+
field inside a `white` Dialog in dark mode was exactly 1.00:1 and invisible
|
|
394
|
+
until focus.
|
|
365
395
|
|
|
366
396
|
```css
|
|
367
397
|
:root {
|
|
368
|
-
--roster-control-border: #
|
|
398
|
+
--roster-control-border: #65635f; /* gray-500 */
|
|
369
399
|
--roster-control-bg: transparent;
|
|
370
400
|
--roster-control-text: #1c1917;
|
|
371
401
|
--roster-control-border-focus: #0f6498;
|
|
372
402
|
}
|
|
373
403
|
.dark {
|
|
374
|
-
--roster-control-border: #
|
|
404
|
+
--roster-control-border: #a8a29e; /* gray-400 */
|
|
375
405
|
--roster-control-bg: transparent;
|
|
376
406
|
--roster-control-text: #f5f5f4;
|
|
377
|
-
--roster-control-border-focus: #
|
|
407
|
+
--roster-control-border-focus: #5ea3de; /* primary-400 */
|
|
378
408
|
}
|
|
379
409
|
```
|
|
380
410
|
|
|
411
|
+
`--roster-control-border` is the one token here whose two scopes hold different
|
|
412
|
+
values, and that is deliberate. WCAG 1.4.11 asks 3:1 to identify a control, and
|
|
413
|
+
a border has to move opposite its surface to get there: gray-500 reaches 5.99
|
|
414
|
+
on white but only 2.53 on a gray-800 Dialog, while gray-400 reaches 6.01 there
|
|
415
|
+
and 2.52 on white. No single step of the ramp clears 3:1 both ways. The old
|
|
416
|
+
gray-300 / gray-700 pair was 1.49 and 1.48 — a hairline rather than a boundary.
|
|
417
|
+
|
|
381
418
|
Set them in both scopes, for the same reason the scrollbar thumb needs both:
|
|
382
419
|
Roster's own `.dark` rule has equal specificity and comes later.
|
|
383
420
|
|
|
384
|
-
|
|
385
|
-
|
|
421
|
+
Three of the four still default to what the components used to hardcode.
|
|
422
|
+
`--roster-control-border` does not — the 1.4.11 pass moved it two ramp steps —
|
|
423
|
+
so installing this version **does** change how your fields look, deliberately. Placeholder color, the error state, and the
|
|
386
424
|
focus ring are deliberately not on tokens — the ring is already
|
|
387
425
|
`--roster-ring`, and error styling should stay recognizably an error.
|
|
388
426
|
|
|
389
427
|
Not every control uses every token: `Select` takes the first three, because
|
|
390
|
-
focus on its trigger is the shared `--roster-ring` rather than a border color
|
|
428
|
+
focus on its trigger is the shared `--roster-ring` rather than a border color,
|
|
429
|
+
and it draws the border as a ring rather than a border.
|
|
391
430
|
One family rather than one per component is deliberate — these controls sit in
|
|
392
431
|
the same row of the same form and are drawn to look identical, so being able to
|
|
393
432
|
repaint one and not the other would be a bug, not a choice.
|
|
394
433
|
|
|
395
|
-
|
|
434
|
+
`outline` reads all four. `soft` reads the border only, because a boundary is
|
|
435
|
+
the one thing a filled field cannot supply for itself on an arbitrary surface;
|
|
436
|
+
`white`, `slate` and `ghost` stay opinionated. `Checkbox` reads the border too,
|
|
437
|
+
for its unchecked box. Historically `outline` was the only reader, and
|
|
396
438
|
it carries no hover fill. The other variants each name a concrete surface
|
|
397
439
|
(`white`, `soft`, `slate`, `ghost`) and stay opinionated: a token that meant
|
|
398
440
|
something different in each would not be a token.
|
|
@@ -403,6 +445,56 @@ One caveat worth knowing before you lean on these: like `--roster-card-*`,
|
|
|
403
445
|
inside `@layer base` is outranked, and an override scoped to a subtree rather
|
|
404
446
|
than the document root will not reach them.
|
|
405
447
|
|
|
448
|
+
### Floating surface colors
|
|
449
|
+
|
|
450
|
+
`Select`'s menu, `Dialog`'s `white` variant and `Tooltip`'s `themed` variant
|
|
451
|
+
read these, so one palette covers every panel that floats over the page.
|
|
452
|
+
|
|
453
|
+
```css
|
|
454
|
+
:root {
|
|
455
|
+
--roster-popover-bg: #fff;
|
|
456
|
+
--roster-popover-border: #e7e5e4;
|
|
457
|
+
--roster-popover-text: #1c1917;
|
|
458
|
+
}
|
|
459
|
+
.dark {
|
|
460
|
+
--roster-popover-bg: #292524;
|
|
461
|
+
--roster-popover-border: #44403c;
|
|
462
|
+
--roster-popover-text: #f5f5f4;
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Separate from `--roster-control-*` on purpose, and the default is the reason:
|
|
467
|
+
`--roster-control-bg` is `transparent`, which is right for a field drawn on a
|
|
468
|
+
page and actively broken for a panel drawn over one. A control and the menu it
|
|
469
|
+
opens are also legitimately different surfaces in plenty of palettes.
|
|
470
|
+
|
|
471
|
+
**Set these at `:root`, not on a container.** The menu and the tooltip bubble
|
|
472
|
+
are portaled to `<body>`, so they are not descendants of whatever you rendered
|
|
473
|
+
the component inside: properties set on a wrapper reach the trigger and stop
|
|
474
|
+
there. Any ancestor of `<body>` works — `:root` is simply the one that always
|
|
475
|
+
is, which is also why the `.dark` block below reaches a menu whose `.dark`
|
|
476
|
+
scope `Select` copies across the portal. The result is a themed trigger opening an unthemed panel, which is the
|
|
477
|
+
exact problem this family exists to fix. Set them in both scopes too, for the
|
|
478
|
+
same reason the control family needs both.
|
|
479
|
+
|
|
480
|
+
Where each component takes them, and why not everywhere:
|
|
481
|
+
|
|
482
|
+
- **`Select`'s menu** has no variants, so it reads all three outright — panel
|
|
483
|
+
fill, hairline, and the option labels, which are `text-inherit` so the text
|
|
484
|
+
token actually reaches the only text in the panel.
|
|
485
|
+
- **`Dialog`** takes them in `white` only. `slate`, `primary` and `glass` each
|
|
486
|
+
name a specific surface, and a token that meant something different inside
|
|
487
|
+
each name would not be a token. The defaults are `white`'s previous values
|
|
488
|
+
exactly, in both schemes, so nothing moves.
|
|
489
|
+
- **`Tooltip`** has a `themed` variant that reads them. Its `dark` and `light`
|
|
490
|
+
variants are untouched: `dark` is the default, an inverted bubble that
|
|
491
|
+
deliberately reads the same on a light or a dark page, and pointing that at a
|
|
492
|
+
token whose light default is white would not theme it, it would delete it.
|
|
493
|
+
|
|
494
|
+
One appearance change ships with this: `Select`'s menu ring moves from
|
|
495
|
+
`black/5` to `--roster-gray-200` in light mode, which is the hairline the rest
|
|
496
|
+
of the library already draws.
|
|
497
|
+
|
|
406
498
|
### Reaching the control
|
|
407
499
|
|
|
408
500
|
`className` on a field component lands on the outer `Field` wrapper. That is
|
|
@@ -411,12 +503,20 @@ cannot reach the element that draws the border, the height or the font.
|
|
|
411
503
|
|
|
412
504
|
Every field therefore has a second prop, **named for the element it reaches**:
|
|
413
505
|
|
|
414
|
-
| Component | Wrapper | The control itself |
|
|
415
|
-
| -------------- | ----------- | -------------------- |
|
|
416
|
-
| `Input` | `className` | `inputClassName` |
|
|
417
|
-
| `PasswordInput`| `className` | `inputClassName` |
|
|
418
|
-
| `Select` | `className` | `triggerClassName` |
|
|
419
|
-
| `Textarea` | `className` | `textareaClassName` |
|
|
506
|
+
| Component | Wrapper | The control itself | Other inner parts |
|
|
507
|
+
| -------------- | ----------- | -------------------- | ------------------- |
|
|
508
|
+
| `Input` | `className` | `inputClassName` | — |
|
|
509
|
+
| `PasswordInput`| `className` | `inputClassName` | — |
|
|
510
|
+
| `Select` | `className` | `triggerClassName` | `optionsClassName` |
|
|
511
|
+
| `Textarea` | `className` | `textareaClassName` | — |
|
|
512
|
+
|
|
513
|
+
`Select` is the first component with two styleable inner parts, which is the
|
|
514
|
+
case the naming scheme was chosen for: `optionsClassName` reaches the popup
|
|
515
|
+
panel. One caveat that is a property of Headless UI rather than of this prop —
|
|
516
|
+
the panel's `max-height` and `overflow` are written inline by its `size`
|
|
517
|
+
middleware, so a height passed here lands in the class list and is outranked.
|
|
518
|
+
To cap the menu, set the variable that inline rule reads:
|
|
519
|
+
`optionsClassName="rst:[--anchor-max-height:20rem]"`.
|
|
420
520
|
|
|
421
521
|
The names differ on purpose. A single `controlClassName` would name a concept
|
|
422
522
|
rather than an element, and it breaks the first time a component has two
|
|
@@ -23,6 +23,22 @@ export interface SelectProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
|
23
23
|
* is the escape hatch for the rest. Mirrors `Input`'s `inputClassName`.
|
|
24
24
|
*/
|
|
25
25
|
triggerClassName?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Classes for the popup panel.
|
|
28
|
+
*
|
|
29
|
+
* The escape-hatch rule already covers the wrapper (`className`) and the
|
|
30
|
+
* control (`triggerClassName`); the panel was the one styleable element with
|
|
31
|
+
* no way in. Mirrors `Input`'s `inputClassName`.
|
|
32
|
+
*/
|
|
33
|
+
optionsClassName?: string;
|
|
34
|
+
/** Supporting text under the trigger. */
|
|
35
|
+
helperText?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The message for an invalid selection. Implies `error`, so a caller cannot
|
|
38
|
+
* set the red ring and forget to say why — which is what `error` on its own
|
|
39
|
+
* did: a red outline and no text anywhere for anyone.
|
|
40
|
+
*/
|
|
41
|
+
errorMessage?: string;
|
|
26
42
|
}
|
|
27
|
-
declare const Select: ({ options, value, onChange, placeholder, disabled, variant, size, error, className, triggerClassName, label, ...props }: SelectProps) => import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
declare const Select: ({ options, value, onChange, placeholder, disabled, variant, size, error, className, triggerClassName, optionsClassName, helperText, errorMessage, label, ...props }: SelectProps) => import("react/jsx-runtime").JSX.Element;
|
|
28
44
|
export { Select };
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
export type TooltipPlacement = "top" | "bottom" | "left" | "right";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* `dark` is an inverted bubble that deliberately reads the same on a light or
|
|
5
|
+
* a dark page, and `light` is its counterpart — both NAME a surface, so
|
|
6
|
+
* neither can be repointed at a consumer token without becoming something
|
|
7
|
+
* else. `themed` is the opt-in third: it reads `--roster-popover-*`, the same
|
|
8
|
+
* family Select's menu and Dialog's `white` variant read, so a host palette
|
|
9
|
+
* covers every floating surface at once.
|
|
10
|
+
*/
|
|
11
|
+
export type TooltipVariant = "dark" | "light" | "themed";
|
|
4
12
|
export interface TooltipProps {
|
|
5
13
|
/** The tooltip's popup content. Accepts a string or any React node. */
|
|
6
14
|
content: React.ReactNode;
|
package/dist/meta.json
CHANGED