Add a shared BandedDiv component - #2073
Conversation
❌ Code HealthIntroduces 49 Unused files
|
`theme.radius` gives roundness a named scale that tracks an element's size and prominence, so surfaces of the same class round the same way. Values are px strings, which `sx` and `styled` use literally — a number in `borderRadius` is multiplied by `theme.shape.borderRadius` instead.
BandedDiv generalizes the tucked-band mechanism: a rounded frame whose face sits over a colored band poking out one edge, selected by a side prop. The band takes an optional label, read vertically when the band is on the left or right. With an onClick set the frame becomes a ButtonBase and the band rests at a desaturated color, coming up to full color on hover. The desaturate helper and rest-saturation constant come with it, exported for owners that hold other surfaces to the band's color scheme.
The no-band state moves from an omitted side to an omitted bandColor, so AccountCard now passes side=bottom unconditionally and gates the band on the expiry color.
A left or right band now rotates the whole label node 90 degrees with a transform, so icons turn with the text. A transform never affects layout - the rotated label would keep its wide horizontal box and stretch the band - so RotatedLabel measures the node and swaps its dimensions onto a spacer, centering the rotated content absolutely inside it. The band no longer imposes a row Stack on the label; the node owns its internal layout. That also restores the icon-to-text gap the extraction dropped: the band's Stack spacing never applied inside a fragment label.
A BandedDiv without a band was the component's name telling a lie, so bandColor is now required and the band always renders. Also folds in the vertical label rotation direction change to 270 degrees, reading bottom-to-top.
MUI's decomposeColor rejects named CSS colors, so bandColor=tomato threw from getContrastText. The band color now round-trips through a canvas fillStyle, which has the browser normalize any color it can parse to hex or rgba() before the MUI color math. The context resets to black between calls, so an unparseable color degrades to a black band instead of silently reusing the previous call's color.
A bare BandedDiv now renders a padded theme-paper face instead of a transparent one, so the band tuck reads without the caller styling the face. faceSx merges over the default, so existing consumers are unchanged.
The face's default border now matches the service account cards exactly, using the same defaultOutline constant instead of the divider token.
The face's default border now carries the band color the way the service account cards do: desaturated at rest when the frame is interactive, rising to full color on hover together with the band; full color always when static. A label-less band also holds a minimum visible run past the overlap, so it reads as a band rather than collapsing to its padding.
The label node now renders into a slot stretched across the band's visible run whichever side the band is on, and owns its own alignment inside it. Horizontal bands stretch the label through a flex slot. Vertical bands rework RotatedLabel: the spacer stretches along the band, its measured length becomes the rotated node's width, and the label's natural height stays the band's thickness. A label that centers, spreads, or fills behaves identically on any side.
A fragment, string, or array label now renders on a centered row with a small gap between children, caption type, and no wrapping, so an icon-and-text label needs no wrapper Stack. A single element keeps the full-run canvas and owns its own layout and alignment. The default applies in both orientations, since the wrapped label feeds the same slot and RotatedLabel paths.
Merged onto the band over its defaults. The band's text styling (caption type, weight 600, nowrap) moves from the default label wrapper onto the band element itself, so it inherits into any label and a bandSx font size or weight override actually wins.
The only realistic band overrides are label text styles, and a single-element label already owns those. Everything else bandSx could reach is the tuck mechanism or the bandColor system, which callers should not mutate. Bare labels keep the caption/600 defaults inherited from the band.
Drops the nowrap default so a long label wraps and thickens the band; the vertical path already feeds the wrapped height back through its measurements. Wrapping makes the default row width-constrained, and an SVG's minimum size resolves to zero under flex shrink, so icons in the default row are pinned with flex none to keep them at their given size.
29b738f to
c53f0d8
Compare
|
Unwinding the split — BandedDiv goes back into greg/access (#1989). |
Changes
Adds
theme.radiusandsrc/components/shared/BandedDiv.tsx. Nothing consumes either yet; the service accounts UI (#1989) is the first caller and stacks on top of this PR.theme.radiusA named border-radius scale on the augmented theme (
sm4px,md8px,lg12px,xl16px,full), so roundness tracks an element's size and prominence and surfaces of the same class round the same way. Values are px strings, whichsxandstyleduse literally — a number inborderRadiusis multiplied bytheme.shape.borderRadiusinstead.BandedDivA rounded frame whose face sits over a colored band poking out one edge.
zIndex: 1, opaque), and the frame'soverflow: hiddenclips the band's outer corners, so the band needs no radius of its own.sidepicks the banded edge and defaults toleft.bandColoris required, and accepts any color the browser can parse — named CSS colors included, normalized through a canvasfillStylebecause MUI's color math rejects them.labelfills a slot spanning the band's visible run. A single element owns its own layout; a fragment, string, or array is laid onto a default centered row (caption type, small gap) so a bare icon-and-text label works without a wrapper. Long labels wrap and thicken the band.RotatedLabelmeasures with aResizeObserver: the label's natural height becomes the band's thickness, and the stretched spacer's length becomes the rotated label's width.onClickset, the frame becomes aButtonBaseand the band rests desaturated, coming up to full color on hover.desaturateandBAND_REST_SATURATIONare exported so an owner can hold other surfaces to the same rest color, andBANDED_DIV_FACE_CLASSlets an owner restyle the face from the frame's hover.faceSxmerges onto the face;sxtargets the frame.Tests
Manually tested
Automated tests