@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.
Files changed (117) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +58 -0
  3. package/dist/index.d.ts +26 -0
  4. package/dist/index.js +586 -0
  5. package/index.json +573 -0
  6. package/package.json +57 -0
  7. package/skills/uxelle-components/Accordion.md +51 -0
  8. package/skills/uxelle-components/AccordionGroup.md +39 -0
  9. package/skills/uxelle-components/Banner.md +73 -0
  10. package/skills/uxelle-components/BannerAnnouncement.md +63 -0
  11. package/skills/uxelle-components/Breadcrumb.md +38 -0
  12. package/skills/uxelle-components/Breadcrumbs.md +32 -0
  13. package/skills/uxelle-components/Bubble.md +34 -0
  14. package/skills/uxelle-components/Button.md +54 -0
  15. package/skills/uxelle-components/ButtonGroup.md +43 -0
  16. package/skills/uxelle-components/Card.md +35 -0
  17. package/skills/uxelle-components/Checkbox.md +57 -0
  18. package/skills/uxelle-components/CheckboxGroup.md +47 -0
  19. package/skills/uxelle-components/ChoiceChip.md +44 -0
  20. package/skills/uxelle-components/ChoiceChipGroup.md +50 -0
  21. package/skills/uxelle-components/DatePicker.md +59 -0
  22. package/skills/uxelle-components/DatePickerRange.md +73 -0
  23. package/skills/uxelle-components/Dialog.md +83 -0
  24. package/skills/uxelle-components/Divider.md +37 -0
  25. package/skills/uxelle-components/DynamicAngleLine.md +36 -0
  26. package/skills/uxelle-components/DynamicAngleLineCombo.md +30 -0
  27. package/skills/uxelle-components/DynamicAngleShape.md +32 -0
  28. package/skills/uxelle-components/DynamicAngleShapeSurface.md +35 -0
  29. package/skills/uxelle-components/DynamicAngleTexture.md +29 -0
  30. package/skills/uxelle-components/DynamicAngleTextureText.md +35 -0
  31. package/skills/uxelle-components/FieldMessage.md +31 -0
  32. package/skills/uxelle-components/FileUpload.md +53 -0
  33. package/skills/uxelle-components/FilterChip.md +35 -0
  34. package/skills/uxelle-components/FilterChipGroup.md +42 -0
  35. package/skills/uxelle-components/Footer.md +34 -0
  36. package/skills/uxelle-components/Hero.md +61 -0
  37. package/skills/uxelle-components/Icon.md +43 -0
  38. package/skills/uxelle-components/IconButton.md +44 -0
  39. package/skills/uxelle-components/Image.md +40 -0
  40. package/skills/uxelle-components/Label.md +35 -0
  41. package/skills/uxelle-components/LabelBadge.md +58 -0
  42. package/skills/uxelle-components/LanguageSelector.md +38 -0
  43. package/skills/uxelle-components/LanguageSelectorButton.md +34 -0
  44. package/skills/uxelle-components/Layout.md +81 -0
  45. package/skills/uxelle-components/LinearProgress.md +53 -0
  46. package/skills/uxelle-components/Link.md +52 -0
  47. package/skills/uxelle-components/List.md +35 -0
  48. package/skills/uxelle-components/ListControls.md +100 -0
  49. package/skills/uxelle-components/ListItem.md +69 -0
  50. package/skills/uxelle-components/Lockup.md +50 -0
  51. package/skills/uxelle-components/Logo.md +39 -0
  52. package/skills/uxelle-components/Menu.md +48 -0
  53. package/skills/uxelle-components/MultiSelect.md +54 -0
  54. package/skills/uxelle-components/NavLink.md +44 -0
  55. package/skills/uxelle-components/Navigation.md +61 -0
  56. package/skills/uxelle-components/NavigationSide.md +51 -0
  57. package/skills/uxelle-components/NavigationSideGroup.md +36 -0
  58. package/skills/uxelle-components/NavigationSideItem.md +56 -0
  59. package/skills/uxelle-components/NavigationSideSubItem.md +39 -0
  60. package/skills/uxelle-components/NotificationBadge.md +62 -0
  61. package/skills/uxelle-components/Pagination.md +49 -0
  62. package/skills/uxelle-components/PaginationComplex.md +51 -0
  63. package/skills/uxelle-components/PaginationSimple.md +44 -0
  64. package/skills/uxelle-components/ProductCard.md +46 -0
  65. package/skills/uxelle-components/Quantity.md +45 -0
  66. package/skills/uxelle-components/Radio.md +55 -0
  67. package/skills/uxelle-components/RadioGroup.md +49 -0
  68. package/skills/uxelle-components/SKILL.md +242 -0
  69. package/skills/uxelle-components/Scrim.md +28 -0
  70. package/skills/uxelle-components/SegmentedControl.md +118 -0
  71. package/skills/uxelle-components/Select.md +55 -0
  72. package/skills/uxelle-components/Sheet.md +31 -0
  73. package/skills/uxelle-components/Skeleton.md +35 -0
  74. package/skills/uxelle-components/Spinner.md +33 -0
  75. package/skills/uxelle-components/StatTile.md +150 -0
  76. package/skills/uxelle-components/Stepper.md +73 -0
  77. package/skills/uxelle-components/Switch.md +41 -0
  78. package/skills/uxelle-components/Tab.md +41 -0
  79. package/skills/uxelle-components/Table.md +55 -0
  80. package/skills/uxelle-components/TabsCompact.md +37 -0
  81. package/skills/uxelle-components/TabsStretch.md +35 -0
  82. package/skills/uxelle-components/Text.md +56 -0
  83. package/skills/uxelle-components/Textarea.md +77 -0
  84. package/skills/uxelle-components/Textfield.md +62 -0
  85. package/skills/uxelle-components/Tooltip.md +33 -0
  86. package/skills/uxelle-design-harness/SKILL.md +149 -0
  87. package/skills/uxelle-design-harness/a2ui.md +73 -0
  88. package/skills/uxelle-design-harness/how-to-accessibility.md +31 -0
  89. package/skills/uxelle-design-harness/how-to-color.md +82 -0
  90. package/skills/uxelle-design-harness/how-to-host.md +108 -0
  91. package/skills/uxelle-design-harness/how-to-page-layout.md +203 -0
  92. package/skills/uxelle-design-harness/principles.md +72 -0
  93. package/skills/uxelle-design-harness/recipe-app-chrome.md +168 -0
  94. package/skills/uxelle-design-harness/recipe-card-grid.md +104 -0
  95. package/skills/uxelle-design-harness/recipe-cta-band.md +91 -0
  96. package/skills/uxelle-design-harness/recipe-dashboard-overview.md +118 -0
  97. package/skills/uxelle-design-harness/recipe-data-table-page.md +323 -0
  98. package/skills/uxelle-design-harness/recipe-feature-section.md +96 -0
  99. package/skills/uxelle-design-harness/recipe-footer.md +156 -0
  100. package/skills/uxelle-design-harness/recipe-form-section.md +163 -0
  101. package/skills/uxelle-design-harness/recipe-hero.md +89 -0
  102. package/skills/uxelle-design-harness/recipe-landing-page.md +94 -0
  103. package/skills/uxelle-design-harness/recipe-logo-wall.md +80 -0
  104. package/skills/uxelle-design-harness/recipe-multi-step-flow.md +108 -0
  105. package/skills/uxelle-design-harness/recipe-page-header.md +102 -0
  106. package/skills/uxelle-design-harness/recipe-page-shell.md +82 -0
  107. package/skills/uxelle-design-harness/recipe-pricing.md +83 -0
  108. package/skills/uxelle-design-harness/recipe-query-bar.md +108 -0
  109. package/skills/uxelle-design-harness/recipe-record-detail.md +96 -0
  110. package/skills/uxelle-design-harness/recipe-settings-page.md +170 -0
  111. package/skills/uxelle-design-harness/recipe-stat-callouts.md +111 -0
  112. package/skills/uxelle-design-harness/recipe-states.md +119 -0
  113. package/skills/uxelle-design-harness/recipe-summary-list.md +88 -0
  114. package/skills/uxelle-design-harness/recipe-template.md +62 -0
  115. package/skills/uxelle-design-harness/recipe-testimonial.md +73 -0
  116. package/skills/uxelle-design-harness/spacing-steps.md +57 -0
  117. 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)).