@gradeui/ui 4.0.1 → 4.1.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.
@@ -10,7 +10,7 @@ props:
10
10
  - Container: maxW? (sm | md | lg | xl | prose | full) — centred max width + gutters; default lg. The MEASURE.
11
11
  - Container: grid?: boolean — snap children to a 12-column grid (use `col-span-*` on children); default false.
12
12
  - Container: as? (div | section) — semantic element; default div.
13
- when_to_use: THE page scaffold. A page is an ordered stack of Sections — every distinct band (hero, logos, features, pricing, testimonial, CTA, footer) gets its OWN Section so each is independently themeable. `Section` is the full-width band (scope + vertical rhythm); drop a `Container` inside it for a measure, or omit the Container for a full-bleed band. Reach for Section/Container instead of hand-rolling `<section className="py-20"><div className="max-w-7xl mx-auto px-6">`. The content inside is free — use the parts (SectionEyebrow/Title/Subtitle/Description/Actions/Media) for the common heading+copy+CTA+media shape, or drop any JSX. SectionMedia is a slot for any media (MediaSurface image, Carousel, VideoPlayer, embed, or a whole app UI). Don't use Section for app chrome — that's AppShell.
13
+ when_to_use: THE page scaffold. A page is an ordered stack of Sections — every distinct band (hero, logos, features, pricing, testimonial, CTA, footer) gets its OWN Section so each is independently themeable. `Section` is the full-width band (scope + vertical rhythm). **REQUIRED scaffold: a `Section` ALWAYS contains a `Container`.** Put content inside the `Container`, never directly in the `Section`. For an edge-to-edge band use `<Container maxW="full">` — that is how you go full-bleed; you never omit the Container. NEVER hand-roll `<section className="py-20">` or a `<div className="max-w-7xl mx-auto px-6">` wrapper — that is exactly what `Section` + `Container` replace. This holds for app content regions too, not just marketing pages (AppShell is only the outer chrome; the regions inside it are still `Section` → `Container`). The content inside the Container is free — use the parts (SectionEyebrow/Title/Subtitle/Description/Actions/Media) for the common heading+copy+CTA+media shape, or drop any JSX. SectionMedia is a slot for any media (MediaSurface image, Carousel, VideoPlayer, embed, or a whole app UI).
14
14
  composes_with: [Container, MediaSurface, Carousel, VideoPlayer, Button, Badge, Card, Grid, Stack]
15
15
  aliases: [section, band, hero section, page section, content section, marketing section, landing section, full bleed, container, max width wrapper, page band, section block]
16
16
  ---
