@uxelle/skills 0.2.0-beta.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/LICENSE +674 -0
- package/README.md +58 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +586 -0
- package/index.json +573 -0
- package/package.json +57 -0
- package/skills/uxelle-components/Accordion.md +51 -0
- package/skills/uxelle-components/AccordionGroup.md +39 -0
- package/skills/uxelle-components/Banner.md +73 -0
- package/skills/uxelle-components/BannerAnnouncement.md +63 -0
- package/skills/uxelle-components/Breadcrumb.md +38 -0
- package/skills/uxelle-components/Breadcrumbs.md +32 -0
- package/skills/uxelle-components/Bubble.md +34 -0
- package/skills/uxelle-components/Button.md +54 -0
- package/skills/uxelle-components/ButtonGroup.md +43 -0
- package/skills/uxelle-components/Card.md +35 -0
- package/skills/uxelle-components/Checkbox.md +57 -0
- package/skills/uxelle-components/CheckboxGroup.md +47 -0
- package/skills/uxelle-components/ChoiceChip.md +44 -0
- package/skills/uxelle-components/ChoiceChipGroup.md +50 -0
- package/skills/uxelle-components/DatePicker.md +59 -0
- package/skills/uxelle-components/DatePickerRange.md +73 -0
- package/skills/uxelle-components/Dialog.md +83 -0
- package/skills/uxelle-components/Divider.md +37 -0
- package/skills/uxelle-components/DynamicAngleLine.md +36 -0
- package/skills/uxelle-components/DynamicAngleLineCombo.md +30 -0
- package/skills/uxelle-components/DynamicAngleShape.md +32 -0
- package/skills/uxelle-components/DynamicAngleShapeSurface.md +35 -0
- package/skills/uxelle-components/DynamicAngleTexture.md +29 -0
- package/skills/uxelle-components/DynamicAngleTextureText.md +35 -0
- package/skills/uxelle-components/FieldMessage.md +31 -0
- package/skills/uxelle-components/FileUpload.md +53 -0
- package/skills/uxelle-components/FilterChip.md +35 -0
- package/skills/uxelle-components/FilterChipGroup.md +42 -0
- package/skills/uxelle-components/Footer.md +34 -0
- package/skills/uxelle-components/Hero.md +61 -0
- package/skills/uxelle-components/Icon.md +43 -0
- package/skills/uxelle-components/IconButton.md +44 -0
- package/skills/uxelle-components/Image.md +40 -0
- package/skills/uxelle-components/Label.md +35 -0
- package/skills/uxelle-components/LabelBadge.md +58 -0
- package/skills/uxelle-components/LanguageSelector.md +38 -0
- package/skills/uxelle-components/LanguageSelectorButton.md +34 -0
- package/skills/uxelle-components/Layout.md +81 -0
- package/skills/uxelle-components/LinearProgress.md +53 -0
- package/skills/uxelle-components/Link.md +52 -0
- package/skills/uxelle-components/List.md +35 -0
- package/skills/uxelle-components/ListControls.md +100 -0
- package/skills/uxelle-components/ListItem.md +69 -0
- package/skills/uxelle-components/Lockup.md +50 -0
- package/skills/uxelle-components/Logo.md +39 -0
- package/skills/uxelle-components/Menu.md +48 -0
- package/skills/uxelle-components/MultiSelect.md +54 -0
- package/skills/uxelle-components/NavLink.md +44 -0
- package/skills/uxelle-components/Navigation.md +61 -0
- package/skills/uxelle-components/NavigationSide.md +51 -0
- package/skills/uxelle-components/NavigationSideGroup.md +36 -0
- package/skills/uxelle-components/NavigationSideItem.md +56 -0
- package/skills/uxelle-components/NavigationSideSubItem.md +39 -0
- package/skills/uxelle-components/NotificationBadge.md +62 -0
- package/skills/uxelle-components/Pagination.md +49 -0
- package/skills/uxelle-components/PaginationComplex.md +51 -0
- package/skills/uxelle-components/PaginationSimple.md +44 -0
- package/skills/uxelle-components/ProductCard.md +46 -0
- package/skills/uxelle-components/Quantity.md +45 -0
- package/skills/uxelle-components/Radio.md +55 -0
- package/skills/uxelle-components/RadioGroup.md +49 -0
- package/skills/uxelle-components/SKILL.md +242 -0
- package/skills/uxelle-components/Scrim.md +28 -0
- package/skills/uxelle-components/SegmentedControl.md +118 -0
- package/skills/uxelle-components/Select.md +55 -0
- package/skills/uxelle-components/Sheet.md +31 -0
- package/skills/uxelle-components/Skeleton.md +35 -0
- package/skills/uxelle-components/Spinner.md +33 -0
- package/skills/uxelle-components/StatTile.md +150 -0
- package/skills/uxelle-components/Stepper.md +73 -0
- package/skills/uxelle-components/Switch.md +41 -0
- package/skills/uxelle-components/Tab.md +41 -0
- package/skills/uxelle-components/Table.md +55 -0
- package/skills/uxelle-components/TabsCompact.md +37 -0
- package/skills/uxelle-components/TabsStretch.md +35 -0
- package/skills/uxelle-components/Text.md +56 -0
- package/skills/uxelle-components/Textarea.md +77 -0
- package/skills/uxelle-components/Textfield.md +62 -0
- package/skills/uxelle-components/Tooltip.md +33 -0
- package/skills/uxelle-design-harness/SKILL.md +149 -0
- package/skills/uxelle-design-harness/a2ui.md +73 -0
- package/skills/uxelle-design-harness/how-to-accessibility.md +31 -0
- package/skills/uxelle-design-harness/how-to-color.md +82 -0
- package/skills/uxelle-design-harness/how-to-host.md +108 -0
- package/skills/uxelle-design-harness/how-to-page-layout.md +203 -0
- package/skills/uxelle-design-harness/principles.md +72 -0
- package/skills/uxelle-design-harness/recipe-app-chrome.md +168 -0
- package/skills/uxelle-design-harness/recipe-card-grid.md +104 -0
- package/skills/uxelle-design-harness/recipe-cta-band.md +91 -0
- package/skills/uxelle-design-harness/recipe-dashboard-overview.md +118 -0
- package/skills/uxelle-design-harness/recipe-data-table-page.md +323 -0
- package/skills/uxelle-design-harness/recipe-feature-section.md +96 -0
- package/skills/uxelle-design-harness/recipe-footer.md +156 -0
- package/skills/uxelle-design-harness/recipe-form-section.md +163 -0
- package/skills/uxelle-design-harness/recipe-hero.md +89 -0
- package/skills/uxelle-design-harness/recipe-landing-page.md +94 -0
- package/skills/uxelle-design-harness/recipe-logo-wall.md +80 -0
- package/skills/uxelle-design-harness/recipe-multi-step-flow.md +108 -0
- package/skills/uxelle-design-harness/recipe-page-header.md +102 -0
- package/skills/uxelle-design-harness/recipe-page-shell.md +82 -0
- package/skills/uxelle-design-harness/recipe-pricing.md +83 -0
- package/skills/uxelle-design-harness/recipe-query-bar.md +108 -0
- package/skills/uxelle-design-harness/recipe-record-detail.md +96 -0
- package/skills/uxelle-design-harness/recipe-settings-page.md +170 -0
- package/skills/uxelle-design-harness/recipe-stat-callouts.md +111 -0
- package/skills/uxelle-design-harness/recipe-states.md +119 -0
- package/skills/uxelle-design-harness/recipe-summary-list.md +88 -0
- package/skills/uxelle-design-harness/recipe-template.md +62 -0
- package/skills/uxelle-design-harness/recipe-testimonial.md +73 -0
- package/skills/uxelle-design-harness/spacing-steps.md +57 -0
- package/skills/uxelle-design-harness/tokens.md +101 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Recipe: dashboard overview
|
|
2
|
+
|
|
3
|
+
A product **experience**: the signed-in landing page. A page header, a band of KPIs,
|
|
4
|
+
then the primary work surface (a table or list) with supporting groupings.
|
|
5
|
+
|
|
6
|
+
Assembles the [page-header](recipe-page-header.md),
|
|
7
|
+
[stat-callouts](recipe-stat-callouts.md), a [`Table`](../uxelle-components/Table.md)
|
|
8
|
+
or [`List`](../uxelle-components/List.md) (see [data-table-page](recipe-data-table-page.md)),
|
|
9
|
+
[card-grid](recipe-card-grid.md), and [states](recipe-states.md). Lives inside the
|
|
10
|
+
[app-chrome](recipe-app-chrome.md).
|
|
11
|
+
|
|
12
|
+
## When
|
|
13
|
+
|
|
14
|
+
The first screen after sign-in: orient the user with headline metrics and route
|
|
15
|
+
them to the main work.
|
|
16
|
+
|
|
17
|
+
## When not
|
|
18
|
+
|
|
19
|
+
- Not a records manager — deep lists belong on their own
|
|
20
|
+
[data-table-page](recipe-data-table-page.md).
|
|
21
|
+
- Do not crowd in every metric; 2-4 KPIs, then the primary surface.
|
|
22
|
+
- One `h1` (the page title); KPI and section titles are `h2`.
|
|
23
|
+
|
|
24
|
+
## Regions
|
|
25
|
+
|
|
26
|
+
page column -> [page-header](recipe-page-header.md) (`h1`, optional actions) ->
|
|
27
|
+
KPI [stat-callouts](recipe-stat-callouts.md) -> primary surface (`Table` / `List`,
|
|
28
|
+
with [states](recipe-states.md)) -> supporting [card-grid](recipe-card-grid.md) or
|
|
29
|
+
sections
|
|
30
|
+
|
|
31
|
+
## Responsive
|
|
32
|
+
|
|
33
|
+
KPI and card grids reflow with `auto-fit` / `minmax` (no JS); the header stacks and
|
|
34
|
+
its actions go `fullWidth` below tablet; the primary `Table` scrolls horizontally
|
|
35
|
+
when narrow. See [how-to-page-layout.md](how-to-page-layout.md#responsiveness).
|
|
36
|
+
|
|
37
|
+
## Spacing
|
|
38
|
+
|
|
39
|
+
`medium-12` between page sections (header / KPIs / primary / supporting);
|
|
40
|
+
`--uxl-breakpoints-gutter` inside the grids. Section intros are `micro-2`
|
|
41
|
+
([spacing-steps.md](spacing-steps.md)).
|
|
42
|
+
|
|
43
|
+
## A11y
|
|
44
|
+
|
|
45
|
+
One `main`, one `h1`. Name each region (KPIs `aria-label="Key metrics"`, the primary
|
|
46
|
+
surface by its heading). KPI deltas pair color with a sign or word. Loading and empty
|
|
47
|
+
for the primary surface follow [states](recipe-states.md). See
|
|
48
|
+
[how-to-accessibility.md](how-to-accessibility.md).
|
|
49
|
+
|
|
50
|
+
## Color
|
|
51
|
+
|
|
52
|
+
Product dashboard: stay on `default`; use status palettes only for KPI deltas and
|
|
53
|
+
alerts, never as decoration ([how-to-color.md](how-to-color.md)).
|
|
54
|
+
|
|
55
|
+
## React
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<Layout
|
|
59
|
+
display="flex"
|
|
60
|
+
flexDirection="column"
|
|
61
|
+
width="100%"
|
|
62
|
+
maxWidth="var(--uxl-breakpoints-max-container-width)"
|
|
63
|
+
mh="auto"
|
|
64
|
+
p="var(--uxl-breakpoints-margin)"
|
|
65
|
+
gap="var(--uxl-theme-layout-spacing-medium-12)"
|
|
66
|
+
>
|
|
67
|
+
{/* Page header — recipe-page-header.md */}
|
|
68
|
+
<DashboardHeader />
|
|
69
|
+
|
|
70
|
+
{/* KPIs — recipe-stat-callouts.md */}
|
|
71
|
+
<Layout
|
|
72
|
+
display="grid"
|
|
73
|
+
gridTemplateColumns="repeat(auto-fit, minmax(14rem, 1fr))"
|
|
74
|
+
gap="var(--uxl-breakpoints-gutter)"
|
|
75
|
+
aria-label="Key metrics"
|
|
76
|
+
>
|
|
77
|
+
<StatTile
|
|
78
|
+
label="Monthly revenue"
|
|
79
|
+
value="$1.24M"
|
|
80
|
+
trailingSlot
|
|
81
|
+
trailingSlotContent={
|
|
82
|
+
<LabelBadge label="+8.2%" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_up" />
|
|
83
|
+
}
|
|
84
|
+
/>
|
|
85
|
+
<StatTile
|
|
86
|
+
label="Active teams"
|
|
87
|
+
value="312"
|
|
88
|
+
trailingSlot
|
|
89
|
+
trailingSlotContent={
|
|
90
|
+
<LabelBadge label="+4" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_up" />
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
<StatTile
|
|
94
|
+
label="Open tickets"
|
|
95
|
+
value="18"
|
|
96
|
+
trailingSlot
|
|
97
|
+
trailingSlotContent={
|
|
98
|
+
<LabelBadge label="-6" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_down" />
|
|
99
|
+
}
|
|
100
|
+
/>
|
|
101
|
+
</Layout>
|
|
102
|
+
|
|
103
|
+
{/* Primary surface — recipe-data-table-page.md (with states) */}
|
|
104
|
+
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
|
|
105
|
+
<Text type="Display Extra Small" as="h2" width={false}>Recent activity</Text>
|
|
106
|
+
{isLoading ? <ActivitySkeleton /> : rows.length ? <ActivityTable rows={rows} /> : <ActivityEmpty />}
|
|
107
|
+
</Layout>
|
|
108
|
+
|
|
109
|
+
{/* Supporting — recipe-card-grid.md */}
|
|
110
|
+
<ShortcutsGrid />
|
|
111
|
+
</Layout>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## A2UI
|
|
115
|
+
|
|
116
|
+
No `getUxelleRecipe("dashboard-overview")` — compose the piece adapters in one
|
|
117
|
+
narrow column: stack `A2uiStatTile` (no `fr` grid), then the `A2uiTable*` surface
|
|
118
|
+
([a2ui.md](a2ui.md)).
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Recipe: data table page
|
|
2
|
+
|
|
3
|
+
## When
|
|
4
|
+
|
|
5
|
+
A records page: a titled collection, then a `Table` you can switch (data tabs), filter, search, and page.
|
|
6
|
+
|
|
7
|
+
Composes [`Table`](../uxelle-components/Table.md), [`TabsCompact`](../uxelle-components/TabsCompact.md), [`Sheet`](../uxelle-components/Sheet.md), [`Textfield`](../uxelle-components/Textfield.md), [`ButtonGroup`](../uxelle-components/ButtonGroup.md), [`Pagination`](../uxelle-components/Pagination.md), [`Lockup`](../uxelle-components/Lockup.md), [`LabelBadge`](../uxelle-components/LabelBadge.md), and [`FilterChipGroup`](../uxelle-components/FilterChipGroup.md). Header and query bar are the shared [page-header](recipe-page-header.md) and [query-bar](recipe-query-bar.md) pieces.
|
|
8
|
+
|
|
9
|
+
## When not
|
|
10
|
+
|
|
11
|
+
- Not a `DataTablePage` / `TablePage` component.
|
|
12
|
+
- Do not invent a toolbar component. Two bands, two jobs — do not mix them:
|
|
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`.
|
|
15
|
+
- **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
|
+
- Do not put a `Banner` (or any other page section) between data tabs and the grid.
|
|
17
|
+
- `ButtonGroup` only renders `Button` children — do not nest `Sheet` or `Textfield` in it.
|
|
18
|
+
- Do not put filter *fields* on the query bar. A filter *trigger* opens a right `Sheet` (v1).
|
|
19
|
+
- Do not put pagination on the query bar. Use `Pagination` in `Table` `bottomSlot`.
|
|
20
|
+
- Do not put table search in `Navigation`.
|
|
21
|
+
- `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
|
+
- 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
|
+
|
|
25
|
+
## Regions
|
|
26
|
+
|
|
27
|
+
page column → **header** (title left, page actions right) → optional `Banner` → **`Table`** (`topSlot`: data tabs → query bar → optional chips → grid or empty copy → `bottomSlot` pagination)
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
┌ Title + optional subtitle [Delete] [Upload] ┐
|
|
31
|
+
├ optional Banner ┤
|
|
32
|
+
├ Table.topSlot: Tabs (this dataset / that dataset) ┤
|
|
33
|
+
├ [Filter] [facet] [Search……] [Export] ┤
|
|
34
|
+
├ [FilterChip] [FilterChip] ┤
|
|
35
|
+
├ Table.tableSlot: grid, or empty copy ┤
|
|
36
|
+
└ Table.bottomSlot: Pagination ┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Page `gap` (`medium-12`) is between **page sections** (header vs table). Inside `topSlot`, use `small-4` so tabs sit on the table, not in a previous section.
|
|
40
|
+
|
|
41
|
+
## What goes where
|
|
42
|
+
|
|
43
|
+
**Header — change the collection.** `Lockup` left (direct child), `ButtonGroup` right, **same row** (`flexWrap="nowrap"`, actions `Layout` `flexShrink={0}`). Do not wrap `Lockup` in a nested `Layout` to share the row. Do not `flexWrap="wrap"` this header, and do not add `minWidth` on the actions to make wrap wait. Below tablet, that header Layout may be `flexDirection="column"` so stacked actions sit under the title ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)). Destructive / import actions open a `Dialog` — title and body `Text` use `width={false}` (100% of the slot). Button labels stay `width` (fit). Query-bar buttons stay a row at every width.
|
|
44
|
+
|
|
45
|
+
**Banner — page offer.** After the header, before `Table`. Never between data tabs and the grid.
|
|
46
|
+
|
|
47
|
+
**Data tabs — switch which rows.** First child of `Table` `topSlot`. Optional counts in the label (`"Referrals (12)"`). No `panels` wrapping the grid — controlled `activeTab` / `onChange`, then render one `Table` body for the active set. Do not put Filter or Search on this row.
|
|
48
|
+
|
|
49
|
+
**Query bar — narrow the current view.** Next in `topSlot`, one row, `justifyContent="space-between"`:
|
|
50
|
+
|
|
51
|
+
- **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 all` / Refresh as `emphasis="low"` `Button` siblings, cluster `flexWrap="nowrap"`. Not inside the Filter `ButtonGroup`.
|
|
53
|
+
|
|
54
|
+
**Footer — page the result.** `Pagination` in `bottomSlot`.
|
|
55
|
+
|
|
56
|
+
## Spacing
|
|
57
|
+
|
|
58
|
+
Page column: [how-to-page-layout.md](how-to-page-layout.md). Header and query bar: `small-4` gap, `space-between`. `topSlot` stack (tabs → query bar → chips): `small-4`. Do not wrap `Table` in extra padding. Placement rules: [how-to-page-layout.md](how-to-page-layout.md#keep-controls-with-what-they-operate).
|
|
59
|
+
|
|
60
|
+
## A11y
|
|
61
|
+
|
|
62
|
+
[how-to-accessibility.md](how-to-accessibility.md). One page `h1`. `TableGrid` `aria-label` matches the active dataset. Filter `Sheet` has a title. Compact search `Textfield` uses `aria-label`. Empty copy is `role="status"`. Each `ButtonGroup` needs `aria-label`. Query bar: `aria-label="Table filters and search"`. Data tabs: `TabsCompact` `aria-label` if the tablist needs a name (`"Referral records"`).
|
|
63
|
+
|
|
64
|
+
## Column alignment
|
|
65
|
+
|
|
66
|
+
Set `alignment` on **both** `TableHeaderCell` and `TableCell`:
|
|
67
|
+
|
|
68
|
+
- Text, names, emails, SKUs, links → `left` (default)
|
|
69
|
+
- Money, counts, numeric → `right`
|
|
70
|
+
- Category / visibility / status (`LabelBadge`) → `center`
|
|
71
|
+
- A column that is only a CTA (`Button` / `IconButton` / `Link`) → `center`
|
|
72
|
+
|
|
73
|
+
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
|
+
|
|
75
|
+
Missing **cell** values: em dash (`—`). Missing **rows**: empty copy, not dashes.
|
|
76
|
+
|
|
77
|
+
## Empty state
|
|
78
|
+
|
|
79
|
+
Keep `Table`, data tabs, and the query bar in `topSlot`. Put a `role="status"` sentence in `tableSlot`: “There are no {what this view lists} here.” Omit pagination when there are no rows.
|
|
80
|
+
|
|
81
|
+
## Responsive
|
|
82
|
+
|
|
83
|
+
- **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.
|
|
86
|
+
- **Pagination** swaps to [`PaginationSimple`](../uxelle-components/PaginationSimple.md) where the full control is too wide.
|
|
87
|
+
|
|
88
|
+
## React
|
|
89
|
+
|
|
90
|
+
Import from `@uxelle/components`. Data tabs and the query bar both go in `topSlot` — tabs first. `tabletUp` comes from `useBreakpointUp("tablet")` ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)).
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
|
|
94
|
+
<Layout
|
|
95
|
+
display="flex"
|
|
96
|
+
flexDirection={tabletUp ? "row" : "column"}
|
|
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>
|
|
134
|
+
|
|
135
|
+
{/* optional Banner — here, not inside Table */}
|
|
136
|
+
|
|
137
|
+
<Table
|
|
138
|
+
topSlot={
|
|
139
|
+
<Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
|
|
140
|
+
<TabsCompact
|
|
141
|
+
amount={2}
|
|
142
|
+
tabLabels={["Referrals (12)", "Connections (6)"]}
|
|
143
|
+
activeTab={activeTab}
|
|
144
|
+
onChange={setActiveTab}
|
|
145
|
+
aria-label="Referral records"
|
|
146
|
+
/>
|
|
147
|
+
<TableQueryBar />
|
|
148
|
+
</Layout>
|
|
149
|
+
}
|
|
150
|
+
tableSlot={
|
|
151
|
+
rows.length === 0 ? (
|
|
152
|
+
<Text type="Body Medium" width={false} role="status">
|
|
153
|
+
There are no referrals here.
|
|
154
|
+
</Text>
|
|
155
|
+
) : (
|
|
156
|
+
<TableColumnSizingProvider columns={columns}>
|
|
157
|
+
<RecordsGrid rows={rows} />
|
|
158
|
+
</TableColumnSizingProvider>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
bottomSlot={
|
|
162
|
+
rows.length === 0 ? undefined : (
|
|
163
|
+
<Pagination totalItems={rows.length} defaultItemsPerPage={10} aria-label="Referrals pagination" />
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
/>
|
|
167
|
+
</Layout>
|
|
168
|
+
```
|
|
169
|
+
|
|
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
|
+
Grid cells (alignment on header and body):
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
<TableHeaderCell columnId={0} title="Connection" showControls={false} />
|
|
253
|
+
<TableHeaderCell columnId={1} title="Status" showControls={false} alignment="center" />
|
|
254
|
+
<TableHeaderCell columnId={2} title="Potential earnings" showControls={false} alignment="right" />
|
|
255
|
+
<TableCell columnId={0}>
|
|
256
|
+
<Layout display="flex" flexDirection="column" gap={0}>
|
|
257
|
+
<Text type="Body Small Alt">{row.name}</Text>
|
|
258
|
+
<Text type="Condensed">{row.email ?? "—"}</Text>
|
|
259
|
+
</Layout>
|
|
260
|
+
</TableCell>
|
|
261
|
+
<TableCell columnId={1} alignment="center">
|
|
262
|
+
<LabelBadge label={row.status} />
|
|
263
|
+
</TableCell>
|
|
264
|
+
<TableCell columnId={2} alignment="right">
|
|
265
|
+
<Text type="Body Small">{row.amount}</Text>
|
|
266
|
+
</TableCell>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Confirm / upload `Dialog` (page actions, not on the table):
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
<Dialog
|
|
273
|
+
size="Small"
|
|
274
|
+
open={deleteOpen}
|
|
275
|
+
onOpenChange={setDeleteOpen}
|
|
276
|
+
aria-labelledby="delete-dialog-title"
|
|
277
|
+
aria-describedby="delete-dialog-body"
|
|
278
|
+
topSlotContent={
|
|
279
|
+
<>
|
|
280
|
+
<Text id="delete-dialog-title" type="Component Medium" width={false}>
|
|
281
|
+
Delete referral information
|
|
282
|
+
</Text>
|
|
283
|
+
<IconButton
|
|
284
|
+
emphasis="low"
|
|
285
|
+
size="small"
|
|
286
|
+
iconName="close"
|
|
287
|
+
iconVariant="sharpFilled"
|
|
288
|
+
aria-label="Close"
|
|
289
|
+
onClick={() => setDeleteOpen(false)}
|
|
290
|
+
/>
|
|
291
|
+
</>
|
|
292
|
+
}
|
|
293
|
+
centerSlotContent={
|
|
294
|
+
<Text id="delete-dialog-body" type="Body Medium" width={false}>
|
|
295
|
+
This removes the referral register from this session. It cannot be undone.
|
|
296
|
+
</Text>
|
|
297
|
+
}
|
|
298
|
+
bottomSlotContent={
|
|
299
|
+
<ButtonGroup
|
|
300
|
+
direction={tabletUp ? "row" : "column"}
|
|
301
|
+
fullWidth={!tabletUp}
|
|
302
|
+
aria-label="Delete confirmation"
|
|
303
|
+
>
|
|
304
|
+
<Button emphasis="low" size="medium" onClick={() => setDeleteOpen(false)}>
|
|
305
|
+
<Text type="Button" width>
|
|
306
|
+
Cancel
|
|
307
|
+
</Text>
|
|
308
|
+
</Button>
|
|
309
|
+
<Button emphasis="high" size="medium" onClick={onDelete}>
|
|
310
|
+
<Text type="Button" width>
|
|
311
|
+
Delete
|
|
312
|
+
</Text>
|
|
313
|
+
</Button>
|
|
314
|
+
</ButtonGroup>
|
|
315
|
+
}
|
|
316
|
+
/>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Upload dialogs put `FileUpload` `fullWidth` in `centerSlotContent`. Title `Text` still uses `width={false}`.
|
|
320
|
+
|
|
321
|
+
## A2UI
|
|
322
|
+
|
|
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`.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Recipe: feature section
|
|
2
|
+
|
|
3
|
+
A marketing **piece**: a capability shown as media beside copy. Stack several down
|
|
4
|
+
the page, alternating the media side for rhythm; or switch capabilities with a
|
|
5
|
+
segmented control.
|
|
6
|
+
|
|
7
|
+
Composes [`Layout`](../uxelle-components/Layout.md) grid,
|
|
8
|
+
[`Lockup`](../uxelle-components/Lockup.md),
|
|
9
|
+
[`Image`](../uxelle-components/Image.md),
|
|
10
|
+
[`ButtonGroup`](../uxelle-components/ButtonGroup.md), and optionally
|
|
11
|
+
[`SegmentedControl`](../uxelle-components/SegmentedControl.md) or
|
|
12
|
+
[`TabsCompact`](../uxelle-components/TabsCompact.md) for the switched variant.
|
|
13
|
+
|
|
14
|
+
## When
|
|
15
|
+
|
|
16
|
+
Explaining one capability at a time with a supporting visual.
|
|
17
|
+
|
|
18
|
+
## When not
|
|
19
|
+
|
|
20
|
+
- Three-plus equal cells are a [card-grid](recipe-card-grid.md), not this.
|
|
21
|
+
- Section headings are `h2` (the page `h1` is the [hero](recipe-hero.md)).
|
|
22
|
+
- Do not alternate sides with absolute positioning — reorder grid columns.
|
|
23
|
+
|
|
24
|
+
## Regions
|
|
25
|
+
|
|
26
|
+
section -> two-column grid: `Lockup` (`h2` + body + optional `ButtonGroup`) and
|
|
27
|
+
`Image`. Alternate which column is the media per section. Switched variant: a
|
|
28
|
+
`SegmentedControl` / `TabsCompact` above one media+copy block.
|
|
29
|
+
|
|
30
|
+
## Responsive
|
|
31
|
+
|
|
32
|
+
Two columns from tablet up, single column (media over copy) below — use
|
|
33
|
+
`useBreakpointUp("tablet")` to set `gridTemplateColumns` and the visual order. See
|
|
34
|
+
[how-to-page-layout.md](how-to-page-layout.md#responsiveness).
|
|
35
|
+
|
|
36
|
+
## Spacing
|
|
37
|
+
|
|
38
|
+
`--uxl-breakpoints-gutter` between the columns; `medium-12` between stacked feature
|
|
39
|
+
sections; `large-13`+ block padding if the section is its own band
|
|
40
|
+
([spacing-steps.md](spacing-steps.md)).
|
|
41
|
+
|
|
42
|
+
## A11y
|
|
43
|
+
|
|
44
|
+
Section heading `h2`; name the region with it (`aria-labelledby`). The
|
|
45
|
+
`SegmentedControl` variant carries a legend or `aria-label`; meaningful media gets
|
|
46
|
+
`alt`. See [how-to-accessibility.md](how-to-accessibility.md).
|
|
47
|
+
|
|
48
|
+
## React
|
|
49
|
+
|
|
50
|
+
Alternating media + copy:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
const tabletUp = useBreakpointUp("tablet");
|
|
54
|
+
|
|
55
|
+
<Layout
|
|
56
|
+
as="section"
|
|
57
|
+
display="grid"
|
|
58
|
+
gridTemplateColumns={tabletUp ? "1fr 1fr" : "1fr"}
|
|
59
|
+
gap="var(--uxl-breakpoints-gutter)"
|
|
60
|
+
alignItems="center"
|
|
61
|
+
>
|
|
62
|
+
<Lockup
|
|
63
|
+
overline={<Text type="Overline" width={false}>Automation</Text>}
|
|
64
|
+
title={<Text type="Display Small" as="h2" width={false}>Ship without the busywork</Text>}
|
|
65
|
+
body={<Text type="Body Medium" width={false}>Rules handle the repetitive steps so your team ships.</Text>}
|
|
66
|
+
/>
|
|
67
|
+
<Image src="/features/automation.png" alt="Automation rules editor" aspectRatio="4:3" />
|
|
68
|
+
</Layout>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For the next section, place the `Image` first (or set column order) so the media
|
|
72
|
+
alternates sides.
|
|
73
|
+
|
|
74
|
+
Switched variant (capabilities behind a control):
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-medium-8)">
|
|
78
|
+
<SegmentedControl
|
|
79
|
+
labelText="Capabilities"
|
|
80
|
+
value={feature}
|
|
81
|
+
onValueChange={setFeature}
|
|
82
|
+
segments={[
|
|
83
|
+
{ value: "plan", label: "Plan" },
|
|
84
|
+
{ value: "build", label: "Build" },
|
|
85
|
+
{ value: "ship", label: "Ship" },
|
|
86
|
+
]}
|
|
87
|
+
/>
|
|
88
|
+
{/* render the media+copy block for the active `feature` */}
|
|
89
|
+
</Layout>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## A2UI
|
|
93
|
+
|
|
94
|
+
No `getUxelleRecipe("feature-section")` and no `gridTemplateColumns` — stack
|
|
95
|
+
`A2uiText` copy then `A2uiImage` in one column; the switched variant uses
|
|
96
|
+
`A2uiSegmentedControl` or `A2uiTabGroup` ([a2ui.md](a2ui.md)).
|