@uxelle/skills 0.2.1-beta.0 → 0.2.3
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 +106 -96
- package/index.json +106 -96
- 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/Icon.md +4 -4
- package/skills/uxelle-components/Image.md +10 -3
- package/skills/uxelle-components/MultiSelect.md +3 -2
- package/skills/uxelle-components/NavigationSide.md +3 -3
- package/skills/uxelle-components/NavigationSideItem.md +4 -4
- package/skills/uxelle-components/NavigationSideSubItem.md +3 -3
- package/skills/uxelle-components/SKILL.md +9 -2
- package/skills/uxelle-components/Select.md +2 -1
- package/skills/uxelle-components/StatTile.md +13 -42
- package/skills/uxelle-components/Table.md +7 -4
- package/skills/uxelle-components/getting-started.md +85 -0
- package/skills/uxelle-design-harness/SKILL.md +44 -20
- package/skills/uxelle-design-harness/a2ui.md +31 -2
- package/skills/uxelle-design-harness/density.md +138 -0
- package/skills/uxelle-design-harness/how-to-accessibility.md +3 -1
- package/skills/uxelle-design-harness/how-to-color.md +6 -3
- package/skills/uxelle-design-harness/how-to-host.md +8 -21
- 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 +21 -7
- 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 +14 -7
- 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
|
@@ -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
|
|
|
@@ -19,7 +19,8 @@ The first impression: one sentence of value, one clear action, and supporting me
|
|
|
19
19
|
- One `h1` for the whole page lives here — later sections are `h2`.
|
|
20
20
|
- Keep the media non-interactive; actions go in `contentSlot`.
|
|
21
21
|
- Pass media as a **direct** `Image` / `img` / `picture` / `video` — no wrappers
|
|
22
|
-
(Hero owns the crop;
|
|
22
|
+
(Hero owns the crop; nested `Image.aspectRatio` and box size — `width`,
|
|
23
|
+
`height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight` — have no effect).
|
|
23
24
|
|
|
24
25
|
## Regions
|
|
25
26
|
|
|
@@ -27,16 +28,24 @@ The first impression: one sentence of value, one clear action, and supporting me
|
|
|
27
28
|
|
|
28
29
|
## Responsive
|
|
29
30
|
|
|
30
|
-
`Hero` owns
|
|
31
|
-
up and drops to a full-width bottom panel below; `Bottom` is
|
|
32
|
-
size. The crop adapts automatically.
|
|
33
|
-
|
|
31
|
+
`Hero` owns the panel and crop: `contentDirection="Left" | "Right"` pins a side
|
|
32
|
+
panel on tablet and up and drops to a full-width bottom panel below; `Bottom` is
|
|
33
|
+
full-width at every size. The crop adapts automatically.
|
|
34
|
+
|
|
35
|
+
The actions are **not** component-owned. Two text `Button`s take the tablet switch
|
|
36
|
+
here exactly as anywhere else — `direction={tabletUp ? "row" : "column"}` with
|
|
37
|
+
`fullWidth={!tabletUp}`, not a judgement call about whether they "feel tight"
|
|
38
|
+
([how-to-page-layout.md](how-to-page-layout.md#action-clusters)). A hero with a
|
|
39
|
+
single action passes no `direction`.
|
|
34
40
|
|
|
35
41
|
## Spacing
|
|
36
42
|
|
|
37
|
-
Content-area spacing is component-owned
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
Content-area spacing is component-owned and **density-invariant** — `Hero` owns its
|
|
44
|
+
own band padding, so do not wrap it in compensating `p` to reach a mode. The hero is
|
|
45
|
+
a full-bleed band, so it sits outside the capped content column — following sections
|
|
46
|
+
resume the column at the page's **Section** gap (`large-14` on the `spacious`
|
|
47
|
+
marketing page where a hero lives) ([how-to-page-layout.md](how-to-page-layout.md),
|
|
48
|
+
[density.md](density.md)).
|
|
40
49
|
|
|
41
50
|
## A11y
|
|
42
51
|
|
|
@@ -53,7 +62,13 @@ dominant brand band; keep later sections calmer ([how-to-color.md](how-to-color.
|
|
|
53
62
|
|
|
54
63
|
## React
|
|
55
64
|
|
|
65
|
+
`tabletUp` is the shared `useBreakpointUp("tablet")` helper — copy it from
|
|
66
|
+
[how-to-page-layout.md](how-to-page-layout.md#3-token-driven-js-narrow-fallback),
|
|
67
|
+
do not reimplement it or inline a px literal.
|
|
68
|
+
|
|
56
69
|
```tsx
|
|
70
|
+
const tabletUp = useBreakpointUp("tablet");
|
|
71
|
+
|
|
57
72
|
<Hero
|
|
58
73
|
contentDirection="Left"
|
|
59
74
|
data-color-switcher="brand-1"
|
|
@@ -63,7 +78,7 @@ dominant brand band; keep later sections calmer ([how-to-color.md](how-to-color.
|
|
|
63
78
|
<Lockup
|
|
64
79
|
overline={<Text type="Overline" width={false}>New</Text>}
|
|
65
80
|
title={
|
|
66
|
-
<Text type="Display
|
|
81
|
+
<Text type="Display Large" as="h1" width={false}>
|
|
67
82
|
One workspace for every team
|
|
68
83
|
</Text>
|
|
69
84
|
}
|
|
@@ -73,9 +88,13 @@ dominant brand band; keep later sections calmer ([how-to-color.md](how-to-color.
|
|
|
73
88
|
</Text>
|
|
74
89
|
}
|
|
75
90
|
/>
|
|
76
|
-
<ButtonGroup
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
<ButtonGroup
|
|
92
|
+
direction={tabletUp ? "row" : "column"}
|
|
93
|
+
fullWidth={!tabletUp}
|
|
94
|
+
aria-label="Get started"
|
|
95
|
+
>
|
|
96
|
+
<Button emphasis="high"><Text type="Button" width>Start free</Text></Button>
|
|
97
|
+
<Button emphasis="medium"><Text type="Button" width>Book a demo</Text></Button>
|
|
79
98
|
</ButtonGroup>
|
|
80
99
|
</>
|
|
81
100
|
}
|
|
@@ -84,6 +103,7 @@ dominant brand band; keep later sections calmer ([how-to-color.md](how-to-color.
|
|
|
84
103
|
|
|
85
104
|
## A2UI
|
|
86
105
|
|
|
87
|
-
`A2uiHero` with a nested `A2uiImage`
|
|
88
|
-
|
|
89
|
-
`data-color-switcher`
|
|
106
|
+
`A2uiHero` with a nested `A2uiImage` (`src` + `alt` only — omit `aspectRatio` and
|
|
107
|
+
box size) and a `contentSlot` of stacked `A2uiText` (no `Lockup` adapter) plus
|
|
108
|
+
`A2uiButtonGroup`. Set the palette via the adapter's `data-color-switcher`
|
|
109
|
+
([a2ui.md](a2ui.md)).
|
|
@@ -41,8 +41,16 @@ clusters) — no page-level breakpoint logic. See
|
|
|
41
41
|
|
|
42
42
|
## Spacing
|
|
43
43
|
|
|
44
|
-
`
|
|
45
|
-
|
|
44
|
+
**Density: `spacious`** — generous whitespace is the argument on a public page, and
|
|
45
|
+
one idea should land per band ([density.md](density.md)). **Section** gap between
|
|
46
|
+
in-column sections is `large-14`; full-bleed bands take the **Band** step
|
|
47
|
+
(`large-15`, or `large-16` for a deliberate full-viewport hero statement);
|
|
48
|
+
`--uxl-breakpoints-gutter` stays the grid gap in every mode
|
|
49
|
+
([spacing-steps.md](spacing-steps.md)).
|
|
50
|
+
|
|
51
|
+
Hold one mode for the whole page. A dense piece inside it (a pricing comparison, an
|
|
52
|
+
embedded table) keeps its own internals tight but still sits at the page's
|
|
53
|
+
`large-14` **Section** gap — density describes the experience, not each component.
|
|
46
54
|
|
|
47
55
|
## A11y
|
|
48
56
|
|
|
@@ -56,9 +64,23 @@ One dominant brand band (usually the hero). Apply palettes per section with
|
|
|
56
64
|
`data-color-switcher`, style with role tokens, keep the calmer sections on `default`
|
|
57
65
|
/ `default-subtle` ([how-to-color.md](how-to-color.md)).
|
|
58
66
|
|
|
67
|
+
This page ships **two** full-bleed bands, so make the choice explicit rather than
|
|
68
|
+
letting both default to `brand-1`: the hero takes the brand palette and the closing
|
|
69
|
+
[cta-band](recipe-cta-band.md) drops to `default-subtle`. Flip it if the CTA is the
|
|
70
|
+
louder moment. What you must not do is set both to a brand palette — that is the
|
|
71
|
+
"stacked loud bands" this recipe's **when not** rules out, and it costs the hero its
|
|
72
|
+
dominance.
|
|
73
|
+
|
|
59
74
|
## React
|
|
60
75
|
|
|
76
|
+
`<main>` comes from [app-chrome](recipe-app-chrome.md) and is shown here only for
|
|
77
|
+
context — this recipe owns what goes *inside* it
|
|
78
|
+
([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)). Note
|
|
79
|
+
that a marketing page caps only its mid-page sections: the full-bleed bands are
|
|
80
|
+
deliberately outside any column.
|
|
81
|
+
|
|
61
82
|
```tsx
|
|
83
|
+
// Density: spacious (public marketing page) — see density.md
|
|
62
84
|
<main id="main-content" tabIndex={-1} style={{ flexGrow: 1, minHeight: 0, overflow: "auto" }}>
|
|
63
85
|
{/* Full-bleed hero — recipe-hero.md (contains the h1) */}
|
|
64
86
|
<MarketingHero />
|
|
@@ -71,7 +93,7 @@ One dominant brand band (usually the hero). Apply palettes per section with
|
|
|
71
93
|
maxWidth="var(--uxl-breakpoints-max-container-width)"
|
|
72
94
|
mh="auto"
|
|
73
95
|
p="var(--uxl-breakpoints-margin)"
|
|
74
|
-
gap="var(--uxl-theme-layout-spacing-
|
|
96
|
+
gap="var(--uxl-theme-layout-spacing-large-14)"
|
|
75
97
|
>
|
|
76
98
|
<LogoWall /> {/* recipe-logo-wall.md */}
|
|
77
99
|
<FeatureSections /> {/* recipe-feature-section.md (alternating) */}
|
|
@@ -80,7 +102,8 @@ One dominant brand band (usually the hero). Apply palettes per section with
|
|
|
80
102
|
<Pricing /> {/* recipe-pricing.md */}
|
|
81
103
|
</Layout>
|
|
82
104
|
|
|
83
|
-
{/* Full-bleed closing CTA — recipe-cta-band.md
|
|
105
|
+
{/* Full-bleed closing CTA — recipe-cta-band.md.
|
|
106
|
+
Hero above is the brand band, so this one runs default-subtle. */}
|
|
84
107
|
<CtaBand />
|
|
85
108
|
|
|
86
109
|
{/* Footer — recipe-footer.md (marketing: directory band + legal bar) */}
|
|
@@ -29,9 +29,10 @@ fit — no JS, no breakpoint literals. Bound each mark's `width` so rows stay ev
|
|
|
29
29
|
|
|
30
30
|
## Spacing
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
**Group** step between marks and **Control** under the eyebrow — `medium-10` and
|
|
33
|
+
`small-6` on the `spacious` marketing page where a trust bar lives (`medium-8` /
|
|
34
|
+
`small-4` under `standard`). As a band, add breathing with the **Band** step
|
|
35
|
+
(`large-15`) ([density.md](density.md), [spacing-steps.md](spacing-steps.md)).
|
|
35
36
|
|
|
36
37
|
## A11y
|
|
37
38
|
|
|
@@ -55,7 +56,7 @@ full-color marks on dark). Keep one dominant brand band per view
|
|
|
55
56
|
display="flex"
|
|
56
57
|
flexDirection="column"
|
|
57
58
|
alignItems="center"
|
|
58
|
-
gap="var(--uxl-theme-layout-spacing-small-
|
|
59
|
+
gap="var(--uxl-theme-layout-spacing-small-6)"
|
|
59
60
|
aria-label="Trusted by leading teams"
|
|
60
61
|
>
|
|
61
62
|
<Text type="Overline" width={false}>Trusted by teams at</Text>
|
|
@@ -64,17 +65,18 @@ full-color marks on dark). Keep one dominant brand band per view
|
|
|
64
65
|
flexWrap="wrap"
|
|
65
66
|
justifyContent="center"
|
|
66
67
|
alignItems="center"
|
|
67
|
-
gap="var(--uxl-theme-layout-spacing-medium-
|
|
68
|
+
gap="var(--uxl-theme-layout-spacing-medium-10)"
|
|
68
69
|
>
|
|
69
70
|
<Logo name="generic" width={112} color="colorSwitcherIcon" aria-label="Northwind" />
|
|
70
71
|
<Logo name="generic" width={112} color="colorSwitcherIcon" aria-label="Acme" />
|
|
71
|
-
{/* Logos not in the catalog use Image */}
|
|
72
|
-
<Image src="/logos/contoso.svg" alt="Contoso" />
|
|
72
|
+
{/* Logos not in the catalog use Image; bound height to match the row */}
|
|
73
|
+
<Image src="/logos/contoso.svg" alt="Contoso" height={40} />
|
|
73
74
|
</Layout>
|
|
74
75
|
</Layout>
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
## A2UI
|
|
78
79
|
|
|
79
|
-
No `getUxelleRecipe("logo-wall")` — compose `A2uiLogo` / `A2uiImage`
|
|
80
|
-
`
|
|
80
|
+
No `getUxelleRecipe("logo-wall")` — compose `A2uiLogo` (`width`) / `A2uiImage`
|
|
81
|
+
(`height` or `width` so marks stay even) inside an `A2uiLayout` (single wrapping
|
|
82
|
+
row, layout-spacing only) ([a2ui.md](a2ui.md)).
|
|
@@ -34,8 +34,10 @@ narrow. See [how-to-page-layout.md](how-to-page-layout.md#action-clusters).
|
|
|
34
34
|
|
|
35
35
|
## Spacing
|
|
36
36
|
|
|
37
|
-
`
|
|
38
|
-
|
|
37
|
+
**Density: `standard`** — a flow asks for one decision at a time, so keep it calm
|
|
38
|
+
even inside a `compact` app ([density.md](density.md)). **Section** gap between the
|
|
39
|
+
stepper, the step body, and the footer (`medium-12`); the step form uses its own
|
|
40
|
+
**Group** field stack (`medium-8`) ([spacing-steps.md](spacing-steps.md)).
|
|
39
41
|
|
|
40
42
|
## A11y
|
|
41
43
|
|
|
@@ -65,7 +67,7 @@ const isLast = step === steps.length;
|
|
|
65
67
|
p="var(--uxl-breakpoints-margin)"
|
|
66
68
|
gap="var(--uxl-theme-layout-spacing-medium-12)"
|
|
67
69
|
>
|
|
68
|
-
<Text type="Display
|
|
70
|
+
<Text type="Display Small" as="h1" width={false}>Create a workspace</Text>
|
|
69
71
|
|
|
70
72
|
<nav aria-label="Progress">
|
|
71
73
|
<Stepper
|
|
@@ -19,9 +19,16 @@ status, and the actions that operate on the whole page.
|
|
|
19
19
|
## When not
|
|
20
20
|
|
|
21
21
|
- Not a `PageHeader` component — it is a `Layout` composition.
|
|
22
|
-
- Page actions only (create, import,
|
|
22
|
+
- Page actions only (create, import, delete). Controls that filter a table
|
|
23
23
|
belong to the [query-bar](recipe-query-bar.md); tabs that swap a table's rows
|
|
24
24
|
belong on the `Table` ([data-table-page](recipe-data-table-page.md)).
|
|
25
|
+
- **Export splits by scope.** Export of the *whole dataset* is a page action and
|
|
26
|
+
belongs here — label it for that scope ("Export all"). Export of the *rows
|
|
27
|
+
currently in view* (after filters and search) belongs on the
|
|
28
|
+
[query-bar](recipe-query-bar.md), labelled for the view ("Export view"). Never
|
|
29
|
+
put both on one page: pick the scope the user actually needs. A
|
|
30
|
+
[data-table-page](recipe-data-table-page.md) defaults to "Export view" — omit
|
|
31
|
+
export from this header when that query bar is present.
|
|
25
32
|
- Do not wrap `Lockup` in a nested `Layout` to share the row — let it be the flex
|
|
26
33
|
sibling that shrinks and put `flexShrink={0}` on the action cluster.
|
|
27
34
|
|
|
@@ -40,9 +47,12 @@ would claim the row and drop the actions).
|
|
|
40
47
|
|
|
41
48
|
## Spacing
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
(
|
|
45
|
-
([spacing-steps.md](spacing-steps.md)).
|
|
50
|
+
**Control** step between title and actions. Lockup internals are component-owned
|
|
51
|
+
(`micro-2`) and do not shift with the mode. The header sits in the page column at
|
|
52
|
+
the **Section** gap ([spacing-steps.md](spacing-steps.md)).
|
|
53
|
+
|
|
54
|
+
Inherits the host experience's mode — `micro-3` / `medium-9` under `compact`,
|
|
55
|
+
`small-4` / `medium-12` under `standard` ([density.md](density.md)).
|
|
46
56
|
|
|
47
57
|
## A11y
|
|
48
58
|
|
|
@@ -19,7 +19,7 @@ host canvas → content column (`max-container-width`, `mh="auto"`, `p` = breakp
|
|
|
19
19
|
|
|
20
20
|
## Spacing
|
|
21
21
|
|
|
22
|
-
React page column: [how-to-page-layout.md](how-to-page-layout.md). Section stacks
|
|
22
|
+
**Density: `standard`** unless the body is data-dense (then `compact`) or public marketing (then `spacious`) — [density.md](density.md). React page column: [how-to-page-layout.md](how-to-page-layout.md). **Section** gap between sections (`medium-12`); section stacks use **Group** (`medium-8`) or **Control** (`small-4`); intro lockup internals **Tight** (`micro-2`). Form/dialog action groups: [how-to-page-layout.md](how-to-page-layout.md#action-clusters). A2UI: layout-spacing only, `spacious` never applies (`p` **Band**-equivalent `large-13`, `gap` **Section** `medium-12`).
|
|
23
23
|
|
|
24
24
|
## A11y
|
|
25
25
|
|
|
@@ -43,7 +43,7 @@ React page column: [how-to-page-layout.md](how-to-page-layout.md). Section stack
|
|
|
43
43
|
<Lockup
|
|
44
44
|
topSlot={false}
|
|
45
45
|
title={
|
|
46
|
-
<Text type="Display
|
|
46
|
+
<Text type="Display Small" as="h1" width={false}>
|
|
47
47
|
Account settings
|
|
48
48
|
</Text>
|
|
49
49
|
}
|
|
@@ -33,8 +33,11 @@ comfortable tier. See [how-to-page-layout.md](how-to-page-layout.md#responsivene
|
|
|
33
33
|
|
|
34
34
|
## Spacing
|
|
35
35
|
|
|
36
|
-
`--uxl-breakpoints-gutter` between tiers; feature rows
|
|
37
|
-
`
|
|
36
|
+
`--uxl-breakpoints-gutter` between tiers in every mode; feature rows keep the
|
|
37
|
+
`List` default (component-owned, density-invariant); the **Band** step as a band —
|
|
38
|
+
`large-15` on the `spacious` marketing page. A tier comparison is the one dense
|
|
39
|
+
piece on an airy page: let its feature rows stay tight while the section still sits
|
|
40
|
+
at the page's **Section** gap ([density.md](density.md), [spacing-steps.md](spacing-steps.md)).
|
|
38
41
|
|
|
39
42
|
## A11y
|
|
40
43
|
|