lucent-ui 0.36.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{LucentProvider-F0EN_7TD.js → LucentProvider-Bm39MMvv.js} +550 -532
- package/dist/{LucentProvider-LqNc0AxD.cjs → LucentProvider-CzEDW5SL.cjs} +6 -6
- package/dist/devtools.cjs +1 -1
- package/dist/devtools.js +1 -1
- package/dist/index.cjs +87 -41
- package/dist/index.d.ts +92 -5
- package/dist/index.js +1519 -1285
- package/dist-server/src/components/atoms/Row/Row.manifest.js +10 -2
- package/dist-server/src/components/atoms/Stack/Stack.manifest.js +6 -2
- package/dist-server/src/components/molecules/Breadcrumb/Breadcrumb.manifest.js +27 -0
- package/dist-server/src/components/molecules/PageHeader/PageHeader.manifest.js +144 -0
- package/dist-server/src/manifest/examples/button.manifest.js +39 -1
- package/dist-server/src/manifest/patterns/action-bar.pattern.js +34 -6
- package/package.json +1 -1
|
@@ -42,8 +42,8 @@ export const COMPONENT_MANIFEST = {
|
|
|
42
42
|
type: 'enum',
|
|
43
43
|
required: false,
|
|
44
44
|
default: 'div',
|
|
45
|
-
description: 'HTML element to render. Use semantic elements when appropriate (nav for navigation,
|
|
46
|
-
enumValues: ['div', 'section', 'nav', 'form', 'fieldset', 'ul', 'ol'],
|
|
45
|
+
description: 'HTML element to render. Use semantic elements when appropriate (nav for navigation, ul/ol for lists, section/header/footer/main/aside/article for landmarks). When rendering as ul/ol, list-style/margin/padding are auto-reset; consumers pass <li> children.',
|
|
46
|
+
enumValues: ['div', 'section', 'nav', 'header', 'footer', 'main', 'aside', 'article', 'form', 'fieldset', 'ul', 'ol'],
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
name: 'wrap',
|
|
@@ -92,6 +92,14 @@ export const COMPONENT_MANIFEST = {
|
|
|
92
92
|
title: 'Header with actions',
|
|
93
93
|
code: `<Row justify="between">\n <Text as="h2" size="xl" weight="semibold">Dashboard</Text>\n <Row gap="2">\n <Button variant="outline" size="sm">Export</Button>\n <Button variant="primary" size="sm">New report</Button>\n </Row>\n</Row>`,
|
|
94
94
|
},
|
|
95
|
+
{
|
|
96
|
+
title: 'Semantic nav',
|
|
97
|
+
code: `<Row as="nav" gap="4" aria-label="Primary">\n <a href="/home">Home</a>\n <a href="/about">About</a>\n <a href="/contact">Contact</a>\n</Row>`,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
title: 'Horizontal list (ul)',
|
|
101
|
+
code: `<Row as="ul" gap="2" aria-label="Tags">\n {tags.map(tag => (\n <li key={tag}><Tag>{tag}</Tag></li>\n ))}\n</Row>`,
|
|
102
|
+
},
|
|
95
103
|
],
|
|
96
104
|
compositionGraph: [],
|
|
97
105
|
accessibility: {
|
|
@@ -41,8 +41,8 @@ export const COMPONENT_MANIFEST = {
|
|
|
41
41
|
type: 'enum',
|
|
42
42
|
required: false,
|
|
43
43
|
default: 'div',
|
|
44
|
-
description: 'HTML element to render. Use semantic elements when appropriate (nav for navigation,
|
|
45
|
-
enumValues: ['div', 'section', 'nav', 'form', 'fieldset', 'ul', 'ol'],
|
|
44
|
+
description: 'HTML element to render. Use semantic elements when appropriate (nav for navigation, ul/ol for lists, section/header/footer/main/aside/article for landmarks). When rendering as ul/ol, list-style/margin/padding are auto-reset; consumers pass <li> children.',
|
|
45
|
+
enumValues: ['div', 'section', 'nav', 'header', 'footer', 'main', 'aside', 'article', 'form', 'fieldset', 'ul', 'ol'],
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
name: 'wrap',
|
|
@@ -91,6 +91,10 @@ export const COMPONENT_MANIFEST = {
|
|
|
91
91
|
title: 'Semantic nav',
|
|
92
92
|
code: `<Stack as="nav" gap="1">\n <NavLink href="/home">Home</NavLink>\n <NavLink href="/settings">Settings</NavLink>\n</Stack>`,
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
title: 'Semantic list (ul)',
|
|
96
|
+
code: `<Stack as="ul" gap="3" aria-label="Recent notes">\n {notes.map(note => (\n <li key={note.id}>\n <Text size="sm">{note.body}</Text>\n </li>\n ))}\n</Stack>`,
|
|
97
|
+
},
|
|
94
98
|
],
|
|
95
99
|
compositionGraph: [],
|
|
96
100
|
accessibility: {
|
|
@@ -31,6 +31,17 @@ export const COMPONENT_MANIFEST = {
|
|
|
31
31
|
required: false,
|
|
32
32
|
description: 'Inline style overrides for the root <nav> element.',
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
name: 'LinkComponent',
|
|
36
|
+
type: 'ComponentType',
|
|
37
|
+
required: false,
|
|
38
|
+
description: 'Optional custom link component used for items with an href, enabling SPA-friendly ' +
|
|
39
|
+
'routing (react-router, Next.js, etc.) without a full-page reload. ' +
|
|
40
|
+
'Receives { href, children, style, onClick, onMouseEnter, onMouseLeave }. ' +
|
|
41
|
+
'If your link primitive uses a different prop name for the URL, wrap it in a small adapter. ' +
|
|
42
|
+
'The adapter must forward `style` to the rendered DOM element so Breadcrumb typography and ' +
|
|
43
|
+
'hover styling are preserved.',
|
|
44
|
+
},
|
|
34
45
|
],
|
|
35
46
|
usageExamples: [
|
|
36
47
|
{
|
|
@@ -61,6 +72,22 @@ export const COMPONENT_MANIFEST = {
|
|
|
61
72
|
{ label: 'Reports', onClick: () => navigate('/reports') },
|
|
62
73
|
{ label: 'Q1 Summary' },
|
|
63
74
|
]}
|
|
75
|
+
/>`,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
title: 'react-router integration (LinkComponent)',
|
|
79
|
+
code: `import { Link } from 'react-router-dom';
|
|
80
|
+
|
|
81
|
+
// react-router's Link uses \`to\`, so wrap it in a small adapter that
|
|
82
|
+
// maps Breadcrumb's \`href\` prop to \`to\` and forwards style/handlers.
|
|
83
|
+
const RouterLink = ({ href, ...rest }) => <Link to={href} {...rest} />;
|
|
84
|
+
|
|
85
|
+
<Breadcrumb
|
|
86
|
+
LinkComponent={RouterLink}
|
|
87
|
+
items={[
|
|
88
|
+
{ label: 'Candidates', href: '/candidates' },
|
|
89
|
+
{ label: 'Jane Doe' },
|
|
90
|
+
]}
|
|
64
91
|
/>`,
|
|
65
92
|
},
|
|
66
93
|
],
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export const COMPONENT_MANIFEST = {
|
|
2
|
+
id: 'page-header',
|
|
3
|
+
name: 'PageHeader',
|
|
4
|
+
tier: 'molecule',
|
|
5
|
+
domain: 'neutral',
|
|
6
|
+
specVersion: '0.1',
|
|
7
|
+
description: 'A page-level header pairing breadcrumbs, a large display title, optional subtitle, and up to one primary CTA with 0–3 secondary outline actions.',
|
|
8
|
+
designIntent: 'PageHeader is the canonical chrome for a page body. It encapsulates the action-bar pattern — ' +
|
|
9
|
+
'breadcrumbs on top, a 3xl display title with optional subtitle below, action buttons anchored ' +
|
|
10
|
+
'to the title baseline, and a divider separating the header from page content. ' +
|
|
11
|
+
'The component owns three zones: breadcrumbs (nav context), title block, and action slot. ' +
|
|
12
|
+
'\n\n' +
|
|
13
|
+
'## Multi-action support\n' +
|
|
14
|
+
'The `action` prop holds the single primary CTA (rightmost position, `variant="primary"` by ' +
|
|
15
|
+
'default). The `secondaryActions` prop accepts 0–3 additional actions that render to the left ' +
|
|
16
|
+
'of the primary action with `variant="outline"` by default, keeping them visually subordinate. ' +
|
|
17
|
+
'This covers the common detail-page need for Edit / Archive / primary-CTA triples without ' +
|
|
18
|
+
'forcing consumers to rebuild the header from primitives. Beyond 3 secondary actions, reach for ' +
|
|
19
|
+
'SplitButton or an overflow menu instead of widening the row.\n\n' +
|
|
20
|
+
'## Responsive behaviour\n' +
|
|
21
|
+
'Both the outer title/action Row and the inner action Row set `wrap` so the button group flows ' +
|
|
22
|
+
'below the title on narrow viewports instead of overflowing. `align="end"` anchors the buttons ' +
|
|
23
|
+
'to the baseline of the subtitle line when the layout is horizontal.\n\n' +
|
|
24
|
+
'## Styling\n' +
|
|
25
|
+
'All spacing, typography, and colors come from Lucent tokens — no custom values. The title uses ' +
|
|
26
|
+
'the display font family and 3xl size; the subtitle uses the base font at sm/secondary. The ' +
|
|
27
|
+
'bottom divider can be hidden via `hideDivider` when the header sits directly above another ' +
|
|
28
|
+
'bordered surface.',
|
|
29
|
+
props: [
|
|
30
|
+
{
|
|
31
|
+
name: 'title',
|
|
32
|
+
type: 'string',
|
|
33
|
+
required: true,
|
|
34
|
+
description: 'Page title rendered as an h1 in the display font at 3xl size.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'subtitle',
|
|
38
|
+
type: 'ReactNode',
|
|
39
|
+
required: false,
|
|
40
|
+
description: 'Optional subtitle or metadata line rendered below the title at sm/secondary.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'breadcrumbs',
|
|
44
|
+
type: 'array',
|
|
45
|
+
required: false,
|
|
46
|
+
description: 'Breadcrumb items rendered above the title via the Breadcrumb molecule. Provides navigation context.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'action',
|
|
50
|
+
type: 'object',
|
|
51
|
+
required: false,
|
|
52
|
+
description: 'Single primary CTA rendered rightmost. Shape: { label, onClick?, variant?, leftIcon?, rightIcon?, disabled?, loading? }. ' +
|
|
53
|
+
'Defaults to variant="primary". Pass `null` to explicitly omit when spreading dynamic props.',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'secondaryActions',
|
|
57
|
+
type: 'array',
|
|
58
|
+
required: false,
|
|
59
|
+
description: '0–3 secondary actions rendered to the left of `action`. Each item uses the same PageHeaderAction shape as `action`. ' +
|
|
60
|
+
'Defaults to variant="outline" so they stay visually subordinate. For more than 3 actions, use a SplitButton or overflow menu.',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'hideDivider',
|
|
64
|
+
type: 'boolean',
|
|
65
|
+
required: false,
|
|
66
|
+
default: 'false',
|
|
67
|
+
description: 'Hide the bottom divider. Use when the header sits directly above another bordered surface.',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'style',
|
|
71
|
+
type: 'object',
|
|
72
|
+
required: false,
|
|
73
|
+
description: 'Inline style overrides for the outer Stack wrapper.',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
usageExamples: [
|
|
77
|
+
{
|
|
78
|
+
title: 'Minimal — title only',
|
|
79
|
+
code: `<PageHeader title="Settings" />`,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: 'With subtitle and single primary action',
|
|
83
|
+
code: `<PageHeader
|
|
84
|
+
title="Acme Corp"
|
|
85
|
+
subtitle="Last updated 5 minutes ago"
|
|
86
|
+
action={{ label: 'New report', onClick: () => createReport() }}
|
|
87
|
+
/>`,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
title: 'Detail view with secondary actions (Edit / Archive / Submit)',
|
|
91
|
+
code: `<PageHeader
|
|
92
|
+
title="Sana Khan"
|
|
93
|
+
subtitle="Senior Product Designer · Added 3 days ago"
|
|
94
|
+
breadcrumbs={[
|
|
95
|
+
{ label: 'Home', href: '#' },
|
|
96
|
+
{ label: 'Candidates', href: '#' },
|
|
97
|
+
{ label: 'Sana Khan' },
|
|
98
|
+
]}
|
|
99
|
+
secondaryActions={[
|
|
100
|
+
{ label: 'Edit', onClick: () => openEdit() },
|
|
101
|
+
{ label: 'Archive', onClick: () => archive() },
|
|
102
|
+
]}
|
|
103
|
+
action={{ label: 'Submit to opportunity', onClick: () => submit() }}
|
|
104
|
+
/>`,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
title: 'Danger zone',
|
|
108
|
+
code: `<PageHeader
|
|
109
|
+
title="Danger zone"
|
|
110
|
+
subtitle="These actions are irreversible."
|
|
111
|
+
breadcrumbs={[
|
|
112
|
+
{ label: 'Home', href: '#' },
|
|
113
|
+
{ label: 'Settings', href: '#' },
|
|
114
|
+
{ label: 'Danger zone' },
|
|
115
|
+
]}
|
|
116
|
+
action={{ label: 'Delete project', variant: 'danger', onClick: () => confirmDelete() }}
|
|
117
|
+
/>`,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
title: 'No primary CTA, secondary actions only',
|
|
121
|
+
code: `<PageHeader
|
|
122
|
+
title="Reports"
|
|
123
|
+
secondaryActions={[
|
|
124
|
+
{ label: 'Export', onClick: () => exportAll() },
|
|
125
|
+
{ label: 'Filter', onClick: () => openFilter() },
|
|
126
|
+
]}
|
|
127
|
+
/>`,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
compositionGraph: [
|
|
131
|
+
{ componentId: 'breadcrumb', componentName: 'Breadcrumb', role: 'Navigation context above the title', required: false },
|
|
132
|
+
{ componentId: 'stack', componentName: 'Stack', role: 'Vertical layout for title and subtitle', required: true },
|
|
133
|
+
{ componentId: 'row', componentName: 'Row', role: 'Horizontal layout for title block and action slot', required: true },
|
|
134
|
+
{ componentId: 'text', componentName: 'Text', role: 'Title and subtitle rendering', required: true },
|
|
135
|
+
{ componentId: 'button', componentName: 'Button', role: 'Primary and secondary action buttons', required: false },
|
|
136
|
+
{ componentId: 'divider', componentName: 'Divider', role: 'Separator between header chrome and page content', required: false },
|
|
137
|
+
],
|
|
138
|
+
accessibility: {
|
|
139
|
+
notes: 'The title renders as an <h1>, providing the page heading landmark for assistive tech. ' +
|
|
140
|
+
'Breadcrumbs render inside <nav aria-label="Breadcrumb">. Action buttons inherit Button\'s ' +
|
|
141
|
+
'focus ring, keyboard activation, and aria-label forwarding — pass aria-label on each action ' +
|
|
142
|
+
'object when the label alone is insufficient (e.g. icon-only buttons).',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
@@ -113,9 +113,32 @@ export const ButtonManifest = {
|
|
|
113
113
|
type: 'enum',
|
|
114
114
|
required: false,
|
|
115
115
|
default: 'button',
|
|
116
|
-
description: 'Native button type attribute.',
|
|
116
|
+
description: 'Native button type attribute. Ignored when `href` is set.',
|
|
117
117
|
enumValues: ['button', 'submit', 'reset'],
|
|
118
118
|
},
|
|
119
|
+
{
|
|
120
|
+
name: 'href',
|
|
121
|
+
type: 'string',
|
|
122
|
+
required: false,
|
|
123
|
+
description: 'When set, renders the Button as an `<a href={href}>` instead of a native `<button>`. ' +
|
|
124
|
+
'Preserves native anchor affordances (middle-click, cmd/ctrl-click, right-click "copy link address", ' +
|
|
125
|
+
'open in new tab) that an onClick handler cannot. ' +
|
|
126
|
+
'Use for `mailto:` / `tel:` quick actions, external links styled as buttons, ' +
|
|
127
|
+
'or in-app routes where users legitimately expect anchor semantics. ' +
|
|
128
|
+
'When combined with `disabled`, the anchor renders with `aria-disabled="true"` and its `href` is stripped so navigation is neutralised.',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'target',
|
|
132
|
+
type: 'string',
|
|
133
|
+
required: false,
|
|
134
|
+
description: 'Forwarded to the rendered `<a>` when `href` is set (e.g. `"_blank"` to open in a new tab). Ignored when rendering as a button.',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'rel',
|
|
138
|
+
type: 'string',
|
|
139
|
+
required: false,
|
|
140
|
+
description: 'Forwarded to the rendered `<a>` when `href` is set (e.g. `"noopener noreferrer"` for external links). Ignored when rendering as a button.',
|
|
141
|
+
},
|
|
119
142
|
],
|
|
120
143
|
usageExamples: [
|
|
121
144
|
{
|
|
@@ -167,6 +190,21 @@ export const ButtonManifest = {
|
|
|
167
190
|
code: `<Button variant="outline" size="2xs" leftIcon={<CloseIcon />} aria-label="Close" />`,
|
|
168
191
|
description: 'Omitting children auto-sizes the button as a square via aspect-ratio: 1.',
|
|
169
192
|
},
|
|
193
|
+
{
|
|
194
|
+
title: 'Mailto quick action',
|
|
195
|
+
code: `<Button variant="ghost" size="sm" href="mailto:foo@example.com" leftIcon={<MailIcon />} aria-label="Email" />`,
|
|
196
|
+
description: 'Renders as <a href="mailto:..."> so middle-click, cmd/ctrl-click, and right-click "copy link" all work.',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
title: 'External link as button',
|
|
200
|
+
code: `<Button variant="primary" href="https://example.com" target="_blank" rel="noopener noreferrer" rightIcon={<ExternalIcon />}>View docs</Button>`,
|
|
201
|
+
description: 'Use href + target + rel for external links that should look like a primary call-to-action.',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
title: 'Disabled link',
|
|
205
|
+
code: `<Button variant="outline" href="/settings" disabled>Settings</Button>`,
|
|
206
|
+
description: 'When disabled, the anchor is rendered with aria-disabled="true" and its href is stripped.',
|
|
207
|
+
},
|
|
170
208
|
],
|
|
171
209
|
compositionGraph: [],
|
|
172
210
|
accessibility: {
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
export const PATTERN = {
|
|
2
2
|
id: 'action-bar',
|
|
3
3
|
name: 'Action Bar',
|
|
4
|
-
description: 'Page-level or card-level header pairing a title with action buttons. Page headers use breadcrumb, large display title, and a divider below; card headers are compact with small text.',
|
|
4
|
+
description: 'Page-level or card-level header pairing a title with action buttons. Page headers use breadcrumb, large display title, and a divider below; card headers are compact with small text. Supports multi-action headers with secondary outline buttons preceding the primary CTA.',
|
|
5
5
|
category: 'action',
|
|
6
6
|
components: ['text', 'button', 'row', 'stack', 'breadcrumb', 'divider', 'card'],
|
|
7
7
|
structure: `
|
|
8
8
|
Page header:
|
|
9
9
|
Stack gap="4"
|
|
10
10
|
├── Breadcrumb ← navigation context
|
|
11
|
-
├── Row justify="between" align="end"
|
|
11
|
+
├── Row justify="between" align="end" wrap
|
|
12
12
|
│ ├── Stack gap="1"
|
|
13
13
|
│ │ ├── Text (h1, 3xl, bold, display) ← page title
|
|
14
14
|
│ │ └── Text (sm, secondary) ← subtitle
|
|
15
|
-
│ └── Row gap="2"
|
|
16
|
-
│ ├── Button (outline, sm)
|
|
17
|
-
│ └── Button (primary, sm)
|
|
15
|
+
│ └── Row gap="2" wrap ← actions (wraps below title on narrow screens)
|
|
16
|
+
│ ├── Button[] (outline, sm) ← secondary actions (0–3, leftmost)
|
|
17
|
+
│ └── Button (primary, sm) ← primary CTA (rightmost, always single)
|
|
18
18
|
└── Divider
|
|
19
19
|
|
|
20
20
|
Card header:
|
|
@@ -44,6 +44,28 @@ Row justify="between" align="start"
|
|
|
44
44
|
<Divider />
|
|
45
45
|
</Stack>`,
|
|
46
46
|
variants: [
|
|
47
|
+
{
|
|
48
|
+
title: 'Page header — detail view with secondary actions',
|
|
49
|
+
code: `<Stack gap="4">
|
|
50
|
+
<Breadcrumb items={[
|
|
51
|
+
{ label: 'Home', href: '#' },
|
|
52
|
+
{ label: 'Candidates', href: '#' },
|
|
53
|
+
{ label: 'Sana Khan' },
|
|
54
|
+
]} />
|
|
55
|
+
<Row justify="between" align="end" wrap gap="3">
|
|
56
|
+
<Stack gap="1">
|
|
57
|
+
<Text as="h1" size="3xl" weight="bold" family="display">Sana Khan</Text>
|
|
58
|
+
<Text size="sm" color="secondary">Senior Product Designer · Added 3 days ago</Text>
|
|
59
|
+
</Stack>
|
|
60
|
+
<Row gap="2" wrap>
|
|
61
|
+
<Button variant="outline" size="sm">Edit</Button>
|
|
62
|
+
<Button variant="outline" size="sm">Archive</Button>
|
|
63
|
+
<Button variant="primary" size="sm">Submit to opportunity</Button>
|
|
64
|
+
</Row>
|
|
65
|
+
</Row>
|
|
66
|
+
<Divider />
|
|
67
|
+
</Stack>`,
|
|
68
|
+
},
|
|
47
69
|
{
|
|
48
70
|
title: 'Page header — danger zone',
|
|
49
71
|
code: `<Stack gap="4">
|
|
@@ -87,5 +109,11 @@ Row justify="between" align="start"
|
|
|
87
109
|
'buttons to the baseline of the title block so they sit level with the subtitle. ' +
|
|
88
110
|
'Card headers are compact: sm semibold text with xs/ghost buttons that recede ' +
|
|
89
111
|
'visually. The primary action is always rightmost following natural reading order. ' +
|
|
90
|
-
'Both patterns use justify="between" to push title and actions to opposite edges.'
|
|
112
|
+
'Both patterns use justify="between" to push title and actions to opposite edges. ' +
|
|
113
|
+
'Multi-action page headers (e.g. detail views with Edit / Archive / Submit) follow ' +
|
|
114
|
+
'the three-zone CTA rule: 0–3 secondary actions render as variant="outline" to the ' +
|
|
115
|
+
'left of a single primary button, keeping them visually subordinate. Beyond 3 ' +
|
|
116
|
+
'secondary actions, reach for SplitButton or an overflow menu instead of widening ' +
|
|
117
|
+
'the row. Both the outer title/action Row and the inner action Row set wrap so the ' +
|
|
118
|
+
'buttons flow below the title on narrow viewports instead of overflowing.',
|
|
91
119
|
};
|