@gradeui/ui 3.0.0 → 3.2.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 +11 -7
- package/components/ui/combobox.md +46 -0
- package/components/ui/data-view.md +59 -0
- package/components/ui/dropdown-menu.md +1 -0
- package/components/ui/logo.md +8 -6
- package/components/ui/map.md +9 -0
- package/components/ui/media-surface.md +1 -0
- package/components/ui/property-list.md +43 -0
- package/components/ui/sidebar.md +2 -1
- package/components/ui/swatch.md +88 -0
- 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 +902 -415
- package/dist/index.d.ts +902 -415
- package/dist/index.js +609 -72
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +609 -72
- package/dist/index.mjs.map +1 -1
- package/dist/map/google.d.mts +1 -1
- package/dist/map/google.d.ts +1 -1
- package/dist/map/google.js +1 -1
- package/dist/map/google.js.map +1 -1
- package/dist/map/google.mjs +1 -1
- package/dist/map/google.mjs.map +1 -1
- package/dist/map/leaflet.d.mts +1 -1
- package/dist/map/leaflet.d.ts +1 -1
- package/dist/map/leaflet.js +2 -2
- package/dist/map/leaflet.js.map +1 -1
- package/dist/map/leaflet.mjs +2 -2
- package/dist/map/leaflet.mjs.map +1 -1
- package/dist/map/mapbox.d.mts +1 -1
- package/dist/map/mapbox.d.ts +1 -1
- package/dist/map/mapbox.js +2 -2
- package/dist/map/mapbox.js.map +1 -1
- package/dist/map/mapbox.mjs +2 -2
- package/dist/map/mapbox.mjs.map +1 -1
- package/dist/map/maplibre.d.mts +1 -1
- package/dist/map/maplibre.d.ts +1 -1
- package/dist/map/maplibre.js +1 -1
- package/dist/map/maplibre.js.map +1 -1
- package/dist/map/maplibre.mjs +1 -1
- package/dist/map/maplibre.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/{types-BxywIwvG.d.mts → types-B45Uirkp.d.mts} +23 -0
- package/dist/{types-BxywIwvG.d.ts → types-B45Uirkp.d.ts} +23 -0
- package/package.json +2 -1
- package/styles/globals.css +306 -95
package/components/ui/button.md
CHANGED
|
@@ -4,12 +4,13 @@ import: "@gradeui/ui"
|
|
|
4
4
|
variants: [default, destructive, outline, secondary, ghost, link, raised]
|
|
5
5
|
sizes: [sm, md, lg, icon]
|
|
6
6
|
props:
|
|
7
|
-
- variant? (default | destructive | outline | secondary | ghost | link | raised)
|
|
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
|
+
- 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)" }}
|
|
8
9
|
- size? (sm | md | lg | icon) — t-shirt scale aligned with Tabs/ToggleGroup heights (sm=h-7, md=h-8, lg=h-10). `default` still works as an alias for `md`.
|
|
9
10
|
- asChild?: boolean — renders as the child element (use to wrap <a>/<Link>)
|
|
10
11
|
- disabled?: boolean
|
|
11
12
|
- All native button HTML attrs (onClick, type, etc.)
|
|
12
|
-
when_to_use: Any clickable action. Use size="icon" for square icon-only buttons, variant="link" for inline links that should look like Button,
|
|
13
|
+
when_to_use: Any clickable action. Use size="icon" for square icon-only buttons, 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.
|
|
13
14
|
composes_with: [Dialog, DropdownMenu, Tooltip, Card (in CardFooter), Row, Form controls]
|
|
14
15
|
aliases: [button, push button, plain button, bordered button, destructive button, capsule button, link button, action button, cta, raised button, pill button, key button]
|
|
15
16
|
---
|
|
@@ -32,16 +33,19 @@ aliases: [button, push button, plain button, bordered button, destructive button
|
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
```jsx
|
|
35
|
-
// Raised
|
|
36
|
+
// Raised TRAIT — tactile bevel + drop shadow + ambient hover glow,
|
|
37
|
+
// layered onto any variant (raised primary, raised outline, ...).
|
|
36
38
|
// Composed from the Presence elevation tokens (--elevation-3 rest,
|
|
37
39
|
// --elevation-hot hover, --elevation-pressed active). Tone is driven
|
|
38
40
|
// by --btn-glow, which defaults to --selected-glow (blue). Override
|
|
39
41
|
// per-button for "traffic light" semantics:
|
|
40
42
|
<Row gap="sm">
|
|
41
|
-
<Button
|
|
43
|
+
<Button raised>Raised primary</Button>
|
|
44
|
+
<Button variant="outline" raised>Raised outline</Button>
|
|
45
|
+
<Button raised style={{ "--btn-glow": "var(--warning)" }}>
|
|
42
46
|
Iterate
|
|
43
47
|
</Button>
|
|
44
|
-
<Button
|
|
48
|
+
<Button raised style={{ "--btn-glow": "var(--success)" }}>
|
|
45
49
|
Ship it
|
|
46
50
|
</Button>
|
|
47
51
|
</Row>
|
|
@@ -51,13 +55,13 @@ aliases: [button, push button, plain button, bordered button, destructive button
|
|
|
51
55
|
// data-state="on" / aria-pressed="true" gives the held-down "key
|
|
52
56
|
// pressed" look — picks up the --selected blue stroke + heat-inner
|
|
53
57
|
// glow. Works as a Toggle/ToggleGroupItem child via asChild.
|
|
54
|
-
<Button
|
|
58
|
+
<Button raised data-state="on">Locked</Button>
|
|
55
59
|
```
|
|
56
60
|
|
|
57
61
|
```jsx
|
|
58
62
|
// Combine with Aura for AI-attention states. The three Aura styles
|
|
59
63
|
// (ring/gradient/shimmer) stack independently of the variant.
|
|
60
|
-
<Button
|
|
64
|
+
<Button raised className="gds-aura-ring">
|
|
61
65
|
Studio is reviewing this
|
|
62
66
|
</Button>
|
|
63
67
|
```
|
|
@@ -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
|
+
```
|
|
@@ -13,6 +13,7 @@ props:
|
|
|
13
13
|
- DropdownMenuItem: onSelect?, disabled?, asChild?, inset?
|
|
14
14
|
- DropdownMenuCheckboxItem / DropdownMenuRadioItem: checked? / value, onCheckedChange? / onValueChange? (radio is on the group)
|
|
15
15
|
- DropdownMenuSub / DropdownMenuSubTrigger / DropdownMenuSubContent: nested menu — sub-trigger shows children, sub-content holds the deeper items
|
|
16
|
+
- DropdownMenuSub: open?, defaultOpen?, onOpenChange? — nested-menu open state (Radix passthrough); pass `open` to compose a pre-opened submenu in static screens and captures
|
|
16
17
|
- DropdownMenuShortcut: children — right-aligned kbd hint
|
|
17
18
|
when_to_use: A small action menu attached to a trigger — overflow "…" buttons on cards, user-avatar menus in headers, "Insert" menus in editors. For a full searchable list, use Command. For ONE primary action plus a secondary, use a Button next to a smaller ghost Button instead of a dropdown.
|
|
18
19
|
composes_with: [Button (as trigger asChild), Avatar (user menu), Card (overflow on a tile), Tooltip (on the trigger)]
|
package/components/ui/logo.md
CHANGED
|
@@ -3,12 +3,14 @@ name: Logo
|
|
|
3
3
|
import: "@gradeui/ui"
|
|
4
4
|
subcomponents: []
|
|
5
5
|
props:
|
|
6
|
-
- sources?: LogoSources — artwork keyed by lockup then appearance:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
- sources?: LogoSources — artwork keyed by lockup then appearance: { square?: { light?, dark?, mono? }, horizontal?: {...}, icon?: {...} }. Each slot is any node (inline <svg>, <img>, component). OMIT ENTIRELY and the GRADE MARK renders (the square G-arrow, painted with currentColor so it sits correctly on any surface). A bare <Logo /> is always correct branding.
|
|
7
|
+
- size? ("sm" | "md" | "lg" | "xl") — height of the mark: 20 / 28 / 40 / 56px (a raw pixel number also works). Width is intrinsic (square/icon 1:1, horizontal keeps its ratio). Default "md"; use "sm" in dense rails and toolbars.
|
|
8
|
+
- lockup? ("square" | "horizontal" | "icon") — which shape to show; falls back to the next-best available artwork. Default "horizontal".
|
|
9
|
+
- mode? ("light" | "dark") — the background the logo SITS ON, selecting light/dark artwork. Explicit, not theme-coupled. Default "light".
|
|
10
|
+
- mono?: boolean — render the single-colour treatment; inherits currentColor from the parent. Default false.
|
|
11
|
+
- label?: string — accessible name (aria-label + role="img"); pair with decorative when the brand name is already beside it.
|
|
12
|
+
- decorative?: boolean — aria-hidden, no role; use when text nearby names the brand.
|
|
13
|
+
- href?: string — renders the logo as a link (logo-links-home).
|
|
12
14
|
- lockup?: "square" | "horizontal" | "icon" (default "horizontal")
|
|
13
15
|
- mode?: "light" | "dark" (default "light") — the background the logo sits on
|
|
14
16
|
- mono?: boolean (default false) — use the single-colour artwork (inherits currentColor)
|
package/components/ui/map.md
CHANGED
|
@@ -10,7 +10,10 @@ props:
|
|
|
10
10
|
- Map: bounds — `[[swLng, swLat], [neLng, neLat]]`. When set, takes precedence over center+zoom.
|
|
11
11
|
- Map: appearance — "light" | "dark" | "satellite" | "auto" (default "auto", follows GradeThemeProvider mode).
|
|
12
12
|
- Map: hoveredId — controlled string id, pairs with onHoveredIdChange. The matching MapMarker gets `data-gds-state="hovered"` automatically. This is how you build list ↔ map two-way sync.
|
|
13
|
+
- Map: onHoveredIdChange — `(id: string | null) => void`. The other half of the controlled hover pair: fires when the pointer enters/leaves a marker so the sibling list can highlight in step. Without this entry in the contract, screens using the documented two-way sync fail save validation.
|
|
13
14
|
- Map: interactive — false freezes pan/zoom, useful for static cards.
|
|
15
|
+
- Map: tools — "auto" (default, zoom buttons follow `interactive`) | "zoom" (force zoom buttons) | "none" (chrome-free map; attribution always stays — license). One vocabulary across all providers.
|
|
16
|
+
- Map: toolsPosition — "top-left" (default) | "top-right" | "bottom-left" | "bottom-right". Corner the tools dock to; use when a search bar or legend sits over the default corner.
|
|
14
17
|
- Map: onLoad(handle) / onError(error) — handle exposes flyTo, panTo, fitBounds, getCenter, getZoom, getBounds, instance.
|
|
15
18
|
- Map: tilerKey? — MapLibre only (provider="maplibre"). Optional everywhere: omit on `gradeui.com`/`localhost` and the referrer-locked demo key is used; set it only when embedding off-domain. The contract never requires it.
|
|
16
19
|
- Map: accessToken? — Mapbox only. Pass it whenever provider="mapbox" — the component itself enforces this at runtime (throws a clear `provider="mapbox" requires an accessToken prop` error via onError if missing). It is OPTIONAL in the contract on purpose, so the validator never demands it from maplibre/google maps.
|
|
@@ -87,3 +90,9 @@ ANTI-PATTERNS — don't do these:
|
|
|
87
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.
|
|
88
91
|
|
|
89
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`).
|
|
@@ -8,6 +8,7 @@ props:
|
|
|
8
8
|
- loading?: boolean — renders the muted skeleton overlay
|
|
9
9
|
- hint?: "album" | "portrait" | "landscape" | "poster" | "product" | "food" | "video" | "audio" | "embed" | "3d" | "generic" (default "generic") — picks the placeholder glyph + the default aspect + the future generation provider
|
|
10
10
|
- alt?: string — becomes the eventual `<img alt>`; also drives the placeholder caption and small-tier initials
|
|
11
|
+
- instanceId?: string — stable per-instance id stamped as `data-gds-instance-id`. Use when rendering MediaSurfaces from a data array (`.map(item => <MediaSurface instanceId={item.id} …/>)`): it's how Studio's selection + Fill flows tell one card apart from its siblings and patch only that entry's data
|
|
11
12
|
- source?: { kind, …per-kind fields } — structured metadata for the generation pipeline. Shapes per kind — album: { artist, title, year? } · poster: { title, year? } · portrait: { name?, role? } · landscape: { location?, mood? } · product: { name?, brand? } · food: { dish?, cuisine? } · generic: { prompt } · video/audio/embed/3d: no fields
|
|
12
13
|
- src?: string — when set, renders an `<img>` filling the slot via object-cover; the wrapper keeps its chrome
|
|
13
14
|
- glyph?: ReactNode — per-instance override of the hint-derived placeholder glyph (escape hatch for unusual slots)
|
|
@@ -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
|
+
```
|
package/components/ui/sidebar.md
CHANGED
|
@@ -11,7 +11,8 @@ props:
|
|
|
11
11
|
- SidebarHeader: any children — brand / logo / org switcher; hides nothing when collapsed (centred)
|
|
12
12
|
- SidebarContent: any children — scrollable body
|
|
13
13
|
- SidebarFooter: any children — user block, settings link, pinned chrome
|
|
14
|
-
- SidebarSection: title?: ReactNode — group label
|
|
14
|
+
- SidebarSection: title?: ReactNode — group label, tracking-wide muted styling; hidden when sidebar is collapsed. CASE: static (non-collapsible) headers historically render UPPERCASE (Notion / Linear / Slack-style "GAMES", "FAVORITES"); collapsible headers render the authored case. Control it explicitly with titleTransform.
|
|
15
|
+
- SidebarSection: titleTransform? ("uppercase" | "none") — title casing for BOTH header variants. "none" renders the authored case (sentence-case headers like a "Recents" list); "uppercase" forces the shouty group label. Unset = the per-variant legacy default above.
|
|
15
16
|
- SidebarSection: icon?: ReactNode — optional icon beside the title
|
|
16
17
|
- SidebarSection: trailing?: ReactNode — **action(s) on the right edge of the header** — the canonical "+" / "..." slot (Notion's "+ Add page" next to Pages, Linear's "+" next to Favorites, Slack's "+" next to Channels). Pointer events isolated so a Button here doesn't toggle collapse.
|
|
17
18
|
- SidebarSection: collapsible?: boolean — title acts as expand/collapse trigger with a **chevron indicator** (default true). Set `false` for a static, non-clickable header.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Swatch
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
subcomponents: [SwatchGroup]
|
|
5
|
+
sizes: [xs, sm, md, lg, xl]
|
|
6
|
+
props:
|
|
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
|
+
- 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
|
+
- size? (xs | sm | md | lg | xl) — t-shirt scale, 20px → 56px; default md (32px). Prefer over h-*/w-* utilities.
|
|
10
|
+
- shape? (square | rounded | circle) — default rounded (rides `--radius`); circle for dot pickers; square for a hard tile.
|
|
11
|
+
- selected?: boolean — draws the shared selection ring (`--selected`). For palette / accent pickers.
|
|
12
|
+
- onSelect?: () => void — makes the swatch a pickable <button> (adds aria-pressed, focus ring, hover lift). Omit for a static display chip.
|
|
13
|
+
- onColorChange?: (value: string) => void — makes the swatch an editable colour well: hosts a native `<input type="color">` (the OS picker) behind the DS chip and fires with the new `#rrggbb`. Presentation stays the chip, interaction stays native. Use for inspector / control-panel colour fields instead of styling a raw colour input. Takes precedence over `onSelect`.
|
|
14
|
+
- label?: ReactNode — caption rendered beneath the chip; also becomes the accessible name + tooltip.
|
|
15
|
+
- SwatchGroup: layout? (row | stack) — `row` (default) spaces chips out; `stack` overlaps them into a coin-stack (the theme-picker / "key colours" treatment, where each chip's ring reads as the separating edge).
|
|
16
|
+
- SwatchGroup: size? / shape? — cascade to every child Swatch so a strip stays consistent without repeating the prop.
|
|
17
|
+
- SwatchGroup: gap? (xs | sm | md | lg) — spacing between chips in `row` layout; default sm.
|
|
18
|
+
when_to_use: Showing a colour as a small chip — brand-pop strips, palette / accent pickers, theme previews, token galleries, "pick a colour" rows. Reach for `token` to bind to a live theme variable; `color` for raw values. A transparency checkerboard sits behind the fill so semi-transparent values read honestly.
|
|
19
|
+
composes_with: [Row (strip of swatches), Stack, Grid (palette wall), Field (as a colour-picker trailing slot), Card (in a theme-preview), RadioGroup (selectable accent set), Label]
|
|
20
|
+
aliases: [colour swatch, color swatch, colour chip, color chip, palette swatch, token swatch, brand pop, accent swatch, colour tile, color tile, paint chip, react native colour swatch]
|
|
21
|
+
notes: |
|
|
22
|
+
Anti-patterns to avoid:
|
|
23
|
+
|
|
24
|
+
- DO NOT hand-roll a colour chip as a bare `<div className="h-10 w-10 rounded">`
|
|
25
|
+
with an inline `style={{ background: ... }}`. That is exactly what
|
|
26
|
+
<Swatch> is — use it so the chip is selectable in Studio, sizes on
|
|
27
|
+
tokens, and gets the transparency checkerboard + selection ring for free.
|
|
28
|
+
- DO NOT wrap a token in oklch() yourself for the `token` prop —
|
|
29
|
+
pass the bare name. `token="brand-3"`, NOT `token="oklch(var(--brand-3))"`.
|
|
30
|
+
(If you already have a wrapped string, pass it as `color` instead.)
|
|
31
|
+
- DO NOT size with h-*/w-* utilities — use `size` so the scale stays on
|
|
32
|
+
the t-shirt tokens.
|
|
33
|
+
- DO NOT use Swatch for an avatar, status dot, or icon background. It is
|
|
34
|
+
specifically a COLOUR specimen. A status dot is a tiny Badge/indicator;
|
|
35
|
+
a person is an Avatar.
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
```jsx
|
|
39
|
+
// Brand-pop strip — eight live theme accents.
|
|
40
|
+
<SwatchGroup size="lg">
|
|
41
|
+
{[1, 2, 3, 4, 5, 6, 7, 8].map((n) => (
|
|
42
|
+
<Swatch key={n} token={`brand-${n}`} />
|
|
43
|
+
))}
|
|
44
|
+
</SwatchGroup>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```jsx
|
|
48
|
+
// Theme-picker "key colours" — overlapping coin-stack of circles.
|
|
49
|
+
<SwatchGroup layout="stack" shape="circle" size="md">
|
|
50
|
+
<Swatch token="background" />
|
|
51
|
+
<Swatch token="muted" />
|
|
52
|
+
<Swatch token="primary" />
|
|
53
|
+
<Swatch token="accent" />
|
|
54
|
+
</SwatchGroup>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```jsx
|
|
58
|
+
// Captioned token chips.
|
|
59
|
+
<Row gap="md" wrap>
|
|
60
|
+
<Swatch token="primary" label="Primary" />
|
|
61
|
+
<Swatch token="accent" label="Accent" />
|
|
62
|
+
<Swatch token="muted" label="Muted" />
|
|
63
|
+
</Row>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```jsx
|
|
67
|
+
// Pickable accent set — selection ring + button semantics.
|
|
68
|
+
<Row gap="sm">
|
|
69
|
+
{["brand-1", "brand-2", "brand-3", "brand-4"].map((t) => (
|
|
70
|
+
<Swatch
|
|
71
|
+
key={t}
|
|
72
|
+
token={t}
|
|
73
|
+
shape="circle"
|
|
74
|
+
selected={t === selectedToken}
|
|
75
|
+
onSelect={() => setSelectedToken(t)}
|
|
76
|
+
/>
|
|
77
|
+
))}
|
|
78
|
+
</Row>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```jsx
|
|
82
|
+
// Raw colour, including a semi-transparent value over the checkerboard.
|
|
83
|
+
<Row gap="sm">
|
|
84
|
+
<Swatch color="#1f6feb" />
|
|
85
|
+
<Swatch color="oklch(0.7 0.18 30)" />
|
|
86
|
+
<Swatch color="rgb(16 185 129 / 0.4)" />
|
|
87
|
+
</Row>
|
|
88
|
+
```
|