@automattic/vip-design-system 2.20.3 → 2.20.5
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/.github/workflows/react-doctor.yml +38 -0
- package/build/system/Accordion/Accordion.js +13 -3
- package/build/system/Form/Input.styles.d.ts +0 -2
- package/build/system/Form/Input.styles.js +0 -3
- package/build/system/Form/Radio/styles.js +1 -1
- package/build/system/NewDialog/DialogTitle.js +1 -2
- package/build/system/Wizard/Wizard.d.ts +13 -0
- package/build/system/Wizard/Wizard.js +7 -3
- package/build/system/Wizard/Wizard.stories.d.ts +7 -0
- package/build/system/Wizard/Wizard.stories.js +26 -0
- package/build/system/Wizard/Wizard.test.js +74 -0
- package/build/system/Wizard/WizardStep.d.ts +7 -1
- package/build/system/Wizard/WizardStep.js +2 -0
- package/doctor.config.jsonc +15 -0
- package/package.json +1 -1
- package/src/system/Accordion/Accordion.tsx +15 -1
- package/src/system/Form/Input.styles.ts +0 -1
- package/src/system/Form/Radio/styles.ts +1 -1
- package/src/system/NewDialog/DialogTitle.tsx +1 -3
- package/src/system/Wizard/Wizard.stories.tsx +30 -0
- package/src/system/Wizard/Wizard.test.tsx +52 -0
- package/src/system/Wizard/Wizard.tsx +19 -2
- package/src/system/Wizard/WizardStep.tsx +9 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: React Doctor
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [trunk]
|
|
6
|
+
types: [opened, reopened, synchronize, ready_for_review]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: react-doctor-${{ github.event.pull_request.number || github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
react-doctor:
|
|
18
|
+
name: React Doctor
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
timeout-minutes: 10
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v6
|
|
23
|
+
with:
|
|
24
|
+
# Full history so the PR can be diffed against the trunk base.
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
|
|
27
|
+
- uses: millionco/react-doctor@547e1e4ecdb70315d81a91ec3605701d58616ee2 # v2.1.0
|
|
28
|
+
with:
|
|
29
|
+
# Advisory only: report findings but never fail the run / block merge.
|
|
30
|
+
blocking: none
|
|
31
|
+
# Only report issues this PR introduces versus the trunk base.
|
|
32
|
+
scope: changed
|
|
33
|
+
# Keep a single sticky summary comment that updates in place.
|
|
34
|
+
comment: true
|
|
35
|
+
# Avoid flooding the PR with inline comments / hitting comment limits.
|
|
36
|
+
review-comments: false
|
|
37
|
+
# No commit status (also keeps permissions to contents:read + PR write).
|
|
38
|
+
commit-status: false
|
|
@@ -57,9 +57,6 @@ export var Item = function Item(_ref) {
|
|
|
57
57
|
'&:last-child': {
|
|
58
58
|
borderBottomLeftRadius: 1,
|
|
59
59
|
borderBottomRightRadius: 1
|
|
60
|
-
},
|
|
61
|
-
'&:focus-within': function focusWithin(theme) {
|
|
62
|
-
return theme.outline;
|
|
63
60
|
}
|
|
64
61
|
},
|
|
65
62
|
children: children
|
|
@@ -108,6 +105,19 @@ export var Trigger = /*#__PURE__*/React.forwardRef(function (_ref2, forwardedRef
|
|
|
108
105
|
},
|
|
109
106
|
'&:hover': {
|
|
110
107
|
backgroundColor: 'accordion.background.hover'
|
|
108
|
+
},
|
|
109
|
+
// Keyboard-only focus ring on the header (the trigger button
|
|
110
|
+
// fills the header). Rendered inset, reusing the shared
|
|
111
|
+
// focus-ring colors, because the parent Item has overflow:hidden
|
|
112
|
+
// for its rounded corners which would clip an outset ring.
|
|
113
|
+
'&:focus-visible': function focusVisible(theme) {
|
|
114
|
+
var _theme$outline;
|
|
115
|
+
var ring = (_theme$outline = theme.outline) == null ? void 0 : _theme$outline.boxShadow;
|
|
116
|
+
return ring ? {
|
|
117
|
+
boxShadow: ring.split(/,(?![^(]*\))/).map(function (layer) {
|
|
118
|
+
return "inset " + layer.trim();
|
|
119
|
+
}).join(', ')
|
|
120
|
+
} : {};
|
|
111
121
|
}
|
|
112
122
|
}, sx)
|
|
113
123
|
}, props, {
|
|
@@ -11,7 +11,6 @@ export declare const inputBaseText = "input.text.default";
|
|
|
11
11
|
export declare const inputBaseBackground = "input.background.default";
|
|
12
12
|
export declare const baseControlFocusStyle: {
|
|
13
13
|
'&:focus-visible': (theme: InputTheme) => Record<string, string> | undefined;
|
|
14
|
-
'&:focus-within': (theme: InputTheme) => Record<string, string> | undefined;
|
|
15
14
|
};
|
|
16
15
|
export declare const baseControlStyle: {
|
|
17
16
|
'&:disabled': {
|
|
@@ -26,7 +25,6 @@ export declare const baseControlStyle: {
|
|
|
26
25
|
opacity: number;
|
|
27
26
|
};
|
|
28
27
|
'&:focus-visible': (theme: InputTheme) => Record<string, string> | undefined;
|
|
29
|
-
'&:focus-within': (theme: InputTheme) => Record<string, string> | undefined;
|
|
30
28
|
backgroundColor: string;
|
|
31
29
|
color: string;
|
|
32
30
|
borderRadius: number;
|
|
@@ -11,9 +11,6 @@ export var inputBaseBackground = 'input.background.default';
|
|
|
11
11
|
export var baseControlFocusStyle = {
|
|
12
12
|
'&:focus-visible': function focusVisible(theme) {
|
|
13
13
|
return theme.outline;
|
|
14
|
-
},
|
|
15
|
-
'&:focus-within': function focusWithin(theme) {
|
|
16
|
-
return theme.outline;
|
|
17
14
|
}
|
|
18
15
|
};
|
|
19
16
|
export var baseControlStyle = _extends({}, baseControlBorderStyle, {
|
|
@@ -11,7 +11,7 @@ export var inputStyle = function inputStyle(variant) {
|
|
|
11
11
|
clipPath: 'inset(50%)',
|
|
12
12
|
width: RADIO_SIZE,
|
|
13
13
|
height: RADIO_SIZE,
|
|
14
|
-
'&:focus ~ label:before': {
|
|
14
|
+
'&:focus-visible ~ label:before': {
|
|
15
15
|
variant: 'outline',
|
|
16
16
|
content: '""',
|
|
17
17
|
border: '1px solid',
|
|
@@ -4,6 +4,7 @@ import React from 'react';
|
|
|
4
4
|
* Internal dependencies
|
|
5
5
|
*/
|
|
6
6
|
import { WizardStepProps } from './WizardStep';
|
|
7
|
+
import { HeadingProps } from '../Heading/Heading';
|
|
7
8
|
export interface WizardProps {
|
|
8
9
|
/** The array of step configurations to render. */
|
|
9
10
|
steps: WizardStepProps[];
|
|
@@ -42,6 +43,18 @@ export interface WizardProps {
|
|
|
42
43
|
* @default 'table'
|
|
43
44
|
*/
|
|
44
45
|
summaryAs?: 'table' | 'dl';
|
|
46
|
+
/**
|
|
47
|
+
* The heading variant (typographic style) applied to every step title. A step
|
|
48
|
+
* can override this with its own `titleVariant`.
|
|
49
|
+
* @default 'h3'
|
|
50
|
+
*/
|
|
51
|
+
titleVariant?: HeadingProps['variant'];
|
|
52
|
+
/**
|
|
53
|
+
* The HTML heading element used for every step title, independent of its
|
|
54
|
+
* visual style — useful for keeping a correct document outline. A step can
|
|
55
|
+
* override this with its own `titleAs`. Defaults to the value of `titleVariant`.
|
|
56
|
+
*/
|
|
57
|
+
titleAs?: HeadingProps['variant'];
|
|
45
58
|
}
|
|
46
59
|
/**
|
|
47
60
|
* A multi-step wizard component that displays a sequence of steps with progress tracking.
|
|
@@ -32,7 +32,9 @@ export var Wizard = /*#__PURE__*/React.forwardRef(function (_ref, forwardRef) {
|
|
|
32
32
|
_ref$showStepText = _ref.showStepText,
|
|
33
33
|
showStepText = _ref$showStepText === void 0 ? true : _ref$showStepText,
|
|
34
34
|
_ref$summaryAs = _ref.summaryAs,
|
|
35
|
-
summaryAs = _ref$summaryAs === void 0 ? 'table' : _ref$summaryAs
|
|
35
|
+
summaryAs = _ref$summaryAs === void 0 ? 'table' : _ref$summaryAs,
|
|
36
|
+
titleVariant = _ref.titleVariant,
|
|
37
|
+
titleAs = _ref.titleAs;
|
|
36
38
|
var _useState = useState(false),
|
|
37
39
|
didMount = _useState[0],
|
|
38
40
|
setDidMount = _useState[1];
|
|
@@ -52,7 +54,8 @@ export var Wizard = /*#__PURE__*/React.forwardRef(function (_ref, forwardRef) {
|
|
|
52
54
|
var title = _ref2.title,
|
|
53
55
|
subTitle = _ref2.subTitle,
|
|
54
56
|
children = _ref2.children,
|
|
55
|
-
|
|
57
|
+
stepTitleVariant = _ref2.titleVariant,
|
|
58
|
+
stepTitleAs = _ref2.titleAs,
|
|
56
59
|
summary = _ref2.summary,
|
|
57
60
|
onChange = _ref2.onChange,
|
|
58
61
|
actionLabel = _ref2.actionLabel,
|
|
@@ -68,7 +71,8 @@ export var Wizard = /*#__PURE__*/React.forwardRef(function (_ref, forwardRef) {
|
|
|
68
71
|
totalSteps: steps.length,
|
|
69
72
|
subTitle: subTitle,
|
|
70
73
|
title: title,
|
|
71
|
-
titleVariant: titleVariant,
|
|
74
|
+
titleVariant: stepTitleVariant != null ? stepTitleVariant : titleVariant,
|
|
75
|
+
titleAs: stepTitleAs != null ? stepTitleAs : titleAs,
|
|
72
76
|
summary: summary,
|
|
73
77
|
onChange: onChange,
|
|
74
78
|
shouldFocusTitle: titleAutofocus && didMount,
|
|
@@ -12,6 +12,13 @@ declare const _default: {
|
|
|
12
12
|
export default _default;
|
|
13
13
|
type Story = StoryObj<typeof Wizard>;
|
|
14
14
|
export declare const Primary: Story;
|
|
15
|
+
/**
|
|
16
|
+
* `titleVariant` (visual style) and `titleAs` (semantic HTML element) can be set
|
|
17
|
+
* once at the Wizard level so they apply to every step, avoiding repetition. Here
|
|
18
|
+
* all titles render as `h2` elements while keeping `h3` styling, and the last step
|
|
19
|
+
* overrides `titleAs` to `h4` to demonstrate per-step precedence.
|
|
20
|
+
*/
|
|
21
|
+
export declare const WizardTitleLevels: Story;
|
|
15
22
|
export declare const Error: Story;
|
|
16
23
|
export declare const Default: Story;
|
|
17
24
|
export declare const WithTitleAutoFocus: Story;
|
|
@@ -37,6 +37,32 @@ export var Primary = {
|
|
|
37
37
|
}]
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `titleVariant` (visual style) and `titleAs` (semantic HTML element) can be set
|
|
43
|
+
* once at the Wizard level so they apply to every step, avoiding repetition. Here
|
|
44
|
+
* all titles render as `h2` elements while keeping `h3` styling, and the last step
|
|
45
|
+
* overrides `titleAs` to `h4` to demonstrate per-step precedence.
|
|
46
|
+
*/
|
|
47
|
+
export var WizardTitleLevels = {
|
|
48
|
+
args: {
|
|
49
|
+
activeStep: 0,
|
|
50
|
+
completed: [],
|
|
51
|
+
titleVariant: 'h3',
|
|
52
|
+
titleAs: 'h2',
|
|
53
|
+
steps: [{
|
|
54
|
+
title: 'Step One',
|
|
55
|
+
subTitle: 'Rendered as an h2 element with h3 styling.',
|
|
56
|
+
children: 'Step one content'
|
|
57
|
+
}, {
|
|
58
|
+
title: 'Step Two',
|
|
59
|
+
subTitle: 'Also an h2 element, inheriting the Wizard-level titleAs.'
|
|
60
|
+
}, {
|
|
61
|
+
title: 'Step Three (overrides titleAs)',
|
|
62
|
+
titleAs: 'h4'
|
|
63
|
+
}]
|
|
64
|
+
}
|
|
65
|
+
};
|
|
40
66
|
export var Error = {
|
|
41
67
|
render: function render() {
|
|
42
68
|
var steps = [{
|
|
@@ -116,4 +116,78 @@ describe('<Wizard /> error state', function () {
|
|
|
116
116
|
}));
|
|
117
117
|
expect(screen.getByText('Step instructions')).toBeInTheDocument();
|
|
118
118
|
});
|
|
119
|
+
});
|
|
120
|
+
describe('<Wizard /> title element and style', function () {
|
|
121
|
+
it('renders step titles as h3 elements by default', function () {
|
|
122
|
+
var _renderWithTheme4 = renderWithTheme(_jsx(Wizard, {
|
|
123
|
+
activeStep: 0,
|
|
124
|
+
steps: steps
|
|
125
|
+
})),
|
|
126
|
+
container = _renderWithTheme4.container;
|
|
127
|
+
expect(container.querySelectorAll('h3')).toHaveLength(steps.length);
|
|
128
|
+
});
|
|
129
|
+
it('applies a Wizard-level titleAs element to every step', function () {
|
|
130
|
+
var _renderWithTheme5 = renderWithTheme(_jsx(Wizard, {
|
|
131
|
+
activeStep: 0,
|
|
132
|
+
steps: steps,
|
|
133
|
+
titleAs: "h2"
|
|
134
|
+
})),
|
|
135
|
+
container = _renderWithTheme5.container;
|
|
136
|
+
expect(container.querySelectorAll('h2')).toHaveLength(steps.length);
|
|
137
|
+
expect(container.querySelectorAll('h3')).toHaveLength(0);
|
|
138
|
+
});
|
|
139
|
+
it('lets a step override the Wizard-level titleAs', function () {
|
|
140
|
+
var overrideSteps = [{
|
|
141
|
+
title: 'Step One'
|
|
142
|
+
}, {
|
|
143
|
+
title: 'Step Two',
|
|
144
|
+
titleAs: 'h4'
|
|
145
|
+
}];
|
|
146
|
+
var _renderWithTheme6 = renderWithTheme(_jsx(Wizard, {
|
|
147
|
+
activeStep: 0,
|
|
148
|
+
steps: overrideSteps,
|
|
149
|
+
titleAs: "h2"
|
|
150
|
+
})),
|
|
151
|
+
container = _renderWithTheme6.container;
|
|
152
|
+
|
|
153
|
+
// One inherits the Wizard default (h2), the other overrides to h4.
|
|
154
|
+
expect(container.querySelectorAll('h2')).toHaveLength(1);
|
|
155
|
+
expect(container.querySelectorAll('h4')).toHaveLength(1);
|
|
156
|
+
});
|
|
157
|
+
it('keeps titleVariant styling while titleAs changes the element', function () {
|
|
158
|
+
var _renderWithTheme7 = renderWithTheme(_jsx(Wizard, {
|
|
159
|
+
activeStep: 0,
|
|
160
|
+
steps: [{
|
|
161
|
+
title: 'Only Step'
|
|
162
|
+
}],
|
|
163
|
+
titleVariant: "h3",
|
|
164
|
+
titleAs: "h2"
|
|
165
|
+
})),
|
|
166
|
+
container = _renderWithTheme7.container;
|
|
167
|
+
|
|
168
|
+
// The rendered element is an h2 that still carries the shared heading class
|
|
169
|
+
// (typographic variant is driven by titleVariant, not the element).
|
|
170
|
+
var heading = container.querySelector('h2.vip-heading-component');
|
|
171
|
+
expect(heading).toBeInTheDocument();
|
|
172
|
+
});
|
|
173
|
+
it('has no accessibility violations when using a custom title element', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
174
|
+
var _renderWithTheme8, container, _t2;
|
|
175
|
+
return _regenerator().w(function (_context2) {
|
|
176
|
+
while (1) switch (_context2.n) {
|
|
177
|
+
case 0:
|
|
178
|
+
_renderWithTheme8 = renderWithTheme(_jsx(Wizard, {
|
|
179
|
+
activeStep: 0,
|
|
180
|
+
steps: steps,
|
|
181
|
+
titleAs: "h2"
|
|
182
|
+
})), container = _renderWithTheme8.container;
|
|
183
|
+
_t2 = expect;
|
|
184
|
+
_context2.n = 1;
|
|
185
|
+
return axe(container);
|
|
186
|
+
case 1:
|
|
187
|
+
_t2(_context2.v).toHaveNoViolations();
|
|
188
|
+
case 2:
|
|
189
|
+
return _context2.a(2);
|
|
190
|
+
}
|
|
191
|
+
}, _callee2);
|
|
192
|
+
})));
|
|
119
193
|
});
|
|
@@ -25,10 +25,16 @@ export interface WizardStepProps {
|
|
|
25
25
|
/** The title content displayed in the step header. */
|
|
26
26
|
title: React.ReactNode;
|
|
27
27
|
/**
|
|
28
|
-
* The heading variant used for the step title.
|
|
28
|
+
* The heading variant used for the step title. Controls the typographic style.
|
|
29
29
|
* @default 'h3'
|
|
30
30
|
*/
|
|
31
31
|
titleVariant?: HeadingProps['variant'];
|
|
32
|
+
/**
|
|
33
|
+
* The HTML heading element used to render the step title, independent of its
|
|
34
|
+
* visual style. Use this to keep a correct document outline (e.g. render an
|
|
35
|
+
* `h2` while keeping `h3` styling). Defaults to the value of `titleVariant`.
|
|
36
|
+
*/
|
|
37
|
+
titleAs?: HeadingProps['variant'];
|
|
32
38
|
/** A subtitle displayed below the title when the step is active. */
|
|
33
39
|
subTitle?: React.ReactNode;
|
|
34
40
|
/** The content rendered inside the step when it is active. */
|
|
@@ -32,6 +32,7 @@ export var WizardStep = /*#__PURE__*/React.forwardRef(function (_ref, forwardRef
|
|
|
32
32
|
shouldFocusTitle = _ref.shouldFocusTitle,
|
|
33
33
|
_ref$titleVariant = _ref.titleVariant,
|
|
34
34
|
titleVariant = _ref$titleVariant === void 0 ? 'h3' : _ref$titleVariant,
|
|
35
|
+
titleAs = _ref.titleAs,
|
|
35
36
|
summary = _ref.summary,
|
|
36
37
|
summaryTitle = _ref.summaryTitle,
|
|
37
38
|
_ref$summaryAs = _ref.summaryAs,
|
|
@@ -113,6 +114,7 @@ export var WizardStep = /*#__PURE__*/React.forwardRef(function (_ref, forwardRef
|
|
|
113
114
|
},
|
|
114
115
|
children: [_jsxs(Heading, {
|
|
115
116
|
variant: titleVariant,
|
|
117
|
+
as: titleAs != null ? titleAs : titleVariant,
|
|
116
118
|
sx: {
|
|
117
119
|
mb: 0,
|
|
118
120
|
color: headingColor,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://react.doctor/schema/config.json",
|
|
3
|
+
"rules": {
|
|
4
|
+
// theme-ui `sx` prop is flagged as an unknown DOM attribute everywhere — pure noise
|
|
5
|
+
"react-doctor/no-unknown-property": "off"
|
|
6
|
+
},
|
|
7
|
+
"ignore": {
|
|
8
|
+
// Glob patterns excluded from ALL rules.
|
|
9
|
+
"files": [
|
|
10
|
+
// Design-token source + Figma tooling: generated/build-time code (incl. the intentional
|
|
11
|
+
// `eval()` in the responsive-type calculator), never shipped in the component bundle.
|
|
12
|
+
"tokens/**"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -92,7 +92,6 @@ export const Item = ( { children, ...props }: AccordionItemProps ) => (
|
|
|
92
92
|
borderBottomLeftRadius: 1,
|
|
93
93
|
borderBottomRightRadius: 1,
|
|
94
94
|
},
|
|
95
|
-
'&:focus-within': ( theme: AccordionTheme ) => theme.outline,
|
|
96
95
|
} }
|
|
97
96
|
>
|
|
98
97
|
{ children }
|
|
@@ -136,6 +135,21 @@ export const Trigger = React.forwardRef< HTMLButtonElement, TriggerProps >(
|
|
|
136
135
|
'.vip-accordion-trigger-indicator': { transform: 'rotate(270deg)' },
|
|
137
136
|
},
|
|
138
137
|
'&:hover': { backgroundColor: 'accordion.background.hover' },
|
|
138
|
+
// Keyboard-only focus ring on the header (the trigger button
|
|
139
|
+
// fills the header). Rendered inset, reusing the shared
|
|
140
|
+
// focus-ring colors, because the parent Item has overflow:hidden
|
|
141
|
+
// for its rounded corners which would clip an outset ring.
|
|
142
|
+
'&:focus-visible': ( theme: AccordionTheme ) => {
|
|
143
|
+
const ring = theme.outline?.boxShadow;
|
|
144
|
+
return ring
|
|
145
|
+
? {
|
|
146
|
+
boxShadow: ring
|
|
147
|
+
.split( /,(?![^(]*\))/ )
|
|
148
|
+
.map( layer => `inset ${ layer.trim() }` )
|
|
149
|
+
.join( ', ' ),
|
|
150
|
+
}
|
|
151
|
+
: {};
|
|
152
|
+
},
|
|
139
153
|
...sx,
|
|
140
154
|
} }
|
|
141
155
|
{ ...props }
|
|
@@ -14,7 +14,6 @@ export const inputBaseText = 'input.text.default';
|
|
|
14
14
|
export const inputBaseBackground = 'input.background.default';
|
|
15
15
|
export const baseControlFocusStyle = {
|
|
16
16
|
'&:focus-visible': ( theme: InputTheme ) => theme.outline,
|
|
17
|
-
'&:focus-within': ( theme: InputTheme ) => theme.outline,
|
|
18
17
|
};
|
|
19
18
|
|
|
20
19
|
export const baseControlStyle = {
|
|
@@ -13,7 +13,7 @@ export const inputStyle = ( variant: string ): ThemeUIStyleObject => ( {
|
|
|
13
13
|
clipPath: 'inset(50%)',
|
|
14
14
|
width: RADIO_SIZE,
|
|
15
15
|
height: RADIO_SIZE,
|
|
16
|
-
'&:focus ~ label:before': {
|
|
16
|
+
'&:focus-visible ~ label:before': {
|
|
17
17
|
variant: 'outline',
|
|
18
18
|
content: '""',
|
|
19
19
|
border: '1px solid',
|
|
@@ -34,9 +34,7 @@ export const DialogTitle: React.FC< DialogTitleProps > = ( { title, hidden = fal
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
|
-
<DialogPrimitive.Title
|
|
38
|
-
sx={ { margin: 0, fontSize: 3, fontWeight: 'medium', color: 'heading' } }
|
|
39
|
-
>
|
|
37
|
+
<DialogPrimitive.Title sx={ { margin: 0, variant: 'text.h3', color: 'heading' } }>
|
|
40
38
|
{ titleNode }
|
|
41
39
|
</DialogPrimitive.Title>
|
|
42
40
|
);
|
|
@@ -47,6 +47,36 @@ export const Primary: Story = {
|
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* `titleVariant` (visual style) and `titleAs` (semantic HTML element) can be set
|
|
52
|
+
* once at the Wizard level so they apply to every step, avoiding repetition. Here
|
|
53
|
+
* all titles render as `h2` elements while keeping `h3` styling, and the last step
|
|
54
|
+
* overrides `titleAs` to `h4` to demonstrate per-step precedence.
|
|
55
|
+
*/
|
|
56
|
+
export const WizardTitleLevels: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
activeStep: 0,
|
|
59
|
+
completed: [],
|
|
60
|
+
titleVariant: 'h3',
|
|
61
|
+
titleAs: 'h2',
|
|
62
|
+
steps: [
|
|
63
|
+
{
|
|
64
|
+
title: 'Step One',
|
|
65
|
+
subTitle: 'Rendered as an h2 element with h3 styling.',
|
|
66
|
+
children: 'Step one content',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
title: 'Step Two',
|
|
70
|
+
subTitle: 'Also an h2 element, inheriting the Wizard-level titleAs.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: 'Step Three (overrides titleAs)',
|
|
74
|
+
titleAs: 'h4',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
50
80
|
export const Error: Story = {
|
|
51
81
|
render: () => {
|
|
52
82
|
const steps: WizardStepProps[] = [
|
|
@@ -77,3 +77,55 @@ describe( '<Wizard /> error state', () => {
|
|
|
77
77
|
expect( screen.getByText( 'Step instructions' ) ).toBeInTheDocument();
|
|
78
78
|
} );
|
|
79
79
|
} );
|
|
80
|
+
|
|
81
|
+
describe( '<Wizard /> title element and style', () => {
|
|
82
|
+
it( 'renders step titles as h3 elements by default', () => {
|
|
83
|
+
const { container } = renderWithTheme( <Wizard activeStep={ 0 } steps={ steps } /> );
|
|
84
|
+
|
|
85
|
+
expect( container.querySelectorAll( 'h3' ) ).toHaveLength( steps.length );
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
it( 'applies a Wizard-level titleAs element to every step', () => {
|
|
89
|
+
const { container } = renderWithTheme(
|
|
90
|
+
<Wizard activeStep={ 0 } steps={ steps } titleAs="h2" />
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect( container.querySelectorAll( 'h2' ) ).toHaveLength( steps.length );
|
|
94
|
+
expect( container.querySelectorAll( 'h3' ) ).toHaveLength( 0 );
|
|
95
|
+
} );
|
|
96
|
+
|
|
97
|
+
it( 'lets a step override the Wizard-level titleAs', () => {
|
|
98
|
+
const overrideSteps = [ { title: 'Step One' }, { title: 'Step Two', titleAs: 'h4' as const } ];
|
|
99
|
+
const { container } = renderWithTheme(
|
|
100
|
+
<Wizard activeStep={ 0 } steps={ overrideSteps } titleAs="h2" />
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// One inherits the Wizard default (h2), the other overrides to h4.
|
|
104
|
+
expect( container.querySelectorAll( 'h2' ) ).toHaveLength( 1 );
|
|
105
|
+
expect( container.querySelectorAll( 'h4' ) ).toHaveLength( 1 );
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
it( 'keeps titleVariant styling while titleAs changes the element', () => {
|
|
109
|
+
const { container } = renderWithTheme(
|
|
110
|
+
<Wizard
|
|
111
|
+
activeStep={ 0 }
|
|
112
|
+
steps={ [ { title: 'Only Step' } ] }
|
|
113
|
+
titleVariant="h3"
|
|
114
|
+
titleAs="h2"
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// The rendered element is an h2 that still carries the shared heading class
|
|
119
|
+
// (typographic variant is driven by titleVariant, not the element).
|
|
120
|
+
const heading = container.querySelector( 'h2.vip-heading-component' );
|
|
121
|
+
expect( heading ).toBeInTheDocument();
|
|
122
|
+
} );
|
|
123
|
+
|
|
124
|
+
it( 'has no accessibility violations when using a custom title element', async () => {
|
|
125
|
+
const { container } = renderWithTheme(
|
|
126
|
+
<Wizard activeStep={ 0 } steps={ steps } titleAs="h2" />
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect( await axe( container ) ).toHaveNoViolations();
|
|
130
|
+
} );
|
|
131
|
+
} );
|
|
@@ -11,6 +11,7 @@ import React, { useLayoutEffect, useState } from 'react';
|
|
|
11
11
|
*/
|
|
12
12
|
import { WizardStepProps } from './WizardStep';
|
|
13
13
|
import { Box, WizardStep } from '..';
|
|
14
|
+
import { HeadingProps } from '../Heading/Heading';
|
|
14
15
|
|
|
15
16
|
export interface WizardProps {
|
|
16
17
|
/** The array of step configurations to render. */
|
|
@@ -50,6 +51,18 @@ export interface WizardProps {
|
|
|
50
51
|
* @default 'table'
|
|
51
52
|
*/
|
|
52
53
|
summaryAs?: 'table' | 'dl';
|
|
54
|
+
/**
|
|
55
|
+
* The heading variant (typographic style) applied to every step title. A step
|
|
56
|
+
* can override this with its own `titleVariant`.
|
|
57
|
+
* @default 'h3'
|
|
58
|
+
*/
|
|
59
|
+
titleVariant?: HeadingProps[ 'variant' ];
|
|
60
|
+
/**
|
|
61
|
+
* The HTML heading element used for every step title, independent of its
|
|
62
|
+
* visual style — useful for keeping a correct document outline. A step can
|
|
63
|
+
* override this with its own `titleAs`. Defaults to the value of `titleVariant`.
|
|
64
|
+
*/
|
|
65
|
+
titleAs?: HeadingProps[ 'variant' ];
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
/**
|
|
@@ -68,6 +81,8 @@ export const Wizard = React.forwardRef< HTMLDivElement, WizardProps >(
|
|
|
68
81
|
titleAutofocus = false,
|
|
69
82
|
showStepText = true,
|
|
70
83
|
summaryAs = 'table',
|
|
84
|
+
titleVariant,
|
|
85
|
+
titleAs,
|
|
71
86
|
},
|
|
72
87
|
forwardRef
|
|
73
88
|
) => {
|
|
@@ -88,7 +103,8 @@ export const Wizard = React.forwardRef< HTMLDivElement, WizardProps >(
|
|
|
88
103
|
title,
|
|
89
104
|
subTitle,
|
|
90
105
|
children,
|
|
91
|
-
titleVariant,
|
|
106
|
+
titleVariant: stepTitleVariant,
|
|
107
|
+
titleAs: stepTitleAs,
|
|
92
108
|
summary,
|
|
93
109
|
onChange,
|
|
94
110
|
actionLabel,
|
|
@@ -108,7 +124,8 @@ export const Wizard = React.forwardRef< HTMLDivElement, WizardProps >(
|
|
|
108
124
|
totalSteps={ steps.length }
|
|
109
125
|
subTitle={ subTitle }
|
|
110
126
|
title={ title }
|
|
111
|
-
titleVariant={ titleVariant }
|
|
127
|
+
titleVariant={ stepTitleVariant ?? titleVariant }
|
|
128
|
+
titleAs={ stepTitleAs ?? titleAs }
|
|
112
129
|
summary={ summary }
|
|
113
130
|
onChange={ onChange }
|
|
114
131
|
shouldFocusTitle={ titleAutofocus && didMount }
|
|
@@ -35,10 +35,16 @@ export interface WizardStepProps {
|
|
|
35
35
|
/** The title content displayed in the step header. */
|
|
36
36
|
title: React.ReactNode;
|
|
37
37
|
/**
|
|
38
|
-
* The heading variant used for the step title.
|
|
38
|
+
* The heading variant used for the step title. Controls the typographic style.
|
|
39
39
|
* @default 'h3'
|
|
40
40
|
*/
|
|
41
41
|
titleVariant?: HeadingProps[ 'variant' ];
|
|
42
|
+
/**
|
|
43
|
+
* The HTML heading element used to render the step title, independent of its
|
|
44
|
+
* visual style. Use this to keep a correct document outline (e.g. render an
|
|
45
|
+
* `h2` while keeping `h3` styling). Defaults to the value of `titleVariant`.
|
|
46
|
+
*/
|
|
47
|
+
titleAs?: HeadingProps[ 'variant' ];
|
|
42
48
|
/** A subtitle displayed below the title when the step is active. */
|
|
43
49
|
subTitle?: React.ReactNode;
|
|
44
50
|
/** The content rendered inside the step when it is active. */
|
|
@@ -106,6 +112,7 @@ export const WizardStep = React.forwardRef< HTMLDivElement, WizardStepProps >(
|
|
|
106
112
|
totalSteps,
|
|
107
113
|
shouldFocusTitle,
|
|
108
114
|
titleVariant = 'h3',
|
|
115
|
+
titleAs,
|
|
109
116
|
summary,
|
|
110
117
|
summaryTitle,
|
|
111
118
|
summaryAs = 'table',
|
|
@@ -188,6 +195,7 @@ export const WizardStep = React.forwardRef< HTMLDivElement, WizardStepProps >(
|
|
|
188
195
|
<Flex sx={ { alignItems: 'center' } }>
|
|
189
196
|
<Heading
|
|
190
197
|
variant={ titleVariant }
|
|
198
|
+
as={ titleAs ?? titleVariant }
|
|
191
199
|
sx={ {
|
|
192
200
|
mb: 0,
|
|
193
201
|
color: headingColor,
|