@@ -32,12 +32,14 @@ aliases: [section, band, hero section, page section, content section, marketing
32
32
  ```
33
33
 
34
34
  ```jsx
35
- // Full-bleed media band — no Container, so the media spans edge to edge.
36
- // The scope re-tones the band; the media frames itself.
35
+ // Full-bleed media band — STILL a Container, just maxW="full" so the media
36
+ // spans edge to edge. Section always wraps a Container; never omit it.
37
37
  <Section scope="card" pad="lg">
38
- <SectionMedia>
39
- <MediaSurface hint="Studio canvas" alt="A generated screen" className="aspect-[21/9] w-full" />
40
- </SectionMedia>
38
+ <Container maxW="full">
39
+ <SectionMedia>
40
+ <MediaSurface hint="Studio canvas" alt="A generated screen" className="aspect-[21/9] w-full" />
41
+ </SectionMedia>
42
+ </Container>
41
43
  </Section>
42
44
  ```
43
45
 
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: SelectionCard
3
+ import: "@gradeui/ui"
4
+ subcomponents: [RadioCard, CheckboxCard, SwitchCard]
5
+ props:
6
+ - label?: ReactNode — title line. Omit and pass `children` for fully custom content.
7
+ - description?: ReactNode — secondary line under the label.
8
+ - aside?: ReactNode — slot between content and indicator (a Badge, price, kbd hint).
9
+ - hideIndicator?: boolean — hide the dot/check/switch glyph; selection is then shown by the card's selected border + background. Semantics stay intact.
10
+ - indicatorPosition? (leading | trailing) — which side the glyph sits on; default trailing.
11
+ - RadioCard: value (string) — required; sits inside a `RadioGroup`. Renders as a RadioGroupPrimitive.Item.
12
+ - CheckboxCard: checked? / defaultChecked? / onCheckedChange? — renders as a CheckboxPrimitive.Root.
13
+ - SwitchCard: checked? / defaultChecked? / onCheckedChange? — renders as a SwitchPrimitives.Root.
14
+ when_to_use: A selectable option where the WHOLE card is the control — plan pickers, shipping/payment options, onboarding choices, settings toggles. Use RadioCard for single-select (inside a RadioGroup), CheckboxCard for multi-select, SwitchCard for an on/off option. The glyph differs by type on purpose so single-select vs multi-select vs toggle reads at a glance. All three share one `.gds-selection-card` surface so they look identical sitting together, and every visual is token-driven (`--gds-selection-card-*` with semantic fallbacks) so a project can re-skin them through the per-project override layer without forking.
15
+ composes_with: [RadioGroup, Badge, Label, Grid, Stack]
16
+ aliases: [selection card, radio card, checkbox card, switch card, option card, choice card, plan picker, pricing option, selectable card, tile select]
17
+ ---
18
+
19
+ The card itself carries `role=radio` / `checkbox` / `switch`, focus, hover, and the
20
+ checked state — the entire surface is the hit target. The glyph is only a visual
21
+ indicator.
22
+
23
+ ```jsx
24
+ // Single-select: RadioCards inside a RadioGroup.
25
+ <RadioGroup defaultValue="standard" className="grid gap-3">
26
+ <RadioCard value="standard" label="Standard" description="4–10 business days" />
27
+ <RadioCard value="fast" label="Fast" description="2–5 business days" aside={<Badge>Popular</Badge>} />
28
+ <RadioCard value="next-day" label="Next day" description="1 business day" />
29
+ </RadioGroup>
30
+ ```
31
+
32
+ ```jsx
33
+ // Multi-select + toggle.
34
+ <div className="grid gap-3">
35
+ <CheckboxCard defaultChecked label="Email receipts" description="Sent after each order" />
36
+ <SwitchCard label="Dark mode" description="Match the system theme" />
37
+ </div>
38
+ ```
39
+
40
+ **Anti-pattern — never nest interactive content.** Because the card is itself a
41
+ control, do NOT put a Slider, Input, Button, or link inside it. Static content only
42
+ (text, images, badges). If a card needs its own controls, use a plain `Card` with a
43
+ `Field` row + the control as siblings instead.
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: ShaderControls
3
+ import: "@gradeui/ui"
4
+ props:
5
+ - controls (ControlSpec[]) — the schema to render. Each spec describes one control (slider / toggle / select / switch / input) and its range, step, label, and display.
6
+ - value (DemoState) — controlled state object keyed by control name; the parent owns it.
7
+ - onChange ((key, value) => void) — fired on every control change.
8
+ - labelPosition? (inline | above) — dense inline (label left, control + value right) or stacked label-above; default inline.
9
+ - numberFormat? (raw | percent) — "percent" normalises eligible sliders (no unit, fractional step, non-negative) to 0–100%; a control's own `display: "percent"` always wins. Default raw.
10
+ when_to_use: Render a `ControlSpec[]` schema into a DS-native control panel — the single renderer behind shader params, the post-processing stack, and any effect layer (they all describe themselves as ControlSpec[]). Reach for it whenever you have a list of named, typed parameters to expose as a tweaker panel and want it to read identically to the Studio inspector. DS-consistent by construction: it composes the primitives at tool-panel density (Label size="xs", Slider size="sm", ghost Input, ToggleGroup, Select, Switch) — never bespoke markup.
11
+ composes_with: [Label, Slider, Input, ToggleGroup, Select, Switch, Card]
12
+ aliases: [shader controls, control panel, params panel, tweaker, parameter panel, controlspec renderer, effect controls, schema controls]
13
+ ---
14
+
15
+ ```jsx
16
+ const [state, setState] = React.useState({ speed: 0.4, grain: 0.2, invert: false });
17
+
18
+ <ShaderControls
19
+ controls={[
20
+ { key: "speed", type: "slider", label: "Speed", min: 0, max: 1, step: 0.01, display: "percent" },
21
+ { key: "grain", type: "slider", label: "Grain", min: 0, max: 1, step: 0.01 },
22
+ { key: "invert", type: "switch", label: "Invert" },
23
+ ]}
24
+ value={state}
25
+ onChange={(key, v) => setState((s) => ({ ...s, [key]: v }))}
26
+ numberFormat="percent"
27
+ />
28
+ ```
29
+
30
+ One renderer drives every params surface (shader, post stack, effect layer), so a
31
+ control added to the schema appears identically in the Studio inspector and any
32
+ consumer panel. Keep state in the parent; `ShaderControls` is fully controlled.