@gradeui/ui 3.1.0 → 3.3.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.
- package/components/ui/button.md +6 -4
- package/components/ui/color-picker.md +34 -0
- package/components/ui/combobox.md +46 -0
- package/components/ui/data-view.md +59 -0
- package/components/ui/fill-picker.md +7 -3
- package/components/ui/gradient-editor.md +30 -0
- package/components/ui/map.md +6 -0
- package/components/ui/property-list.md +43 -0
- package/components/ui/section.md +52 -0
- package/components/ui/swatch.md +5 -2
- package/components/ui/toggle-group.md +21 -4
- package/dist/contracts.js +6 -6
- package/dist/contracts.js.map +1 -1
- package/dist/contracts.mjs +6 -6
- package/dist/contracts.mjs.map +1 -1
- package/dist/index.d.mts +516 -30
- package/dist/index.d.ts +516 -30
- package/dist/index.js +94 -94
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +94 -94
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -1
- package/styles/globals.css +341 -106
package/components/ui/button.md
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
name: Button
|
|
3
3
|
import: "@gradeui/ui"
|
|
4
4
|
variants: [default, destructive, outline, secondary, ghost, link, raised]
|
|
5
|
-
sizes: [sm, md, lg
|
|
5
|
+
sizes: [2xs, xs, sm, md, lg]
|
|
6
6
|
props:
|
|
7
7
|
- variant? (default | destructive | outline | secondary | ghost | link | raised) — `raised` here is a back-compat alias (the raised TRAIT on a neutral key surface); prefer the `raised` prop
|
|
8
8
|
- raised?: boolean — presence TRAIT: tactile elevation (bevel + drop + hover glow + pressed sink) layered onto ANY variant — raised primary, raised outline, etc. Glow tone reads --btn-glow → --accent-glow → --selected-glow; override per-button via style={{ "--btn-glow": "var(--warning)" }}
|
|
9
|
-
- size? (sm | md | lg
|
|
9
|
+
- size? (2xs | xs | sm | md | lg) — t-shirt scale aligned with Tabs/ToggleGroup heights (2xs=h-5, xs=h-6, sm=h-7, md=h-8, lg=h-10). 2xs/xs are the dense tool-panel sizes (match Figma Button size=2xs/xs). `default` still works as an alias for `md`.
|
|
10
|
+
- iconOnly?: boolean — squares the button at the current `size` height (w = h, no horizontal padding) for icon-only buttons; the icon child is centered. This is THE way to make a square icon button at any density (sm→28², 2xs→20²).
|
|
10
11
|
- asChild?: boolean — renders as the child element (use to wrap <a>/<Link>)
|
|
11
12
|
- disabled?: boolean
|
|
12
13
|
- All native button HTML attrs (onClick, type, etc.)
|
|
13
|
-
when_to_use: Any clickable action. Use
|
|
14
|
+
when_to_use: Any clickable action. Use `iconOnly` for square icon-only buttons (at any size), variant="link" for inline links that should look like Button, the `raised` prop for high-commitment / weighty actions where the chrome can afford a tactile "physical key" treatment (composes with any variant; variant="raised" remains the neutral-key alias). A Button placed next to a TabsList of the same size lines up edge-to-edge without per-call overrides.
|
|
14
15
|
composes_with: [Dialog, DropdownMenu, Tooltip, Card (in CardFooter), Row, Form controls]
|
|
15
16
|
aliases: [button, push button, plain button, bordered button, destructive button, capsule button, link button, action button, cta, raised button, pill button, key button]
|
|
16
17
|
---
|
|
@@ -18,7 +19,8 @@ aliases: [button, push button, plain button, bordered button, destructive button
|
|
|
18
19
|
```jsx
|
|
19
20
|
<Button>Save</Button>
|
|
20
21
|
<Button variant="outline" size="sm">Cancel</Button>
|
|
21
|
-
<Button
|
|
22
|
+
<Button iconOnly variant="ghost"><Mail /></Button>
|
|
23
|
+
<Button size="sm" iconOnly variant="outline"><Plus /></Button>
|
|
22
24
|
```
|
|
23
25
|
|
|
24
26
|
```jsx
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ColorPicker
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- value?: string | null — a Grade colour token NAME ("action/primary"), the literal "transparent", or null when nothing is picked
|
|
6
|
+
- onValueChange?: (value: string | null) => void — fired with the next value (token name, "transparent", or null)
|
|
7
|
+
- tokens?: { group, tokens }[] — token families offered in the list; defaults to the Grade semantic set (surface / action / status)
|
|
8
|
+
- searchable?: boolean — show the search input (default true)
|
|
9
|
+
- triggerVariant? (default | inline) — default = form-control surface (swatch + name); inline = just a clickable swatch for inspector / fill-row use
|
|
10
|
+
- placeholder?: string — trigger text when nothing is selected
|
|
11
|
+
- searchPlaceholder?: string — search-input placeholder
|
|
12
|
+
- emptyMessage?: string — shown when search returns no rows
|
|
13
|
+
- allowTransparent?: boolean — include a Transparent option at the top (default true)
|
|
14
|
+
- align? (start | center | end) — popover alignment (default start)
|
|
15
|
+
- disabled?: boolean — lock to a read-only display of the current value
|
|
16
|
+
when_to_use: The token-led single-select colour picker — the focused "pick one colour token" sibling of FillPicker's solid tab. Use it anywhere a value is ONE Grade colour token (a fill colour, a border colour, an accent override) rather than a full paint. Composes Popover + Command exactly like Combobox, but each row is a Swatch + the token's short name, grouped by family and searchable. triggerVariant="inline" reduces the trigger to a single clickable swatch — reach for that inside inspectors and the FillSection fill rows. For a full paint (gradient / image / shader) use FillPicker; for a list of fills use FillSection; for a multi-stop gradient use GradientEditor.
|
|
17
|
+
composes_with: [Popover, Command, Swatch, FillSection, GradientEditor, Field, PropertyList]
|
|
18
|
+
aliases: [color picker, colour picker, token picker, colour token picker, color token picker, swatch picker, paint colour, fill colour picker, accent picker, colour dropdown]
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
```jsx
|
|
22
|
+
// Token-led colour field.
|
|
23
|
+
<ColorPicker value={color} onValueChange={setColor} />
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```jsx
|
|
27
|
+
// Inline swatch trigger — the inspector / fill-row affordance.
|
|
28
|
+
<ColorPicker
|
|
29
|
+
triggerVariant="inline"
|
|
30
|
+
value={stopColor}
|
|
31
|
+
onValueChange={setStopColor}
|
|
32
|
+
aria-label="Stop colour"
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Combobox
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- options: { value, label, icon?, keywords?, disabled? }[] — the selectable pool
|
|
6
|
+
- value?: string | null — controlled selection (wire onValueChange)
|
|
7
|
+
- defaultValue?: string | null — uncontrolled initial selection
|
|
8
|
+
- onValueChange?: (next: string | null) => void — fired with the next value, or null when cleared
|
|
9
|
+
- placeholder?: string — trigger text when nothing is selected
|
|
10
|
+
- searchPlaceholder?: string — search-input placeholder
|
|
11
|
+
- emptyMessage?: string — shown when search returns no rows
|
|
12
|
+
- searchable?: boolean — show the search input (default true)
|
|
13
|
+
- clearable?: boolean — add a Clear row so the value can return to unset
|
|
14
|
+
- triggerVariant?: "default" | "inline" — default = form-control surface (like Select); inline = chrome-free token trigger
|
|
15
|
+
- renderValue?: (option) => ReactNode — render the selected value yourself (e.g. a Badge); falls back to icon + label
|
|
16
|
+
- hideChevron?: boolean — drop the trailing chevron (inline token look)
|
|
17
|
+
- disabled?: boolean — lock to a read-only display of the current value
|
|
18
|
+
- align?: "start" | "center" | "end" — popover alignment
|
|
19
|
+
when_to_use: Single-pick searchable picker — the single-select sibling of MultiSelect and the Linear "selectable badge" pattern (status / priority / assignee). Use triggerVariant="inline" with renderValue returning a Badge to make a value read as a clickable token that opens a command menu. For multiple selection use MultiSelect; for a small fixed list with no search use Select; for free-form command palettes use Command directly. Pass disabled (driven by a permission check) to show the value without letting the user edit it.
|
|
20
|
+
composes_with: [Popover, Command, Badge, Avatar, PropertyList, Table, Field]
|
|
21
|
+
aliases: [combobox, single select, searchable select, picker, status picker, priority picker, assignee picker, command select, autocomplete, dropdown select, selectable badge, inline select, token select, linear combobox]
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
```jsx
|
|
25
|
+
<Combobox
|
|
26
|
+
options={[
|
|
27
|
+
{ value: "low", label: "Low" },
|
|
28
|
+
{ value: "medium", label: "Medium" },
|
|
29
|
+
{ value: "high", label: "High" },
|
|
30
|
+
]}
|
|
31
|
+
defaultValue="low"
|
|
32
|
+
placeholder="Set priority"
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```jsx
|
|
37
|
+
// Linear-style: the value IS the trigger.
|
|
38
|
+
<Combobox
|
|
39
|
+
triggerVariant="inline"
|
|
40
|
+
hideChevron
|
|
41
|
+
options={priorityOptions}
|
|
42
|
+
value={priority}
|
|
43
|
+
onValueChange={setPriority}
|
|
44
|
+
renderValue={(opt) => <Badge variant="warning-soft">{opt.label}</Badge>}
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: DataView
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- data: T[] — the rows
|
|
6
|
+
- columns: { key, header, type?, options?, cell?, role?, sortable?, pinned?, width?, align?, hideable?, defaultHidden? }[] — the schema; one list drives table, cards, and grid
|
|
7
|
+
- getRowId?: (row, i) => string — defaults to row.id
|
|
8
|
+
- view? / defaultView? / onViewChange?: "table" | "cards" | "grid" — controlled or uncontrolled view
|
|
9
|
+
- views?: ("table" | "cards" | "grid")[] — allowed views; one entry = single view, no toggle
|
|
10
|
+
- activeId? / defaultActiveId? / onActiveChange?: string | null — the selected row; click emits it
|
|
11
|
+
- sorting? / defaultSorting? / onSortingChange? — TanStack SortingState
|
|
12
|
+
- columnVisibility? / defaultColumnVisibility? / onColumnVisibilityChange? — which fields show
|
|
13
|
+
- stickyHeader?: boolean — freeze the header row on scroll
|
|
14
|
+
- toolbar?: boolean — render the built-in columns menu + view toggle above the view
|
|
15
|
+
- renderCard?: (row, { active }) => ReactNode — override card / grid tiles
|
|
16
|
+
- emptyMessage?: ReactNode
|
|
17
|
+
when_to_use: One dataset, drawn as a table, a list of cards, or a grid — without re-typing the TanStack boilerplate (sortable headers, flexRender, selection, view switch) on every page. Hand it data + a columns schema; columns declare a `type` (badge/tags/number/currency/percent/date/boolean/url/text) that DataView renders, with a `cell` override for bespoke cells (avatars, relations). The view toggle can live anywhere — `useDataView()` holds the state so a `<DataViewToggle>` or `<DataViewColumns>` in a page header drives a `<DataView>` lower down. Mark a column `pinned="left"` (with a `width`) for a fixed column and `stickyHeader` to freeze the header. For a single record's fields use PropertyList; for the raw table primitive use Table.
|
|
18
|
+
composes_with: [Table, Card, Badge, Avatar, ToggleGroup, DropdownMenu, PropertyList, Combobox]
|
|
19
|
+
aliases: [data view, data table, datatable, data grid, dataview, table view, card view, grid view, list view, gallery, records list, master list, tanstack table, sortable table, column visibility, pinned column, frozen column, sticky header, view switcher]
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
```jsx
|
|
23
|
+
const dv = useDataView({ defaultView: "table", defaultActiveId: rows[0].id });
|
|
24
|
+
|
|
25
|
+
// The toggle / columns menu can live anywhere — they just read dv.
|
|
26
|
+
<Row justify="between">
|
|
27
|
+
<h1>Alerts</h1>
|
|
28
|
+
<Row gap="sm">
|
|
29
|
+
<DataView.Columns columns={columns} visibility={dv.columnVisibility} onVisibilityChange={dv.setColumnVisibility} />
|
|
30
|
+
<DataView.Toggle value={dv.view} onChange={dv.setView} views={dv.views} />
|
|
31
|
+
</Row>
|
|
32
|
+
</Row>
|
|
33
|
+
|
|
34
|
+
<DataView
|
|
35
|
+
data={rows}
|
|
36
|
+
columns={columns}
|
|
37
|
+
view={dv.view}
|
|
38
|
+
activeId={dv.activeId}
|
|
39
|
+
onActiveChange={dv.setActiveId}
|
|
40
|
+
sorting={dv.sorting}
|
|
41
|
+
onSortingChange={dv.setSorting}
|
|
42
|
+
columnVisibility={dv.columnVisibility}
|
|
43
|
+
onColumnVisibilityChange={dv.setColumnVisibility}
|
|
44
|
+
stickyHeader
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```jsx
|
|
49
|
+
// Self-contained: built-in toolbar, single column pinned, table only.
|
|
50
|
+
<DataView
|
|
51
|
+
data={rows}
|
|
52
|
+
toolbar
|
|
53
|
+
columns={[
|
|
54
|
+
{ key: "name", header: "Name", role: "title", pinned: "left", width: 220 },
|
|
55
|
+
{ key: "status", header: "Status", type: "badge", options: statusOptions, sortable: true },
|
|
56
|
+
{ key: "arr", header: "ARR", type: "currency", align: "end", sortable: true },
|
|
57
|
+
]}
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: FillPicker
|
|
3
3
|
import: "@gradeui/ui"
|
|
4
|
+
subcomponents: [FillSection]
|
|
4
5
|
props:
|
|
5
6
|
- value: FillValue — current paint ({ type, color?, gradient?, src?, fit?, repeat?, tileSize?, preset?, palette?, postPreset?, opacity? }) (required)
|
|
6
7
|
- onChange: (value: FillValue) => void — called on any change (required)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
- FillSection: value — FillValue[] — the ordered list of fills to stack as rows
|
|
9
|
+
- FillSection: onChange — (value: FillValue[]) => void — fired with the next list on add / edit / remove / visibility toggle
|
|
10
|
+
- FillSection: title?: string — section heading (default "Fills")
|
|
11
|
+
when_to_use: Grade's paint picker — the control for choosing a frame's background fill, modelled on Figma's fill popover. A fill-type icon row (solid · gradient · image · pattern · video · shader) switches the panel below; a global opacity sits at the foot. Emits a FillValue that maps 1:1 onto BackgroundFill props. This is a Studio/inspector chrome control — pair it with BackgroundFill, which renders the chosen paint. Not for app content. Use the FillSection subcomponent to edit a LIST of fills (the Figma "Fill" inspector section): each row is a Solid/Gradient/Image toggle, the matching value control (ColorPicker / GradientEditor popover / image URL), an opacity %, a visibility eye, and a remove button, with an add button in the header.
|
|
12
|
+
composes_with: [BackgroundFill (renders the FillValue), Popover (host it in a popover), ColorPicker (the solid value), GradientEditor (the gradient value), ShaderPresetPicker (the shader tab), the inspector Fill section]
|
|
13
|
+
aliases: [fill picker, paint picker, background picker, fill chooser, fill popover, fill section, fill list, fills inspector, paint section]
|
|
10
14
|
notes: |
|
|
11
15
|
Grade is token-led, so the solid + gradient tabs lead with theme-token
|
|
12
16
|
swatches (`primary`, `accent`, `secondary`, `muted`, `card`,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: GradientEditor
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- value: { type, angle?, stops } — the structured gradient (type linear/radial/angular, optional angle in deg, ordered stops). NOT a CSS string — render the string via gradientToCss(value).
|
|
6
|
+
- onChange: (value) => void — fired with the next structured gradient on any edit
|
|
7
|
+
when_to_use: Edit a multi-stop CSS gradient with token-led stops. A type Select (Linear / Radial / Angular) with reverse + rotate actions, a live full-width preview bar (a Swatch type="gradient"), then a Stops list where each stop is a position %, a colour (ColorPicker token or raw), an opacity %, and a remove button; an add button appends a stop. Token stops resolve to oklch(var(--<token>)) so the preview re-voices with the theme. Emits the structured GradientValue (kept editable + serialisable); the caller turns it into CSS with the exported gradientToCss(value). Use inside a Popover from a FillSection gradient row, or standalone in a theme builder. For a single solid colour use ColorPicker; for a full paint (solid / gradient / image / shader) use FillPicker.
|
|
8
|
+
composes_with: [Select, Button, Input, ColorPicker, Swatch, Popover, FillSection]
|
|
9
|
+
aliases: [gradient editor, gradient picker, gradient builder, css gradient editor, stop editor, gradient stops, linear gradient editor, conic gradient editor]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
```jsx
|
|
13
|
+
<GradientEditor
|
|
14
|
+
value={{
|
|
15
|
+
type: "linear",
|
|
16
|
+
angle: 90,
|
|
17
|
+
stops: [
|
|
18
|
+
{ id: "a", position: 0, token: "action/primary", opacity: 1 },
|
|
19
|
+
{ id: "b", position: 100, token: "action/accent", opacity: 1 },
|
|
20
|
+
],
|
|
21
|
+
}}
|
|
22
|
+
onChange={setGradient}
|
|
23
|
+
/>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```jsx
|
|
27
|
+
// Render the CSS string for a background.
|
|
28
|
+
import { gradientToCss } from "@gradeui/ui";
|
|
29
|
+
<div style={{ background: gradientToCss(gradient) }} />
|
|
30
|
+
```
|
package/components/ui/map.md
CHANGED
|
@@ -90,3 +90,9 @@ ANTI-PATTERNS — don't do these:
|
|
|
90
90
|
- DO NOT render >500 markers without clustering. The component warns in dev. For larger datasets, drop to `.instance` and use the provider's clustering layer.
|
|
91
91
|
|
|
92
92
|
Markers are DOM — children inherit `--gds-*` tokens. Drop a `<Card>`, `<Badge>`, `<Avatar>`, or anything else inside `<MapMarker>` and it themes correctly.
|
|
93
|
+
|
|
94
|
+
Stacking inside a marker follows normal DOM order on every provider — you do NOT need `z-index` hacks to layer content (e.g. a count label sitting on top of an inline pin-shield `<svg>`). The DS neutralizes Leaflet's vendor rule that sets `z-index: 200` on map `<svg>` elements (via `[data-gds-part="map-marker-content"] svg { z-index: auto }` in `globals.css`); without it, an inline SVG would paint above later siblings on Leaflet (the default provider) but not on Mapbox/MapLibre/Google, making the same marker markup look provider-dependent. If you nest an inline SVG behind text in a marker, just rely on source order.
|
|
95
|
+
|
|
96
|
+
For a floating text label over busy tiles, add the `gds-map-label` class — it applies a mode-aware halo (`--gds-map-label-halo`: white stroke on light tiles, near-black on dark) so the label never washes out. Don't hard-code a white `-webkit-text-stroke`.
|
|
97
|
+
|
|
98
|
+
Note: `<Map>` carries no border-radius or border of its own — it's an unopinionated primitive (the container clips with `overflow: hidden`). Round or frame it from the call site with `className` (e.g. `rounded-xl border`).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: PropertyList
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- layout?: "row" | "stack" — row (default): label column beside value; stack: label above value for narrow panels
|
|
6
|
+
- density?: "compact" | "default" | "relaxed" — row rhythm
|
|
7
|
+
- align?: "start" | "center" — default vertical alignment of label vs value; use start when values wrap (tag groups, multi-line)
|
|
8
|
+
- divider?: boolean — hairline rule between rows
|
|
9
|
+
- labelWidth?: string — override the label column width (any CSS length); sets --gds-property-list-label-width
|
|
10
|
+
- children: PropertyList.Row[]
|
|
11
|
+
when_to_use: Read-only display of the properties of a SINGLE item — detail panels, inspectors, "about this" cards, order/record summaries. It is a Table row transposed (schema vertical, one record). The value side is a polymorphic slot, so the same renderers that fill a Table cell (text, Badge, tag group, Avatar stack, date, link) drop straight into a row. For an EDITABLE field (label + control) use Field instead; a panel that flips between read and edit swaps a PropertyList for a stack of Fields.
|
|
12
|
+
composes_with: [Badge, Avatar, Table, Field, Separator, Card]
|
|
13
|
+
aliases: [property list, properties, property panel, description list, definition list, detail list, key value, key-value, data list, field list, attributes, metadata list, record summary, detail panel, inspector fields, spec list]
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
```jsx
|
|
17
|
+
<PropertyList>
|
|
18
|
+
<PropertyList.Row label="Status" icon={<Activity />}>
|
|
19
|
+
<Badge variant="warning-soft">Low</Badge>
|
|
20
|
+
</PropertyList.Row>
|
|
21
|
+
<PropertyList.Row label="Published">2026-06-18</PropertyList.Row>
|
|
22
|
+
<PropertyList.Row label="Owner">
|
|
23
|
+
<Avatar className="h-5 w-5"><AvatarFallback>EO</AvatarFallback></Avatar>
|
|
24
|
+
</PropertyList.Row>
|
|
25
|
+
</PropertyList>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```jsx
|
|
29
|
+
<PropertyList density="compact" divider align="start">
|
|
30
|
+
<PropertyList.Row label="Topics">
|
|
31
|
+
<Row gap="xs" wrap>
|
|
32
|
+
<Badge variant="secondary">Pricing</Badge>
|
|
33
|
+
<Badge variant="secondary">Onboarding</Badge>
|
|
34
|
+
</Row>
|
|
35
|
+
</PropertyList.Row>
|
|
36
|
+
<PropertyList.Row label="Business profiles">
|
|
37
|
+
<Row gap="xs" wrap>
|
|
38
|
+
<Badge variant="outline">Acme</Badge>
|
|
39
|
+
<Badge variant="outline">Kite</Badge>
|
|
40
|
+
</Row>
|
|
41
|
+
</PropertyList.Row>
|
|
42
|
+
</PropertyList>
|
|
43
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Section
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
subcomponents: [Container, SectionEyebrow, SectionTitle, SectionSubtitle, SectionDescription, SectionActions, SectionMedia]
|
|
5
|
+
props:
|
|
6
|
+
- Section: scope? (default | inverse | brand | accent | muted | card) — colour SUBTHEME; applies the `scope-*` class so the whole band re-tones (bg/fg/card/muted/border) while action colours stay vivid. Unset = the page surface. See STUDIO-COLOR.md.
|
|
7
|
+
- Section: background?: ReactNode — visual band background slot: image / video / gradient / shader (drop a <BackgroundFill> here). Renders BEHIND the content; Section owns the relative/overflow/z plumbing. Works with `scope` (which re-tones the content tokens so text stays legible over the media).
|
|
8
|
+
- Section: pad? (none | sm | md | lg | xl) — vertical rhythm (responsive py); default lg. Section is ALWAYS full width — it never sets a max width.
|
|
9
|
+
- Section: as? (section | header | footer | div) — semantic element; default section.
|
|
10
|
+
- Container: maxW? (sm | md | lg | xl | prose | full) — centred max width + gutters; default lg. The MEASURE.
|
|
11
|
+
- Container: grid?: boolean — snap children to a 12-column grid (use `col-span-*` on children); default false.
|
|
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.
|
|
14
|
+
composes_with: [Container, MediaSurface, Carousel, VideoPlayer, Button, Badge, Card, Grid, Stack]
|
|
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
|
+
---
|
|
17
|
+
|
|
18
|
+
```jsx
|
|
19
|
+
// A page is a stack of Sections. Each band picks a scope; a Container
|
|
20
|
+
// holds the measure (omit it to let the band bleed full-width).
|
|
21
|
+
<Section scope="inverse" pad="xl">
|
|
22
|
+
<Container maxW="lg">
|
|
23
|
+
<SectionEyebrow>New</SectionEyebrow>
|
|
24
|
+
<SectionTitle>Use the agent you prefer.</SectionTitle>
|
|
25
|
+
<SectionSubtitle>Own the components. Ship on your subscription.</SectionSubtitle>
|
|
26
|
+
<SectionActions>
|
|
27
|
+
<Button size="lg">Open Studio</Button>
|
|
28
|
+
<Button size="lg" variant="outline">Docs</Button>
|
|
29
|
+
</SectionActions>
|
|
30
|
+
</Container>
|
|
31
|
+
</Section>
|
|
32
|
+
```
|
|
33
|
+
|
|
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.
|
|
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>
|
|
41
|
+
</Section>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```jsx
|
|
45
|
+
// Contained content on a grid — children snap to the 12-col Container grid.
|
|
46
|
+
<Section pad="lg">
|
|
47
|
+
<Container grid>
|
|
48
|
+
<div className="col-span-12 md:col-span-7">{/* lead */}</div>
|
|
49
|
+
<div className="col-span-12 md:col-span-5">{/* aside */}</div>
|
|
50
|
+
</Container>
|
|
51
|
+
</Section>
|
|
52
|
+
```
|
package/components/ui/swatch.md
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
name: Swatch
|
|
3
3
|
import: "@gradeui/ui"
|
|
4
4
|
subcomponents: [SwatchGroup]
|
|
5
|
-
sizes: [xs, sm, md, lg, xl]
|
|
5
|
+
sizes: [2xs, xs, sm, md, lg, xl]
|
|
6
6
|
props:
|
|
7
7
|
- color?: string — any raw CSS colour (`#1f6feb`, `oklch(...)`, `rgb(...)`, or `var(--x)`). Takes precedence over `token`. Use for one-off or external colours.
|
|
8
8
|
- token?: string — a Grade colour token NAME with no `--` and no `oklch()` wrap; resolved internally to `oklch(var(--<token>))`. THE design-system path — e.g. `token="brand-3"`, `token="primary"`, `token="chart-2"`. Re-voices live when the theme changes.
|
|
9
|
-
-
|
|
9
|
+
- type? (solid | gradient | image) — fill kind; default solid (or inferred from `image` / `gradient`). Determines what the chip renders in place.
|
|
10
|
+
- gradient?: string — CSS gradient for `type="gradient"`, e.g. `linear-gradient(135deg,#6366f1,#ec4899)`.
|
|
11
|
+
- image?: string — image URL for `type="image"`; rendered cover-fit behind the chip.
|
|
12
|
+
- size? (2xs | xs | sm | md | lg | xl) — t-shirt scale, 16px → 56px; default md (32px). 2xs (16px) suits dense colour lists. Prefer over h-*/w-* utilities.
|
|
10
13
|
- shape? (square | rounded | circle) — default rounded (rides `--radius`); circle for dot pickers; square for a hard tile.
|
|
11
14
|
- selected?: boolean — draws the shared selection ring (`--selected`). For palette / accent pickers.
|
|
12
15
|
- onSelect?: () => void — makes the swatch a pickable <button> (adds aria-pressed, focus ring, hover lift). Omit for a static display chip.
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
name: ToggleGroup
|
|
3
3
|
import: "@gradeui/ui"
|
|
4
4
|
subcomponents: [ToggleGroupItem]
|
|
5
|
-
variants: [default, outline]
|
|
6
|
-
sizes: [sm, md, lg]
|
|
5
|
+
variants: [default, outline, segmented]
|
|
6
|
+
sizes: [2xs, xs, sm, md, lg]
|
|
7
7
|
props:
|
|
8
8
|
- ToggleGroup: type: "single" | "multiple" — single picks one, multiple picks any number
|
|
9
9
|
- ToggleGroup: value?: string | string[] — controlled; matches `type` (string for single, string[] for multiple)
|
|
10
10
|
- ToggleGroup: defaultValue?: string | string[] — uncontrolled initial
|
|
11
11
|
- ToggleGroup: onValueChange?: (value: string | string[]) => void
|
|
12
|
-
- ToggleGroup: size? (sm | md | lg, default md) — cascades to every ToggleGroupItem via context, matches Tabs/Button heights
|
|
13
|
-
- ToggleGroup: variant? (default | outline)
|
|
12
|
+
- ToggleGroup: size? (2xs | xs | sm | md | lg, default md) — cascades to every ToggleGroupItem via context, matches Tabs/Button heights; 2xs/xs are the dense tool-panel sizes (2xs also drops text to text-2xs and icons to size-3 so labelled items read at panel density)
|
|
13
|
+
- ToggleGroup: variant? (default | outline | segmented) — segmented sits the items in a muted track with the active item as a soft raised pill, so it reads like a TabsList; reach for it in dense property panels (e.g. a Row/Stack direction toggle)
|
|
14
14
|
- ToggleGroupItem: value: string — what the group reports when this item is pressed
|
|
15
15
|
- ToggleGroupItem: tooltip?: ReactNode — when set, wraps the item in a Tooltip; required for icon-only items where the visible chrome doesn't carry a label
|
|
16
16
|
- ToggleGroupItem: tooltipSide? ("top" | "right" | "bottom" | "left", default "top") — side the tooltip renders on
|
|
@@ -41,3 +41,20 @@ aliases: [toggle group, segmented control, segmented buttons, button group, pill
|
|
|
41
41
|
<ToggleGroupItem value="underline" aria-label="Underline"><Underline /></ToggleGroupItem>
|
|
42
42
|
</ToggleGroup>
|
|
43
43
|
```
|
|
44
|
+
|
|
45
|
+
```jsx
|
|
46
|
+
// Segmented variant + 2xs — a dense property-panel toggle. Reads like a
|
|
47
|
+
// tab strip (muted track, active pill) but emits a value, so it's a form
|
|
48
|
+
// control, not panel-switching. This is the Studio Row/Stack control.
|
|
49
|
+
<ToggleGroup
|
|
50
|
+
type="single"
|
|
51
|
+
variant="segmented"
|
|
52
|
+
size="2xs"
|
|
53
|
+
value={direction}
|
|
54
|
+
onValueChange={(v) => v && setDirection(v)}
|
|
55
|
+
className="w-full"
|
|
56
|
+
>
|
|
57
|
+
<ToggleGroupItem value="row" className="flex-1"><Columns3 /> Row</ToggleGroupItem>
|
|
58
|
+
<ToggleGroupItem value="col" className="flex-1"><Rows3 /> Stack</ToggleGroupItem>
|
|
59
|
+
</ToggleGroup>
|
|
60
|
+
```
|