import type {
ShapePathParams,
ShapeProvider,
StrokePathParams,
} from '@wearables-ui-toolkit/mrbd';
| Member | Signature | Purpose |
|---|---|---|
getShapePath | (params: ShapePathParams) => string | The outer fill outline, as an SVG path d string. |
getStrokePath | (params: StrokePathParams) => string | The border outline, already inset so a stroke of the given width sits inside the fill. |
getCssBorderRadius? | () => string | Optional CSS border-radius fallback used before measured geometry is available. |
ShapePathParams is { width, height }; StrokePathParams adds strokeWidth. All three values are logical (CSS) pixels for the current rendered size.Z) in the layer’s own coordinate space, with the fill outline anchored at the origin. Layers draw against this path directly; nothing re-centers it for you.getStrokePath, inset the outline by half the stroke width on every side and reduce corner rounding by the same amount. A stroke centered on the outer boundary spills half its width outside the fill and reads as a fringe.width - strokeWidth can go negative on a small surface, and a corner radius larger than half the short side has to collapse.| Provider | Constructor | Notes |
|---|---|---|
RoundedRectangleShapeProvider | (cornerRadius = CornerRadius.LARGE, smoothing = 0.75) | Smooth (squircle) rounded rectangle. Exposes readonly cornerRadius and smoothing, and implements getCssBorderRadius. |
TailShapeProvider | (direction = 'right', cornerRadius = CornerRadius.MEDIUM, smoothing = 0.75) | Rounded rectangle with a speech-bubble tail. direction is a ShapeTailDirection: 'none', 'left', or 'right'; 'none' degenerates to the plain rounded rectangle. Exposes readonly direction, cornerRadius, and smoothing. Does not implement getCssBorderRadius, so the pre-measurement frame has square corners. |
smoothing controls how far the corner curve extends along each edge before it becomes a circular arc: 0 is exactly a CSS border-radius corner, 1 is maximally smoothed, and 0.75 is the system default. See Smooth Corners.CornerRadius is a branded numeric token type, not a free number. Use a named token so surfaces stay on the shared radius scale:CornerRadius.XXSMALL, XSMALL, SMALL, MEDIUM, LARGE, XLARGE, XXLARGE, XXXLARGE, and FULL.FULL renders as a pill: the outline collapses to a stadium (or a true circle on a square surface) regardless of the requested smoothing. See Corner Radius for which token belongs to which surface role.Container, StaticContainer, and IconWithContainerMaterial all take a shapeProvider prop. Components built on Container — Button, Card, Panel, ListItem, and the rest — inherit the same prop and supply the provider their role calls for, so overriding it is a deliberate act. When the prop is omitted, the host falls back to a smooth rounded rectangle with the large corner-radius token.import { useMemo } from 'react';
import {
Container,
CornerRadius,
MaterialLibrary,
RoundedRectangleShapeProvider,
} from '@wearables-ui-toolkit/mrbd';
export function CompactTile({ label }: { label: string }) {
const shapeProvider = useMemo(
() => new RoundedRectangleShapeProvider(CornerRadius.SMALL),
[],
);
const material = useMemo(() => MaterialLibrary.default(), []);
return (
<Container
material={material}
shapeProvider={shapeProvider}
focusable
clickable
className="compact-tile"
>
<span className="uit-text-body1">{label}</span>
</Container>
);
}
.compact-tile {
padding: var(--uit-spacing-medium);
color: var(--uit-color-text-primary);
}
Path2D from getShapePath and passes it to every layer as params.path. Fills, gradients, textures, and images all paint through it.clipsToShape: false. CSS layers are clipped to the same outline under the same rule.params.getInsetStrokePath(strokeWidth), which resolves through getStrokePath and returns a Path2D already translated into place. Stroke it directly; do not apply your own transform.Container and StaticContainer), it clips to the same fill path, so content and material can never disagree about the edge.getCssBorderRadius() as a plain CSS border-radius. A provider that omits the method falls back to square corners for that first frame, which is the right answer for a shape CSS cannot express and the wrong one for a shape it can.params.shapeContext is a MaterialShapeContext carrying the active shapeProvider plus the material’s resolved inset, which is what getInsetAdjustedShaderHeight uses to keep gradient radii consistent under an inset.Path2D through a second shared cache, keyed on the string itself, so identical geometry across components shares one Path2D.getShapeProviderId(provider) returns a stable number for a provider instance, assigned lazily on first call and held weakly. Layers that cache path-derived work — a blurred inner-shadow bitmap, for example — mix it into their cache key so switching shapes invalidates the cached bitmap while a pure alpha change does not. Include it in the key of any custom layer that caches by geometry.ShapeProvider when the outline itself is the point — not to imitate another component’s look. Below, a chamfered rectangle: corners cut at 45 degrees instead of rounded, with a correctly inset stroke path and an honest CSS fallback (CSS cannot express a chamfer, so the fallback is square).import {
CornerRadius,
type ShapePathParams,
type ShapeProvider,
type StrokePathParams,
} from '@wearables-ui-toolkit/mrbd';
/** Rectangle whose corners are cut at 45 degrees. Immutable by contract. */
export class ChamferShapeProvider implements ShapeProvider {
constructor(private readonly cut: number = CornerRadius.SMALL) {}
getShapePath({ width, height }: ShapePathParams): string {
return this.buildPath(0, 0, width, height, this.cut);
}
getStrokePath({ width, height, strokeWidth }: StrokePathParams): string {
const half = strokeWidth / 2;
return this.buildPath(
half,
half,
Math.max(0, width - strokeWidth),
Math.max(0, height - strokeWidth),
Math.max(0, this.cut - half),
);
}
getCssBorderRadius(): string {
return '0';
}
private buildPath(
x: number,
y: number,
width: number,
height: number,
cut: number,
): string {
const c = Math.max(0, Math.min(cut, Math.min(width, height) / 2));
return [
`M ${x + c},${y}`,
`L ${x + width - c},${y}`,
`L ${x + width},${y + c}`,
`L ${x + width},${y + height - c}`,
`L ${x + width - c},${y + height}`,
`L ${x + c},${y + height}`,
`L ${x},${y + height - c}`,
`L ${x},${y + c}`,
'Z',
].join(' ');
}
}
import { useMemo, type ReactNode } from 'react';
import { Container, MaterialLibrary } from '@wearables-ui-toolkit/mrbd';
import { ChamferShapeProvider } from './ChamferShapeProvider';
export function ChamferedTile({ children }: { children: ReactNode }) {
const shapeProvider = useMemo(() => new ChamferShapeProvider(), []);
const material = useMemo(() => MaterialLibrary.default(), []);
return (
<Container
material={material}
shapeProvider={shapeProvider}
focusable
clickable
className="chamfered-tile"
>
{children}
</Container>
);
}
getCachedSmoothRoundedRectPath instead of writing path commands by hand; it returns the same cached strings the built-in providers use.CornerRadius token so the surface stays on the shared radius scale.width and height so surfaces remain responsive.