@uxelle/skills 0.2.0-beta.2 → 0.2.2
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/README.md +3 -5
- package/dist/index.js +98 -88
- package/index.json +98 -88
- package/package.json +1 -1
- package/skills/uxelle-components/ChoiceChip.md +1 -1
- package/skills/uxelle-components/ChoiceChipGroup.md +3 -3
- package/skills/uxelle-components/FilterChip.md +2 -2
- package/skills/uxelle-components/FilterChipGroup.md +1 -1
- package/skills/uxelle-components/Hero.md +2 -2
- package/skills/uxelle-components/Image.md +10 -3
- package/skills/uxelle-components/Logo.md +1 -1
- package/skills/uxelle-components/MultiSelect.md +3 -2
- package/skills/uxelle-components/SKILL.md +3 -1
- package/skills/uxelle-components/Select.md +2 -1
- package/skills/uxelle-components/Table.md +7 -4
- package/skills/uxelle-components/getting-started.md +54 -0
- package/skills/uxelle-design-harness/SKILL.md +44 -20
- package/skills/uxelle-design-harness/a2ui.md +22 -2
- package/skills/uxelle-design-harness/density.md +138 -0
- package/skills/uxelle-design-harness/how-to-accessibility.md +2 -0
- package/skills/uxelle-design-harness/how-to-color.md +6 -3
- package/skills/uxelle-design-harness/how-to-host.md +5 -22
- package/skills/uxelle-design-harness/how-to-page-layout.md +51 -6
- package/skills/uxelle-design-harness/principles.md +10 -6
- package/skills/uxelle-design-harness/recipe-app-chrome.md +20 -16
- package/skills/uxelle-design-harness/recipe-card-grid.md +14 -5
- package/skills/uxelle-design-harness/recipe-cta-band.md +16 -6
- package/skills/uxelle-design-harness/recipe-dashboard-overview.md +17 -5
- package/skills/uxelle-design-harness/recipe-data-table-page.md +30 -137
- package/skills/uxelle-design-harness/recipe-feature-section.md +9 -7
- package/skills/uxelle-design-harness/recipe-footer.md +7 -4
- package/skills/uxelle-design-harness/recipe-form-section.md +2 -2
- package/skills/uxelle-design-harness/recipe-hero.md +35 -15
- package/skills/uxelle-design-harness/recipe-landing-page.md +27 -4
- package/skills/uxelle-design-harness/recipe-logo-wall.md +11 -9
- package/skills/uxelle-design-harness/recipe-multi-step-flow.md +5 -3
- package/skills/uxelle-design-harness/recipe-page-header.md +14 -4
- package/skills/uxelle-design-harness/recipe-page-shell.md +2 -2
- package/skills/uxelle-design-harness/recipe-pricing.md +5 -2
- package/skills/uxelle-design-harness/recipe-query-bar.md +21 -5
- package/skills/uxelle-design-harness/recipe-record-detail.md +5 -2
- package/skills/uxelle-design-harness/recipe-settings-page.md +3 -3
- package/skills/uxelle-design-harness/recipe-stat-callouts.md +8 -3
- package/skills/uxelle-design-harness/recipe-states.md +9 -4
- package/skills/uxelle-design-harness/recipe-summary-list.md +5 -3
- package/skills/uxelle-design-harness/recipe-template.md +6 -0
- package/skills/uxelle-design-harness/recipe-testimonial.md +5 -3
- package/skills/uxelle-design-harness/spacing-steps.md +17 -8
- package/skills/uxelle-design-harness/tokens.md +45 -4
|
@@ -7,18 +7,25 @@ React app frame: skip link, [`Navigation`](../uxelle-components/Navigation.md),
|
|
|
7
7
|
## When not
|
|
8
8
|
|
|
9
9
|
- Not an `AppChrome` / `PageShell` component.
|
|
10
|
+
- **Chrome does not render the content column.** `<main>` here is only the
|
|
11
|
+
scrollport — the **experience** inside it owns the capped column
|
|
12
|
+
([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)). Do
|
|
13
|
+
not emit a `maxWidth` + `--uxl-breakpoints-margin` wrapper from this recipe *and*
|
|
14
|
+
from the experience recipe, or the page is padded and capped twice.
|
|
10
15
|
- Theme CSS and `data-*`: [how-to-host.md](how-to-host.md).
|
|
11
16
|
- A2UI chat surfaces: skip link, 100% viewport shell, and html `data-*` are host-owned. Do not emit this recipe as A2UI. Optional `A2uiNavigation` / `A2uiFooter` only when the host already has a surface.
|
|
12
17
|
|
|
13
18
|
## Regions
|
|
14
19
|
|
|
15
|
-
skip link → `Navigation` (header) and/or `NavigationSide` (side rail wrapping `NavigationSideGroup` rows) → `<main id="main-content">` → content column ([how-to-page-layout.md](how-to-page-layout.md)) → optional `Footer` **inside that same `<main>`**
|
|
20
|
+
skip link → `Navigation` (header) and/or `NavigationSide` (side rail wrapping `NavigationSideGroup` rows) → `<main id="main-content">` → *the experience's* content column ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)) → optional `Footer` **inside that same `<main>`**
|
|
16
21
|
|
|
17
22
|
## Spacing
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
Chrome is **density-invariant**: nav and footer are catalog components with their own tokens, and the margin / max-container-width on the experience's column never shift with the mode either. The experience picks the density mode and renders that column ([density.md](density.md)) — chrome does neither.
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
Nav is a catalog component (its own tokens). Main is the scrollport — **no page padding and no column cap here**; the experience's own column supplies breakpoint margin / max-container-width (one-column settings may use `600px` — [recipe-settings-page.md](recipe-settings-page.md)). When present, `Footer` sits after that column in the same scrollport.
|
|
27
|
+
|
|
28
|
+
Do not wrap `Footer` in extra `Layout` padding. Band padding is catalog-owned: two-band footers use `--uxl-breakpoints-padding-large-13`; a **single** band (legal-only, unused slot `null`) uses `--uxl-theme-layout-spacing-small-4` on the block axis. That is the **Control** step, not **Section** or **Band**.
|
|
22
29
|
|
|
23
30
|
## A11y
|
|
24
31
|
|
|
@@ -73,17 +80,14 @@ Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
|
|
|
73
80
|
/>
|
|
74
81
|
</Layout>
|
|
75
82
|
<main id="main-content" tabIndex={-1} style={{ flexGrow: 1, minHeight: 0, overflow: "auto" }}>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
>
|
|
85
|
-
{/* page body — recipe-page-shell.md / recipe-form-section.md / recipe-data-table-page.md / recipe-settings-page.md */}
|
|
86
|
-
</Layout>
|
|
83
|
+
{/*
|
|
84
|
+
The experience renders its own capped column here — chrome does not.
|
|
85
|
+
It owns maxWidth / mh / p / gap so it can set its density mode and, for a
|
|
86
|
+
one-column form or settings view, the 600px reading measure.
|
|
87
|
+
recipe-page-shell.md / recipe-dashboard-overview.md / recipe-data-table-page.md /
|
|
88
|
+
recipe-settings-page.md
|
|
89
|
+
*/}
|
|
90
|
+
<PageBody />
|
|
87
91
|
<Footer
|
|
88
92
|
topMainContent={null}
|
|
89
93
|
bottomMainContent={
|
|
@@ -108,7 +112,7 @@ When-not for this recipe. Host owns chrome (skip link, viewport shell, html `dat
|
|
|
108
112
|
|
|
109
113
|
## Help and marketing chrome
|
|
110
114
|
|
|
111
|
-
Use the same `Navigation` when there are **no** primary app links (help center, marketing). Pass `bottomSlot={null}` to hide the secondary bar. Unused **primary** slots must not be omitted — omitted (`undefined`)
|
|
115
|
+
Use the same `Navigation` when there are **no** primary app links (help center, marketing). Pass `bottomSlot={null}` to hide the secondary bar. Unused **primary** slots must not be omitted — an omitted (`undefined`) `leadingSlot` / `centerSlot` / `trailingSlot` fills catalog demo content. Pass **`null`** to drop a slot entirely (`centerSlot={null}` when there is no center nav) — the same pattern as `bottomSlot={null}`. Do not pass `false`: it clears the content but still renders an empty slot wrapper in the flex row.
|
|
112
116
|
|
|
113
117
|
Nav trailing (search, language, Log in / Register) stays a **row** at every width — [how-to-page-layout.md](how-to-page-layout.md#action-clusters). Do not stack those controls on mobile.
|
|
114
118
|
|
|
@@ -122,7 +126,7 @@ Nav trailing (search, language, Log in / Register) stays a **row** at every widt
|
|
|
122
126
|
<Logo name="generic" width={40} interactive aria-hidden />
|
|
123
127
|
</a>
|
|
124
128
|
}
|
|
125
|
-
centerSlot={
|
|
129
|
+
centerSlot={null}
|
|
126
130
|
trailingSlot={
|
|
127
131
|
<>
|
|
128
132
|
<LanguageSelector value="EN" />
|
|
@@ -35,13 +35,20 @@ minmax(MIN, 1fr))` drops from 3-up to 2-up to 1-up as width shrinks; pick `MIN`
|
|
|
35
35
|
|
|
36
36
|
## Spacing
|
|
37
37
|
|
|
38
|
-
`gap="var(--uxl-breakpoints-gutter)"`. Card inner padding is component-owned.
|
|
39
|
-
|
|
38
|
+
`gap="var(--uxl-breakpoints-gutter)"`. Card inner padding is component-owned. Both
|
|
39
|
+
are **density-invariant** — a card grid looks the same in every mode, so do not
|
|
40
|
+
tighten the gutter for a `compact` dashboard or pad it out for a marketing page
|
|
41
|
+
([density.md](density.md)). Stacks *inside* a card use the host's **Control** or
|
|
42
|
+
**Group** step. The page column still uses [how-to-page-layout.md](how-to-page-layout.md).
|
|
40
43
|
|
|
41
44
|
## A11y
|
|
42
45
|
|
|
43
46
|
Name the group when the cards are a set (`aria-label` on the grid `Layout`).
|
|
44
|
-
Headings inside cards continue page rank (`h2` under the page `h1`)
|
|
47
|
+
Headings inside cards continue page rank (`h2` under the page `h1`) but take the
|
|
48
|
+
type **one stop below** that rank's default, since the card already supplies the
|
|
49
|
+
separation — with `Display Extra Small` as the floor. A `spacious` card `h2` is
|
|
50
|
+
`Display Small`; a product card `h2` is already at the floor and stays
|
|
51
|
+
`Display Extra Small`, as shown ([density.md](density.md)). A `ProductCard`
|
|
45
52
|
CTA names itself; if the whole card is the link, the heading text is the name. See
|
|
46
53
|
[how-to-accessibility.md](how-to-accessibility.md).
|
|
47
54
|
|
|
@@ -100,5 +107,7 @@ Case-study / product tiles (`ProductCard`, media + CTA):
|
|
|
100
107
|
## A2UI
|
|
101
108
|
|
|
102
109
|
`A2uiLayout` has no `gridTemplateColumns` — stack `A2uiCard` / `A2uiProductCard` in
|
|
103
|
-
a column with `var(--uxl-theme-layout-spacing-*)` `gap`.
|
|
104
|
-
`
|
|
110
|
+
a column with `var(--uxl-theme-layout-spacing-*)` `gap`. Media in
|
|
111
|
+
`A2uiProductCard` `centerSlotContent` is `A2uiImage` with `aspectRatio` (omit
|
|
112
|
+
`width` so it fills the card). No `getUxelleRecipe("card-grid")`
|
|
113
|
+
([a2ui.md](a2ui.md)).
|
|
@@ -16,8 +16,10 @@ A single, decisive conversion moment: "Start free," "Talk to sales."
|
|
|
16
16
|
|
|
17
17
|
- One primary action (an optional secondary is fine) — not a menu of links.
|
|
18
18
|
- Section heading is `h2`, not another `h1`.
|
|
19
|
-
- Keep it to one dominant brand band per page
|
|
20
|
-
|
|
19
|
+
- Keep it to one dominant brand band per page, and make that concrete rather than
|
|
20
|
+
vague: if the hero already carries a brand palette, set this band to
|
|
21
|
+
`default-subtle`. Only one of the two gets a `brand-*` palette
|
|
22
|
+
([recipe-landing-page.md](recipe-landing-page.md)).
|
|
21
23
|
|
|
22
24
|
## Regions
|
|
23
25
|
|
|
@@ -33,8 +35,10 @@ viewports and stack `fullWidth` below tablet (`useBreakpointUp("tablet")`). See
|
|
|
33
35
|
|
|
34
36
|
## Spacing
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
**Band** step for the band's block padding and **Group** between the lockup and the
|
|
39
|
+
actions — `large-15` and `medium-10` on a `spacious` marketing page, its usual home
|
|
40
|
+
([density.md](density.md), [spacing-steps.md](spacing-steps.md)). A CTA closing a
|
|
41
|
+
`standard` product surface resolves to `large-13` / `medium-8`.
|
|
38
42
|
|
|
39
43
|
## A11y
|
|
40
44
|
|
|
@@ -50,20 +54,26 @@ role tokens — never hardcode hex. Pick the brand palette by the look you want
|
|
|
50
54
|
|
|
51
55
|
## React
|
|
52
56
|
|
|
57
|
+
`tabletUp` is the shared `useBreakpointUp("tablet")` helper — copy it from
|
|
58
|
+
[how-to-page-layout.md](how-to-page-layout.md#3-token-driven-js-narrow-fallback),
|
|
59
|
+
do not reimplement it or inline a px literal.
|
|
60
|
+
|
|
53
61
|
```tsx
|
|
62
|
+
const tabletUp = useBreakpointUp("tablet");
|
|
63
|
+
|
|
54
64
|
<Layout
|
|
55
65
|
as="section"
|
|
56
66
|
data-color-switcher="brand-1"
|
|
57
67
|
display="flex"
|
|
58
68
|
justifyContent="center"
|
|
59
|
-
p="var(--uxl-theme-layout-spacing-large-
|
|
69
|
+
p="var(--uxl-theme-layout-spacing-large-15)"
|
|
60
70
|
style={{ backgroundColor: "var(--uxl-color-switcher-background)" }}
|
|
61
71
|
>
|
|
62
72
|
<Layout
|
|
63
73
|
display="flex"
|
|
64
74
|
flexDirection="column"
|
|
65
75
|
alignItems="center"
|
|
66
|
-
gap="var(--uxl-theme-layout-spacing-medium-
|
|
76
|
+
gap="var(--uxl-theme-layout-spacing-medium-10)"
|
|
67
77
|
width="100%"
|
|
68
78
|
maxWidth="var(--uxl-breakpoints-max-container-width)"
|
|
69
79
|
>
|
|
@@ -36,10 +36,16 @@ when narrow. See [how-to-page-layout.md](how-to-page-layout.md#responsiveness).
|
|
|
36
36
|
|
|
37
37
|
## Spacing
|
|
38
38
|
|
|
39
|
-
`
|
|
40
|
-
|
|
39
|
+
**Density: `compact`** — a dashboard's job is to show more at a glance, so the
|
|
40
|
+
column tightens ([density.md](density.md)). **Section** gap between page sections
|
|
41
|
+
(header / KPIs / primary / supporting) is `medium-9`; subgroups inside a section
|
|
42
|
+
use **Group** (`small-6`); section intros use **Tight** (`micro-1`).
|
|
43
|
+
`--uxl-breakpoints-gutter` stays the grid gap in every mode
|
|
41
44
|
([spacing-steps.md](spacing-steps.md)).
|
|
42
45
|
|
|
46
|
+
Step up to `standard` when the overview is a light landing page with two or three
|
|
47
|
+
KPIs rather than a dense monitoring surface — but pick one mode and hold it.
|
|
48
|
+
|
|
43
49
|
## A11y
|
|
44
50
|
|
|
45
51
|
One `main`, one `h1`. Name each region (KPIs `aria-label="Key metrics"`, the primary
|
|
@@ -55,6 +61,7 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
|
|
|
55
61
|
## React
|
|
56
62
|
|
|
57
63
|
```tsx
|
|
64
|
+
// Density: compact (data-dense dashboard) — see density.md
|
|
58
65
|
<Layout
|
|
59
66
|
display="flex"
|
|
60
67
|
flexDirection="column"
|
|
@@ -62,7 +69,7 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
|
|
|
62
69
|
maxWidth="var(--uxl-breakpoints-max-container-width)"
|
|
63
70
|
mh="auto"
|
|
64
71
|
p="var(--uxl-breakpoints-margin)"
|
|
65
|
-
gap="var(--uxl-theme-layout-spacing-medium-
|
|
72
|
+
gap="var(--uxl-theme-layout-spacing-medium-9)"
|
|
66
73
|
>
|
|
67
74
|
{/* Page header — recipe-page-header.md */}
|
|
68
75
|
<DashboardHeader />
|
|
@@ -90,18 +97,23 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
|
|
|
90
97
|
<LabelBadge label="+4" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_up" />
|
|
91
98
|
}
|
|
92
99
|
/>
|
|
100
|
+
{/*
|
|
101
|
+
Palette tracks whether the change is *good*, not whether the number rose.
|
|
102
|
+
Fewer open tickets is an improvement, so this delta is `success` while
|
|
103
|
+
trending down — say so in the label rather than relying on the arrow.
|
|
104
|
+
*/}
|
|
93
105
|
<StatTile
|
|
94
106
|
label="Open tickets"
|
|
95
107
|
value="18"
|
|
96
108
|
trailingSlot
|
|
97
109
|
trailingSlotContent={
|
|
98
|
-
<LabelBadge label="
|
|
110
|
+
<LabelBadge label="6 fewer" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_down" />
|
|
99
111
|
}
|
|
100
112
|
/>
|
|
101
113
|
</Layout>
|
|
102
114
|
|
|
103
115
|
{/* Primary surface — recipe-data-table-page.md (with states) */}
|
|
104
|
-
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-
|
|
116
|
+
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
|
|
105
117
|
<Text type="Display Extra Small" as="h2" width={false}>Recent activity</Text>
|
|
106
118
|
{isLoading ? <ActivitySkeleton /> : rows.length ? <ActivityTable rows={rows} /> : <ActivityEmpty />}
|
|
107
119
|
</Layout>
|
|
@@ -11,7 +11,8 @@ Composes [`Table`](../uxelle-components/Table.md), [`TabsCompact`](../uxelle-com
|
|
|
11
11
|
- Not a `DataTablePage` / `TablePage` component.
|
|
12
12
|
- Do not invent a toolbar component. Two bands, two jobs — do not mix them:
|
|
13
13
|
- **Page actions** (add, invite, import, delete, re-upload) sit with the title, in a `ButtonGroup`.
|
|
14
|
-
- **Query controls** (filter, facets, search, export) sit on the table, in `Table` `topSlot`.
|
|
14
|
+
- **Query controls** (filter, facets, search, view-scoped export) sit on the table, in `Table` `topSlot`.
|
|
15
|
+
- **Export splits by scope — pick one.** "Export view" (what the filters leave on screen) rides the query bar; "Export all" (the whole dataset) is a page action beside the title ([recipe-page-header.md](recipe-page-header.md)). Never both on one page. This records page defaults to **Export view**.
|
|
15
16
|
- **Data tabs are not page navigation.** `Navigation` / `NavLink` change pages (Referrals vs Earnings). `TabsCompact` that switch which rows are in **this** table (referrals vs connections, active vs invited) live in `Table` `topSlot`, immediately above the query bar and grid. Do not park them under the page title.
|
|
16
17
|
- Do not put a `Banner` (or any other page section) between data tabs and the grid.
|
|
17
18
|
- `ButtonGroup` only renders `Button` children — do not nest `Sheet` or `Textfield` in it.
|
|
@@ -20,7 +21,7 @@ Composes [`Table`](../uxelle-components/Table.md), [`TabsCompact`](../uxelle-com
|
|
|
20
21
|
- Do not put table search in `Navigation`.
|
|
21
22
|
- `Lockup` and `Textfield` are `width: 100%` of the parent. Do not put them in a `flexWrap="wrap"` row with actions — they take the line and stack siblings underneath. Keep `flexWrap="nowrap"` (Layout default). Do **not** wrap `Lockup` in another `Layout` (`flexGrow` / `flexBasis={0}`) to sit beside a `ButtonGroup`. Do not put `minWidth` on the actions to invent a wrap threshold. Size a search field (`width="16rem"`, `flexShrink={0}`). Actions: `Layout` `flexShrink={0}` around the `ButtonGroup` only. Stack the header with `flexDirection="column"` below tablet.
|
|
22
23
|
- Table has no expandable / nested-row API. v1 is **flat rows**.
|
|
23
|
-
- A2UI: no `getUxelleRecipe("data-table-page")` yet. Compose `A2uiTable*` / `A2uiSheet` / `A2uiTabGroup` / `A2uiTextfield`. `Lockup` has no adapter — stack `A2uiText`. Layout-spacing only (no breakpoint chrome).
|
|
24
|
+
- A2UI: no `getUxelleRecipe("data-table-page")` yet. Compose `A2uiTable*` / `A2uiSheet` / `A2uiTabGroup` / `A2uiTextfield` / `A2uiChoiceChip` / `A2uiFilterChipGroup`. `Lockup` has no adapter — stack `A2uiText`. Layout-spacing only (no breakpoint chrome).
|
|
24
25
|
|
|
25
26
|
## Regions
|
|
26
27
|
|
|
@@ -30,13 +31,13 @@ page column → **header** (title left, page actions right) → optional `Banner
|
|
|
30
31
|
┌ Title + optional subtitle [Delete] [Upload] ┐
|
|
31
32
|
├ optional Banner ┤
|
|
32
33
|
├ Table.topSlot: Tabs (this dataset / that dataset) ┤
|
|
33
|
-
├
|
|
34
|
+
├ [Filter] [facet] [Search……] [Export view] ┤
|
|
34
35
|
├ [FilterChip] [FilterChip] ┤
|
|
35
36
|
├ Table.tableSlot: grid, or empty copy ┤
|
|
36
37
|
└ Table.bottomSlot: Pagination ┘
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
Page `gap` (`medium-
|
|
40
|
+
Page `gap` (the **Section** step — `medium-9` under this recipe's `compact` mode) is between **page sections** (header vs table). Inside `topSlot`, use the **Control** step (`micro-3`) so tabs sit on the table, not in a previous section.
|
|
40
41
|
|
|
41
42
|
## What goes where
|
|
42
43
|
|
|
@@ -49,13 +50,13 @@ Page `gap` (`medium-12`) is between **page sections** (header vs table). Inside
|
|
|
49
50
|
**Query bar — narrow the current view.** Next in `topSlot`, one row, `justifyContent="space-between"`:
|
|
50
51
|
|
|
51
52
|
- **Left — constrain:** Filter `Button` → `Sheet`. Name the trigger after a single dimension (`Status`, `Role`); use `Filter` when the sheet has several groups. Optional `ChoiceChip` for a sticky facet. Applied values: `FilterChipGroup` **under** this row.
|
|
52
|
-
- **Right — find:** search and utilities **on one line**. Wrapper around `Textfield` with a bounded `width` (e.g. `16rem`) and `flexShrink={0}`. `Export
|
|
53
|
+
- **Right — find:** search and utilities **on one line**. Wrapper around `Textfield` with a bounded `width` (e.g. `16rem`) and `flexShrink={0}`. `Export view` / Refresh as `emphasis="low"` `Button` siblings, cluster `flexWrap="nowrap"`. Not inside the Filter `ButtonGroup`. Default is `Export view` here. If the page needs the whole dataset instead, put `Export all` beside the title and omit this control — never both.
|
|
53
54
|
|
|
54
55
|
**Footer — page the result.** `Pagination` in `bottomSlot`.
|
|
55
56
|
|
|
56
57
|
## Spacing
|
|
57
58
|
|
|
58
|
-
Page column: [how-to-page-layout.md](how-to-page-layout.md). Header and query bar
|
|
59
|
+
**Density: `compact`** — a records page exists to show rows ([density.md](density.md)). Page column: [how-to-page-layout.md](how-to-page-layout.md). Header and query bar use the **Control** step (`micro-3`) with `space-between`; the `topSlot` stack (tabs → query bar → chips) uses the same **Control** step; the **Section** gap between page sections is `medium-9`. Do not wrap `Table` in extra padding — row height is component-owned and does not shift with the mode, so never claw back space by shrinking hit areas. Placement rules: [how-to-page-layout.md](how-to-page-layout.md#keep-controls-with-what-they-operate).
|
|
59
60
|
|
|
60
61
|
## A11y
|
|
61
62
|
|
|
@@ -69,6 +70,9 @@ Set `alignment` on **both** `TableHeaderCell` and `TableCell`:
|
|
|
69
70
|
- Money, counts, numeric → `right`
|
|
70
71
|
- Category / visibility / status (`LabelBadge`) → `center`
|
|
71
72
|
- A column that is only a CTA (`Button` / `IconButton` / `Link`) → `center`
|
|
73
|
+
- Selection / checkbox: `center`, and set `flex: 0` with `maxWidth` (e.g. 72) on that column track so leftover `fr` space does not stretch it on a wide viewport. `flex: 0` without a pixel `minWidth` or `maxWidth` sizes the track to content. `maxWidth` on a growing (`flex > 0`) column is a resize ceiling only.
|
|
74
|
+
|
|
75
|
+
Cell copy wraps and the row height follows the tallest cell. Header titles stay on one line and ellipsize when the column is narrower than the name (native tooltip shows the full title). Do not pass `truncation` on body `Text` unless a dense single-line column is required.
|
|
72
76
|
|
|
73
77
|
Identity cells: stack `Text type="Body Small Alt"` (name) and `Text type="Condensed"` (email) with `gap={0}`. Line boxes already separate the lines — do not add `micro-2` (or any layout-spacing) between them. Do not invent an Avatar component.
|
|
74
78
|
|
|
@@ -81,62 +85,27 @@ Keep `Table`, data tabs, and the query bar in `topSlot`. Put a `role="status"` s
|
|
|
81
85
|
## Responsive
|
|
82
86
|
|
|
83
87
|
- **Header** stacks below tablet: switch the header `Layout` `flexDirection` to `column` and let the `ButtonGroup` go `direction="column"` `fullWidth` (`useBreakpointUp("tablet")`, [how-to-page-layout.md](how-to-page-layout.md#action-clusters)).
|
|
84
|
-
- **Query bar** stays a row of controls; when very tight, the search field can take the full line above the filter/export cluster. Applied `FilterChipGroup` wraps.
|
|
85
|
-
- **Table** scrolls horizontally when narrower than its columns — there is no stacked-row API. Keep the query bar and tabs above it.
|
|
88
|
+
- **Query bar** stays a row of controls; when very tight, the search field can take the full line above the filter/export cluster. Applied `FilterChipGroup` wraps whole chips; a too-wide chip ellipsizes so dismiss stays visible.
|
|
89
|
+
- **Table** scrolls horizontally when narrower than its columns — there is no stacked-row API. Keep the query bar and tabs above it. Before any column is resized, header titles stay unwrapped and the grid’s min width is the sum of those titles (plus column mins). After a resize, every column is a pixel track at the widths on screen (siblings do not reflow to fill the container). Header titles in a squeezed track ellipsize. Call `useResetTableColumnWidths()` to restore every column to its template.
|
|
86
90
|
- **Pagination** swaps to [`PaginationSimple`](../uxelle-components/PaginationSimple.md) where the full control is too wide.
|
|
87
91
|
|
|
88
92
|
## React
|
|
89
93
|
|
|
90
|
-
Import from `@uxelle/components`. Data tabs and the query bar both go in `topSlot` — tabs first.
|
|
94
|
+
Import from `@uxelle/components`. Data tabs and the query bar both go in `topSlot` — tabs first.
|
|
95
|
+
|
|
96
|
+
`RecordsHeader` is [recipe-page-header.md](recipe-page-header.md) as-is, with two deltas for this page: the `h1` is `Display Small` and the row gap is the `compact` **Control** step (`micro-3`). Do not add `Export all` to that header when the query bar already has `Export view`. `TableQueryBar` is [recipe-query-bar.md](recipe-query-bar.md), with its gaps at `micro-3` and its export labelled `Export view`. Build both from their own recipes rather than copying them here — the rules that differ are in **Regions** above.
|
|
91
97
|
|
|
92
98
|
```tsx
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
flexWrap="nowrap"
|
|
98
|
-
justifyContent="space-between"
|
|
99
|
-
alignItems={tabletUp ? "flex-end" : "stretch"}
|
|
100
|
-
gap="var(--uxl-theme-layout-spacing-small-4)"
|
|
101
|
-
>
|
|
102
|
-
<Lockup
|
|
103
|
-
topSlot={false}
|
|
104
|
-
title={
|
|
105
|
-
<Text type="Display Extra Small" as="h1" width={false}>
|
|
106
|
-
Referrals
|
|
107
|
-
</Text>
|
|
108
|
-
}
|
|
109
|
-
body={
|
|
110
|
-
<Text type="Body Medium" width={false}>
|
|
111
|
-
Connections, matches, and potential earnings in one register.
|
|
112
|
-
</Text>
|
|
113
|
-
}
|
|
114
|
-
/>
|
|
115
|
-
<Layout flexShrink={0}>
|
|
116
|
-
<ButtonGroup
|
|
117
|
-
direction={tabletUp ? "row" : "column"}
|
|
118
|
-
fullWidth={!tabletUp}
|
|
119
|
-
aria-label="Referral actions"
|
|
120
|
-
>
|
|
121
|
-
<Button emphasis="medium">
|
|
122
|
-
<Text type="Button" width>
|
|
123
|
-
Delete
|
|
124
|
-
</Text>
|
|
125
|
-
</Button>
|
|
126
|
-
<Button emphasis="high">
|
|
127
|
-
<Text type="Button" width>
|
|
128
|
-
Upload
|
|
129
|
-
</Text>
|
|
130
|
-
</Button>
|
|
131
|
-
</ButtonGroup>
|
|
132
|
-
</Layout>
|
|
133
|
-
</Layout>
|
|
99
|
+
// Density: compact (records page — rows are the point) — see density.md
|
|
100
|
+
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
|
|
101
|
+
{/* Page header — recipe-page-header.md (h1 + collection actions; no Export all) */}
|
|
102
|
+
<RecordsHeader />
|
|
134
103
|
|
|
135
104
|
{/* optional Banner — here, not inside Table */}
|
|
136
105
|
|
|
137
106
|
<Table
|
|
138
107
|
topSlot={
|
|
139
|
-
<Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-
|
|
108
|
+
<Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
|
|
140
109
|
<TabsCompact
|
|
141
110
|
amount={2}
|
|
142
111
|
tabLabels={["Referrals (12)", "Connections (6)"]}
|
|
@@ -167,85 +136,6 @@ Import from `@uxelle/components`. Data tabs and the query bar both go in `topSlo
|
|
|
167
136
|
</Layout>
|
|
168
137
|
```
|
|
169
138
|
|
|
170
|
-
Query bar (filters left; search and export **nowrap** on the right):
|
|
171
|
-
|
|
172
|
-
```tsx
|
|
173
|
-
function TableQueryBar() {
|
|
174
|
-
return (
|
|
175
|
-
<Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
|
|
176
|
-
<Layout
|
|
177
|
-
display="flex"
|
|
178
|
-
flexDirection="row"
|
|
179
|
-
flexWrap="nowrap"
|
|
180
|
-
justifyContent="space-between"
|
|
181
|
-
alignItems="center"
|
|
182
|
-
gap="var(--uxl-theme-layout-spacing-small-4)"
|
|
183
|
-
aria-label="Table filters and search"
|
|
184
|
-
>
|
|
185
|
-
<Layout
|
|
186
|
-
display="flex"
|
|
187
|
-
flexDirection="row"
|
|
188
|
-
flexWrap="nowrap"
|
|
189
|
-
alignItems="center"
|
|
190
|
-
gap="var(--uxl-theme-layout-spacing-small-4)"
|
|
191
|
-
>
|
|
192
|
-
<Sheet
|
|
193
|
-
direction="Right"
|
|
194
|
-
title="Filters"
|
|
195
|
-
closeLabel="Close filters"
|
|
196
|
-
trigger={
|
|
197
|
-
<Button emphasis="medium" leadingIcon leadingIconName="filter_list">
|
|
198
|
-
<Text type="Button" width>
|
|
199
|
-
Status
|
|
200
|
-
</Text>
|
|
201
|
-
</Button>
|
|
202
|
-
}
|
|
203
|
-
>
|
|
204
|
-
<CheckboxGroup name="status" label labelText="Status">
|
|
205
|
-
<Checkbox value="open">
|
|
206
|
-
<Text type="Body Medium">Open</Text>
|
|
207
|
-
</Checkbox>
|
|
208
|
-
<Checkbox value="matched">
|
|
209
|
-
<Text type="Body Medium">Matched</Text>
|
|
210
|
-
</Checkbox>
|
|
211
|
-
</CheckboxGroup>
|
|
212
|
-
</Sheet>
|
|
213
|
-
<ChoiceChip label="Top match" onChange={() => {}} />
|
|
214
|
-
</Layout>
|
|
215
|
-
<Layout
|
|
216
|
-
display="flex"
|
|
217
|
-
flexDirection="row"
|
|
218
|
-
flexWrap="nowrap"
|
|
219
|
-
alignItems="center"
|
|
220
|
-
gap="var(--uxl-theme-layout-spacing-small-4)"
|
|
221
|
-
flexShrink={0}
|
|
222
|
-
>
|
|
223
|
-
<Layout width="16rem" flexShrink={0}>
|
|
224
|
-
<Textfield
|
|
225
|
-
label={false}
|
|
226
|
-
aria-label="Search by name"
|
|
227
|
-
name="table-search"
|
|
228
|
-
autoComplete="off"
|
|
229
|
-
placeholder="Search by name"
|
|
230
|
-
leadingIcon
|
|
231
|
-
leadingIconName="search"
|
|
232
|
-
/>
|
|
233
|
-
</Layout>
|
|
234
|
-
<Layout flexShrink={0}>
|
|
235
|
-
<Button emphasis="low">
|
|
236
|
-
<Text type="Button" width>
|
|
237
|
-
Export all
|
|
238
|
-
</Text>
|
|
239
|
-
</Button>
|
|
240
|
-
</Layout>
|
|
241
|
-
</Layout>
|
|
242
|
-
</Layout>
|
|
243
|
-
{/* Optional: FilterChipGroup of applied filters */}
|
|
244
|
-
</Layout>
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
139
|
Grid cells (alignment on header and body):
|
|
250
140
|
|
|
251
141
|
```tsx
|
|
@@ -266,7 +156,10 @@ Grid cells (alignment on header and body):
|
|
|
266
156
|
</TableCell>
|
|
267
157
|
```
|
|
268
158
|
|
|
269
|
-
Confirm / upload `Dialog` (page actions, not on the table)
|
|
159
|
+
Confirm / upload `Dialog` (page actions, not on the table). `tabletUp` is the shared
|
|
160
|
+
`useBreakpointUp("tablet")` helper — copy it from
|
|
161
|
+
[how-to-page-layout.md](how-to-page-layout.md#3-token-driven-js-narrow-fallback),
|
|
162
|
+
do not reimplement it or inline a px literal:
|
|
270
163
|
|
|
271
164
|
```tsx
|
|
272
165
|
<Dialog
|
|
@@ -296,11 +189,11 @@ Confirm / upload `Dialog` (page actions, not on the table):
|
|
|
296
189
|
</Text>
|
|
297
190
|
}
|
|
298
191
|
bottomSlotContent={
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
192
|
+
<ButtonGroup
|
|
193
|
+
direction={tabletUp ? "row" : "column"}
|
|
194
|
+
fullWidth={!tabletUp}
|
|
195
|
+
aria-label="Delete confirmation"
|
|
196
|
+
>
|
|
304
197
|
<Button emphasis="low" size="medium" onClick={() => setDeleteOpen(false)}>
|
|
305
198
|
<Text type="Button" width>
|
|
306
199
|
Cancel
|
|
@@ -320,4 +213,4 @@ Upload dialogs put `FileUpload` `fullWidth` in `centerSlotContent`. Title `Text`
|
|
|
320
213
|
|
|
321
214
|
## A2UI
|
|
322
215
|
|
|
323
|
-
Same region order and layout-spacing strings. Bind row data and search/filter state. Runtime: no `getUxelleRecipe("data-table-page")` yet — compose `A2uiTable*` / `A2uiSheet` / `A2uiTabGroup` / `A2uiTextfield` ([a2ui.md](a2ui.md)). `Lockup` has no adapter — stack `A2uiText`. `A2uiText` in `A2uiDialog` slots uses `"width": false`.
|
|
216
|
+
Same region order and layout-spacing strings. Bind row data and search/filter state. Runtime: no `getUxelleRecipe("data-table-page")` yet — compose `A2uiTable*` / `A2uiSheet` / `A2uiTabGroup` / `A2uiTextfield` / `A2uiChoiceChip` / `A2uiFilterChipGroup` ([a2ui.md](a2ui.md)). `Lockup` has no adapter — stack `A2uiText`. `A2uiText` in `A2uiDialog` slots uses `"width": false`. Chip labels stay on the adapter (`label`); do not nest `A2uiText` inside chips. Exclusive `A2uiChoiceChip` selection is one checked value in host state; use `A2uiRadioGroup` or `A2uiSegmentedControl` for radio semantics.
|
|
@@ -35,9 +35,10 @@ Two columns from tablet up, single column (media over copy) below — use
|
|
|
35
35
|
|
|
36
36
|
## Spacing
|
|
37
37
|
|
|
38
|
-
`--uxl-breakpoints-gutter` between the columns
|
|
39
|
-
sections
|
|
40
|
-
|
|
38
|
+
`--uxl-breakpoints-gutter` between the columns in every mode. **Section** step
|
|
39
|
+
between stacked feature sections and the **Band** step if the section is its own
|
|
40
|
+
band — `large-14` and `large-15` on the `spacious` marketing page where these live
|
|
41
|
+
([density.md](density.md), [spacing-steps.md](spacing-steps.md)).
|
|
41
42
|
|
|
42
43
|
## A11y
|
|
43
44
|
|
|
@@ -61,7 +62,7 @@ const tabletUp = useBreakpointUp("tablet");
|
|
|
61
62
|
>
|
|
62
63
|
<Lockup
|
|
63
64
|
overline={<Text type="Overline" width={false}>Automation</Text>}
|
|
64
|
-
title={<Text type="Display
|
|
65
|
+
title={<Text type="Display Medium" as="h2" width={false}>Ship without the busywork</Text>}
|
|
65
66
|
body={<Text type="Body Medium" width={false}>Rules handle the repetitive steps so your team ships.</Text>}
|
|
66
67
|
/>
|
|
67
68
|
<Image src="/features/automation.png" alt="Automation rules editor" aspectRatio="4:3" />
|
|
@@ -74,7 +75,7 @@ alternates sides.
|
|
|
74
75
|
Switched variant (capabilities behind a control):
|
|
75
76
|
|
|
76
77
|
```tsx
|
|
77
|
-
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-medium-
|
|
78
|
+
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-medium-10)">
|
|
78
79
|
<SegmentedControl
|
|
79
80
|
labelText="Capabilities"
|
|
80
81
|
value={feature}
|
|
@@ -92,5 +93,6 @@ Switched variant (capabilities behind a control):
|
|
|
92
93
|
## A2UI
|
|
93
94
|
|
|
94
95
|
No `getUxelleRecipe("feature-section")` and no `gridTemplateColumns` — stack
|
|
95
|
-
`A2uiText` copy then `A2uiImage`
|
|
96
|
-
`A2uiSegmentedControl` or
|
|
96
|
+
`A2uiText` copy then `A2uiImage` (`aspectRatio` to crop; omit `width` so it fills
|
|
97
|
+
the column) in one column; the switched variant uses `A2uiSegmentedControl` or
|
|
98
|
+
`A2uiTabGroup` ([a2ui.md](a2ui.md)).
|
|
@@ -107,10 +107,13 @@ leads. The legal bar wraps its links. See
|
|
|
107
107
|
|
|
108
108
|
## Spacing
|
|
109
109
|
|
|
110
|
-
`--uxl-breakpoints-gutter` between columns;
|
|
111
|
-
inside the brand block and the legal bar
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
`--uxl-breakpoints-gutter` between columns; the **Tight** step within a link list;
|
|
111
|
+
the **Control** step inside the brand block and the legal bar (`micro-2` / `small-4`
|
|
112
|
+
under `standard`) — [density.md](density.md).
|
|
113
|
+
|
|
114
|
+
Band padding is **component-owned and density-invariant**: a single remaining band
|
|
115
|
+
is tight (`small-4`), two bands use the large step. Do not wrap `Footer` in extra
|
|
116
|
+
`p` / `gap` to hit a density mode.
|
|
114
117
|
|
|
115
118
|
## A11y
|
|
116
119
|
|
|
@@ -23,9 +23,9 @@ Any catalog field: [`Textfield`](../uxelle-components/Textfield.md), [`Textarea`
|
|
|
23
23
|
|
|
24
24
|
## Spacing
|
|
25
25
|
|
|
26
|
-
`gap="var(--uxl-theme-layout-spacing-medium-8)"`
|
|
26
|
+
**Density: `standard`** — a form is read and filled, so it stays legible; a form embedded in a `compact` console may take that mode, but never tighten a public marketing form ([density.md](density.md)). The **Group** step goes between fields (or field **rows**) — `gap="var(--uxl-theme-layout-spacing-medium-8)"` under `standard`. Header stack uses **Tight** (`var(--uxl-theme-layout-spacing-micro-2)`). Parts of one value on one row use **Control** (`small-4`) inside the grid. Actions in `ButtonGroup` (not ad-hoc `Layout` for two buttons) — row from tablet up, column below tablet ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)).
|
|
27
27
|
|
|
28
|
-
**Separate the actions from the fields.** The submit / action cluster is a decision point, not one more field — give it
|
|
28
|
+
**Separate the actions from the fields.** The submit / action cluster is a decision point, not one more field — give it the **Section** step from the last field (`medium-12` under `standard`), not the **Group** field gap. At the field gap the primary button reads as grouped with the control above it (e.g. a submit sitting right under the last `Select`). Group the heading + fields in one stack and make the actions a sibling at the **Section** step (see the React example). This separation must survive every mode: under `compact` it is `medium-9` against a `small-6` field gap — still the required ~1.5x apart.
|
|
29
29
|
|
|
30
30
|
## Measure
|
|
31
31
|
|