@instructure/ui-breadcrumb 10.10.1-snapshot-1 → 10.10.1-snapshot-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/CHANGELOG.md +5 -2
- package/es/Breadcrumb/BreadcrumbLink/index.js +4 -1
- package/es/Breadcrumb/BreadcrumbLink/props.js +3 -2
- package/es/Breadcrumb/__new-tests__/Breadcrumb.test.js +54 -1
- package/es/Breadcrumb/index.js +16 -1
- package/lib/Breadcrumb/BreadcrumbLink/index.js +4 -1
- package/lib/Breadcrumb/BreadcrumbLink/props.js +3 -2
- package/lib/Breadcrumb/__new-tests__/Breadcrumb.test.js +56 -3
- package/lib/Breadcrumb/index.js +16 -1
- package/package.json +15 -15
- package/src/Breadcrumb/BreadcrumbLink/index.tsx +10 -2
- package/src/Breadcrumb/BreadcrumbLink/props.ts +9 -2
- package/src/Breadcrumb/README.md +17 -4
- package/src/Breadcrumb/__new-tests__/Breadcrumb.test.tsx +64 -0
- package/src/Breadcrumb/index.tsx +29 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Breadcrumb/BreadcrumbLink/index.d.ts +2 -0
- package/types/Breadcrumb/BreadcrumbLink/index.d.ts.map +1 -1
- package/types/Breadcrumb/BreadcrumbLink/props.d.ts +5 -0
- package/types/Breadcrumb/BreadcrumbLink/props.d.ts.map +1 -1
- package/types/Breadcrumb/index.d.ts +3 -0
- package/types/Breadcrumb/index.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [10.10.1-snapshot-
|
|
6
|
+
## [10.10.1-snapshot-2](https://github.com/instructure/instructure-ui/compare/v10.10.0...v10.10.1-snapshot-2) (2025-01-09)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **ui-breadcrumb:** add and update aria tags in Breadcrumb and in documentation ([e8db0f2](https://github.com/instructure/instructure-ui/commit/e8db0f20b5f8acc6662a1c8cc7c2530466e6869d))
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
|
|
@@ -50,7 +50,8 @@ let BreadcrumbLink = (_dec = testable(), _dec(_class = (_BreadcrumbLink = class
|
|
|
50
50
|
renderIcon = _this$props.renderIcon,
|
|
51
51
|
iconPlacement = _this$props.iconPlacement,
|
|
52
52
|
onClick = _this$props.onClick,
|
|
53
|
-
onMouseEnter = _this$props.onMouseEnter
|
|
53
|
+
onMouseEnter = _this$props.onMouseEnter,
|
|
54
|
+
isCurrentPage = _this$props.isCurrentPage;
|
|
54
55
|
const props = omitProps(this.props, BreadcrumbLink.allowedProps);
|
|
55
56
|
return /*#__PURE__*/React.createElement(Link, Object.assign({}, props, {
|
|
56
57
|
href: href,
|
|
@@ -60,6 +61,8 @@ let BreadcrumbLink = (_dec = testable(), _dec(_class = (_BreadcrumbLink = class
|
|
|
60
61
|
onMouseEnter: onMouseEnter,
|
|
61
62
|
isWithinText: false,
|
|
62
63
|
elementRef: this.handleRef
|
|
64
|
+
}, isCurrentPage && {
|
|
65
|
+
'aria-current': 'page'
|
|
63
66
|
}), /*#__PURE__*/React.createElement(TruncateText, null, children));
|
|
64
67
|
}
|
|
65
68
|
}, _BreadcrumbLink.displayName = "BreadcrumbLink", _BreadcrumbLink.componentId = 'Breadcrumb.Link', _BreadcrumbLink.propTypes = propTypes, _BreadcrumbLink.allowedProps = allowedProps, _BreadcrumbLink.defaultProps = {}, _BreadcrumbLink)) || _class);
|
|
@@ -30,7 +30,8 @@ const propTypes = {
|
|
|
30
30
|
onMouseEnter: PropTypes.func,
|
|
31
31
|
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
|
32
32
|
renderIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
|
|
33
|
-
iconPlacement: PropTypes.oneOf(['start', 'end'])
|
|
33
|
+
iconPlacement: PropTypes.oneOf(['start', 'end']),
|
|
34
|
+
isCurrentPage: PropTypes.bool
|
|
34
35
|
};
|
|
35
|
-
const allowedProps = ['children', 'href', 'iconPlacement', 'onClick', 'onMouseEnter', 'renderIcon', 'size'];
|
|
36
|
+
const allowedProps = ['children', 'href', 'iconPlacement', 'onClick', 'onMouseEnter', 'renderIcon', 'size', 'isCurrentPage'];
|
|
36
37
|
export { propTypes, allowedProps };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var _Breadcrumb, _Breadcrumb2, _Breadcrumb3;
|
|
1
|
+
var _Breadcrumb, _Breadcrumb2, _Breadcrumb3, _Breadcrumb4, _Breadcrumb5, _Breadcrumb6, _Breadcrumb7;
|
|
2
2
|
/*
|
|
3
3
|
* The MIT License (MIT)
|
|
4
4
|
*
|
|
@@ -89,4 +89,57 @@ describe('<Breadcrumb />', () => {
|
|
|
89
89
|
expect(icon).toBeInTheDocument();
|
|
90
90
|
expect(icon).toHaveAttribute('aria-hidden', 'true');
|
|
91
91
|
});
|
|
92
|
+
it('should add aria-current="page" to the last element by default', () => {
|
|
93
|
+
const _render4 = render(_Breadcrumb4 || (_Breadcrumb4 = /*#__PURE__*/React.createElement(Breadcrumb, {
|
|
94
|
+
label: TEST_LABEL
|
|
95
|
+
}, /*#__PURE__*/React.createElement(Breadcrumb.Link, {
|
|
96
|
+
href: TEST_LINK
|
|
97
|
+
}, TEST_TEXT_01), /*#__PURE__*/React.createElement(Breadcrumb.Link, null, TEST_TEXT_02)))),
|
|
98
|
+
container = _render4.container;
|
|
99
|
+
const links = container.querySelectorAll('[class$="--block-link"]');
|
|
100
|
+
const firstLink = links[0];
|
|
101
|
+
const lastLink = links[links.length - 1];
|
|
102
|
+
expect(firstLink).not.toHaveAttribute('aria-current', 'page');
|
|
103
|
+
expect(lastLink).toHaveAttribute('aria-current', 'page');
|
|
104
|
+
});
|
|
105
|
+
it('should add aria-current="page" to the element if isCurrent is true', () => {
|
|
106
|
+
const _render5 = render(_Breadcrumb5 || (_Breadcrumb5 = /*#__PURE__*/React.createElement(Breadcrumb, {
|
|
107
|
+
label: TEST_LABEL
|
|
108
|
+
}, /*#__PURE__*/React.createElement(Breadcrumb.Link, {
|
|
109
|
+
isCurrentPage: true,
|
|
110
|
+
href: TEST_LINK
|
|
111
|
+
}, TEST_TEXT_01), /*#__PURE__*/React.createElement(Breadcrumb.Link, null, TEST_TEXT_02)))),
|
|
112
|
+
container = _render5.container;
|
|
113
|
+
const links = container.querySelectorAll('[class$="--block-link"]');
|
|
114
|
+
const firstLink = links[0];
|
|
115
|
+
const lastLink = links[links.length - 1];
|
|
116
|
+
expect(firstLink).toHaveAttribute('aria-current', 'page');
|
|
117
|
+
expect(lastLink).not.toHaveAttribute('aria-current', 'page');
|
|
118
|
+
});
|
|
119
|
+
it('should throw a warning when multiple elements have isCurrent set to true ', () => {
|
|
120
|
+
render(_Breadcrumb6 || (_Breadcrumb6 = /*#__PURE__*/React.createElement(Breadcrumb, {
|
|
121
|
+
label: TEST_LABEL
|
|
122
|
+
}, /*#__PURE__*/React.createElement(Breadcrumb.Link, {
|
|
123
|
+
isCurrentPage: true,
|
|
124
|
+
href: TEST_LINK
|
|
125
|
+
}, TEST_TEXT_01), /*#__PURE__*/React.createElement(Breadcrumb.Link, {
|
|
126
|
+
isCurrentPage: true
|
|
127
|
+
}, TEST_TEXT_02))));
|
|
128
|
+
expect(consoleWarningMock).toHaveBeenCalledWith(expect.stringContaining('Warning: Multiple elements with isCurrentPage=true found. Only one element should be set to current.'));
|
|
129
|
+
});
|
|
130
|
+
it('should not add aria-current="page" to the last element if it set to false', () => {
|
|
131
|
+
const _render6 = render(_Breadcrumb7 || (_Breadcrumb7 = /*#__PURE__*/React.createElement(Breadcrumb, {
|
|
132
|
+
label: TEST_LABEL
|
|
133
|
+
}, /*#__PURE__*/React.createElement(Breadcrumb.Link, {
|
|
134
|
+
href: TEST_LINK
|
|
135
|
+
}, TEST_TEXT_01), /*#__PURE__*/React.createElement(Breadcrumb.Link, {
|
|
136
|
+
isCurrentPage: false
|
|
137
|
+
}, TEST_TEXT_02)))),
|
|
138
|
+
container = _render6.container;
|
|
139
|
+
const links = container.querySelectorAll('[class$="--block-link"]');
|
|
140
|
+
const firstLink = links[0];
|
|
141
|
+
const lastLink = links[links.length - 1];
|
|
142
|
+
expect(firstLink).not.toHaveAttribute('aria-current', 'page');
|
|
143
|
+
expect(lastLink).not.toHaveAttribute('aria-current', 'page');
|
|
144
|
+
});
|
|
92
145
|
});
|
package/es/Breadcrumb/index.js
CHANGED
|
@@ -45,6 +45,12 @@ let Breadcrumb = (_dec = withStyle(generateStyle, generateComponentTheme), _dec2
|
|
|
45
45
|
this.handleRef = el => {
|
|
46
46
|
this.ref = el;
|
|
47
47
|
};
|
|
48
|
+
this.addAriaCurrent = child => {
|
|
49
|
+
const updatedChild = /*#__PURE__*/React.cloneElement(child, {
|
|
50
|
+
'aria-current': 'page'
|
|
51
|
+
});
|
|
52
|
+
return updatedChild;
|
|
53
|
+
};
|
|
48
54
|
}
|
|
49
55
|
componentDidMount() {
|
|
50
56
|
var _this$props$makeStyle, _this$props;
|
|
@@ -62,11 +68,20 @@ let Breadcrumb = (_dec = withStyle(generateStyle, generateComponentTheme), _dec2
|
|
|
62
68
|
const inlineStyle = {
|
|
63
69
|
maxWidth: `${Math.floor(100 / numChildren)}%`
|
|
64
70
|
};
|
|
71
|
+
let isAriaCurrentSet = false;
|
|
65
72
|
return React.Children.map(children, (child, index) => {
|
|
73
|
+
const isLastElement = index === numChildren - 1;
|
|
74
|
+
if (/*#__PURE__*/React.isValidElement(child)) {
|
|
75
|
+
const isCurrentPage = child.props.isCurrentPage || false;
|
|
76
|
+
if (isAriaCurrentSet && isCurrentPage) {}
|
|
77
|
+
if (isCurrentPage) {
|
|
78
|
+
isAriaCurrentSet = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
66
81
|
return jsx("li", {
|
|
67
82
|
css: styles === null || styles === void 0 ? void 0 : styles.crumb,
|
|
68
83
|
style: inlineStyle
|
|
69
|
-
}, child, index < numChildren - 1 && jsx(IconArrowOpenEndSolid, {
|
|
84
|
+
}, !isAriaCurrentSet && isLastElement && child.props.isCurrentPage !== false ? this.addAriaCurrent(child) : child, index < numChildren - 1 && jsx(IconArrowOpenEndSolid, {
|
|
70
85
|
color: "auto",
|
|
71
86
|
css: styles === null || styles === void 0 ? void 0 : styles.separator
|
|
72
87
|
}));
|
|
@@ -56,7 +56,8 @@ let BreadcrumbLink = exports.BreadcrumbLink = (_dec = (0, _testable.testable)(),
|
|
|
56
56
|
renderIcon = _this$props.renderIcon,
|
|
57
57
|
iconPlacement = _this$props.iconPlacement,
|
|
58
58
|
onClick = _this$props.onClick,
|
|
59
|
-
onMouseEnter = _this$props.onMouseEnter
|
|
59
|
+
onMouseEnter = _this$props.onMouseEnter,
|
|
60
|
+
isCurrentPage = _this$props.isCurrentPage;
|
|
60
61
|
const props = (0, _omitProps.omitProps)(this.props, BreadcrumbLink.allowedProps);
|
|
61
62
|
return /*#__PURE__*/_react.default.createElement(_Link.Link, Object.assign({}, props, {
|
|
62
63
|
href: href,
|
|
@@ -66,6 +67,8 @@ let BreadcrumbLink = exports.BreadcrumbLink = (_dec = (0, _testable.testable)(),
|
|
|
66
67
|
onMouseEnter: onMouseEnter,
|
|
67
68
|
isWithinText: false,
|
|
68
69
|
elementRef: this.handleRef
|
|
70
|
+
}, isCurrentPage && {
|
|
71
|
+
'aria-current': 'page'
|
|
69
72
|
}), /*#__PURE__*/_react.default.createElement(_TruncateText.TruncateText, null, children));
|
|
70
73
|
}
|
|
71
74
|
}, _BreadcrumbLink.displayName = "BreadcrumbLink", _BreadcrumbLink.componentId = 'Breadcrumb.Link', _BreadcrumbLink.propTypes = _props.propTypes, _BreadcrumbLink.allowedProps = _props.allowedProps, _BreadcrumbLink.defaultProps = {}, _BreadcrumbLink)) || _class);
|
|
@@ -37,6 +37,7 @@ const propTypes = exports.propTypes = {
|
|
|
37
37
|
onMouseEnter: _propTypes.default.func,
|
|
38
38
|
size: _propTypes.default.oneOf(['small', 'medium', 'large']),
|
|
39
39
|
renderIcon: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.func]),
|
|
40
|
-
iconPlacement: _propTypes.default.oneOf(['start', 'end'])
|
|
40
|
+
iconPlacement: _propTypes.default.oneOf(['start', 'end']),
|
|
41
|
+
isCurrentPage: _propTypes.default.bool
|
|
41
42
|
};
|
|
42
|
-
const allowedProps = exports.allowedProps = ['children', 'href', 'iconPlacement', 'onClick', 'onMouseEnter', 'renderIcon', 'size'];
|
|
43
|
+
const allowedProps = exports.allowedProps = ['children', 'href', 'iconPlacement', 'onClick', 'onMouseEnter', 'renderIcon', 'size', 'isCurrentPage'];
|
|
@@ -8,8 +8,8 @@ require("@testing-library/jest-dom");
|
|
|
8
8
|
var _generateA11yTests = require("@instructure/ui-scripts/lib/test/generateA11yTests");
|
|
9
9
|
var _runAxeCheck = require("@instructure/ui-axe-check/lib/runAxeCheck.js");
|
|
10
10
|
var _index = require("../index");
|
|
11
|
-
var
|
|
12
|
-
var _Breadcrumb, _Breadcrumb2, _Breadcrumb3;
|
|
11
|
+
var _Breadcrumb8 = _interopRequireDefault(require("../__examples__/Breadcrumb.examples"));
|
|
12
|
+
var _Breadcrumb, _Breadcrumb2, _Breadcrumb3, _Breadcrumb4, _Breadcrumb5, _Breadcrumb6, _Breadcrumb7;
|
|
13
13
|
/*
|
|
14
14
|
* The MIT License (MIT)
|
|
15
15
|
*
|
|
@@ -61,7 +61,7 @@ describe('<Breadcrumb />', () => {
|
|
|
61
61
|
expect(axeCheck).toBe(true);
|
|
62
62
|
});
|
|
63
63
|
describe('with generated examples', () => {
|
|
64
|
-
const generatedComponents = (0, _generateA11yTests.generateA11yTests)(_index.Breadcrumb,
|
|
64
|
+
const generatedComponents = (0, _generateA11yTests.generateA11yTests)(_index.Breadcrumb, _Breadcrumb8.default);
|
|
65
65
|
it.each(generatedComponents)('should be accessible with example: $description', async ({
|
|
66
66
|
content
|
|
67
67
|
}) => {
|
|
@@ -90,4 +90,57 @@ describe('<Breadcrumb />', () => {
|
|
|
90
90
|
expect(icon).toBeInTheDocument();
|
|
91
91
|
expect(icon).toHaveAttribute('aria-hidden', 'true');
|
|
92
92
|
});
|
|
93
|
+
it('should add aria-current="page" to the last element by default', () => {
|
|
94
|
+
const _render4 = (0, _react2.render)(_Breadcrumb4 || (_Breadcrumb4 = /*#__PURE__*/_react.default.createElement(_index.Breadcrumb, {
|
|
95
|
+
label: TEST_LABEL
|
|
96
|
+
}, /*#__PURE__*/_react.default.createElement(_index.Breadcrumb.Link, {
|
|
97
|
+
href: TEST_LINK
|
|
98
|
+
}, TEST_TEXT_01), /*#__PURE__*/_react.default.createElement(_index.Breadcrumb.Link, null, TEST_TEXT_02)))),
|
|
99
|
+
container = _render4.container;
|
|
100
|
+
const links = container.querySelectorAll('[class$="--block-link"]');
|
|
101
|
+
const firstLink = links[0];
|
|
102
|
+
const lastLink = links[links.length - 1];
|
|
103
|
+
expect(firstLink).not.toHaveAttribute('aria-current', 'page');
|
|
104
|
+
expect(lastLink).toHaveAttribute('aria-current', 'page');
|
|
105
|
+
});
|
|
106
|
+
it('should add aria-current="page" to the element if isCurrent is true', () => {
|
|
107
|
+
const _render5 = (0, _react2.render)(_Breadcrumb5 || (_Breadcrumb5 = /*#__PURE__*/_react.default.createElement(_index.Breadcrumb, {
|
|
108
|
+
label: TEST_LABEL
|
|
109
|
+
}, /*#__PURE__*/_react.default.createElement(_index.Breadcrumb.Link, {
|
|
110
|
+
isCurrentPage: true,
|
|
111
|
+
href: TEST_LINK
|
|
112
|
+
}, TEST_TEXT_01), /*#__PURE__*/_react.default.createElement(_index.Breadcrumb.Link, null, TEST_TEXT_02)))),
|
|
113
|
+
container = _render5.container;
|
|
114
|
+
const links = container.querySelectorAll('[class$="--block-link"]');
|
|
115
|
+
const firstLink = links[0];
|
|
116
|
+
const lastLink = links[links.length - 1];
|
|
117
|
+
expect(firstLink).toHaveAttribute('aria-current', 'page');
|
|
118
|
+
expect(lastLink).not.toHaveAttribute('aria-current', 'page');
|
|
119
|
+
});
|
|
120
|
+
it('should throw a warning when multiple elements have isCurrent set to true ', () => {
|
|
121
|
+
(0, _react2.render)(_Breadcrumb6 || (_Breadcrumb6 = /*#__PURE__*/_react.default.createElement(_index.Breadcrumb, {
|
|
122
|
+
label: TEST_LABEL
|
|
123
|
+
}, /*#__PURE__*/_react.default.createElement(_index.Breadcrumb.Link, {
|
|
124
|
+
isCurrentPage: true,
|
|
125
|
+
href: TEST_LINK
|
|
126
|
+
}, TEST_TEXT_01), /*#__PURE__*/_react.default.createElement(_index.Breadcrumb.Link, {
|
|
127
|
+
isCurrentPage: true
|
|
128
|
+
}, TEST_TEXT_02))));
|
|
129
|
+
expect(consoleWarningMock).toHaveBeenCalledWith(expect.stringContaining('Warning: Multiple elements with isCurrentPage=true found. Only one element should be set to current.'));
|
|
130
|
+
});
|
|
131
|
+
it('should not add aria-current="page" to the last element if it set to false', () => {
|
|
132
|
+
const _render6 = (0, _react2.render)(_Breadcrumb7 || (_Breadcrumb7 = /*#__PURE__*/_react.default.createElement(_index.Breadcrumb, {
|
|
133
|
+
label: TEST_LABEL
|
|
134
|
+
}, /*#__PURE__*/_react.default.createElement(_index.Breadcrumb.Link, {
|
|
135
|
+
href: TEST_LINK
|
|
136
|
+
}, TEST_TEXT_01), /*#__PURE__*/_react.default.createElement(_index.Breadcrumb.Link, {
|
|
137
|
+
isCurrentPage: false
|
|
138
|
+
}, TEST_TEXT_02)))),
|
|
139
|
+
container = _render6.container;
|
|
140
|
+
const links = container.querySelectorAll('[class$="--block-link"]');
|
|
141
|
+
const firstLink = links[0];
|
|
142
|
+
const lastLink = links[links.length - 1];
|
|
143
|
+
expect(firstLink).not.toHaveAttribute('aria-current', 'page');
|
|
144
|
+
expect(lastLink).not.toHaveAttribute('aria-current', 'page');
|
|
145
|
+
});
|
|
93
146
|
});
|
package/lib/Breadcrumb/index.js
CHANGED
|
@@ -59,6 +59,12 @@ let Breadcrumb = exports.Breadcrumb = (_dec = (0, _emotion.withStyle)(_styles.de
|
|
|
59
59
|
this.handleRef = el => {
|
|
60
60
|
this.ref = el;
|
|
61
61
|
};
|
|
62
|
+
this.addAriaCurrent = child => {
|
|
63
|
+
const updatedChild = /*#__PURE__*/_react.default.cloneElement(child, {
|
|
64
|
+
'aria-current': 'page'
|
|
65
|
+
});
|
|
66
|
+
return updatedChild;
|
|
67
|
+
};
|
|
62
68
|
}
|
|
63
69
|
componentDidMount() {
|
|
64
70
|
var _this$props$makeStyle, _this$props;
|
|
@@ -76,11 +82,20 @@ let Breadcrumb = exports.Breadcrumb = (_dec = (0, _emotion.withStyle)(_styles.de
|
|
|
76
82
|
const inlineStyle = {
|
|
77
83
|
maxWidth: `${Math.floor(100 / numChildren)}%`
|
|
78
84
|
};
|
|
85
|
+
let isAriaCurrentSet = false;
|
|
79
86
|
return _react.default.Children.map(children, (child, index) => {
|
|
87
|
+
const isLastElement = index === numChildren - 1;
|
|
88
|
+
if (/*#__PURE__*/_react.default.isValidElement(child)) {
|
|
89
|
+
const isCurrentPage = child.props.isCurrentPage || false;
|
|
90
|
+
if (isAriaCurrentSet && isCurrentPage) {}
|
|
91
|
+
if (isCurrentPage) {
|
|
92
|
+
isAriaCurrentSet = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
80
95
|
return (0, _emotion.jsx)("li", {
|
|
81
96
|
css: styles === null || styles === void 0 ? void 0 : styles.crumb,
|
|
82
97
|
style: inlineStyle
|
|
83
|
-
}, child, index < numChildren - 1 && (0, _emotion.jsx)(_IconArrowOpenEndSolid.IconArrowOpenEndSolid, {
|
|
98
|
+
}, !isAriaCurrentSet && isLastElement && child.props.isCurrentPage !== false ? this.addAriaCurrent(child) : child, index < numChildren - 1 && (0, _emotion.jsx)(_IconArrowOpenEndSolid.IconArrowOpenEndSolid, {
|
|
84
99
|
color: "auto",
|
|
85
100
|
css: styles === null || styles === void 0 ? void 0 : styles.separator
|
|
86
101
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/ui-breadcrumb",
|
|
3
|
-
"version": "10.10.1-snapshot-
|
|
3
|
+
"version": "10.10.1-snapshot-2",
|
|
4
4
|
"description": "A breadcrumb component",
|
|
5
5
|
"author": "Instructure, Inc. Engineering and Product Design",
|
|
6
6
|
"module": "./es/index.js",
|
|
@@ -23,26 +23,26 @@
|
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@instructure/ui-axe-check": "10.10.1-snapshot-
|
|
27
|
-
"@instructure/ui-babel-preset": "10.10.1-snapshot-
|
|
28
|
-
"@instructure/ui-scripts": "10.10.1-snapshot-
|
|
29
|
-
"@instructure/ui-themes": "10.10.1-snapshot-
|
|
26
|
+
"@instructure/ui-axe-check": "10.10.1-snapshot-2",
|
|
27
|
+
"@instructure/ui-babel-preset": "10.10.1-snapshot-2",
|
|
28
|
+
"@instructure/ui-scripts": "10.10.1-snapshot-2",
|
|
29
|
+
"@instructure/ui-themes": "10.10.1-snapshot-2",
|
|
30
30
|
"@testing-library/jest-dom": "^6.6.3",
|
|
31
31
|
"@testing-library/react": "^16.0.1",
|
|
32
32
|
"vitest": "^2.1.8"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@babel/runtime": "^7.26.0",
|
|
36
|
-
"@instructure/emotion": "10.10.1-snapshot-
|
|
37
|
-
"@instructure/shared-types": "10.10.1-snapshot-
|
|
38
|
-
"@instructure/ui-icons": "10.10.1-snapshot-
|
|
39
|
-
"@instructure/ui-link": "10.10.1-snapshot-
|
|
40
|
-
"@instructure/ui-prop-types": "10.10.1-snapshot-
|
|
41
|
-
"@instructure/ui-react-utils": "10.10.1-snapshot-
|
|
42
|
-
"@instructure/ui-testable": "10.10.1-snapshot-
|
|
43
|
-
"@instructure/ui-truncate-text": "10.10.1-snapshot-
|
|
44
|
-
"@instructure/ui-utils": "10.10.1-snapshot-
|
|
45
|
-
"@instructure/ui-view": "10.10.1-snapshot-
|
|
36
|
+
"@instructure/emotion": "10.10.1-snapshot-2",
|
|
37
|
+
"@instructure/shared-types": "10.10.1-snapshot-2",
|
|
38
|
+
"@instructure/ui-icons": "10.10.1-snapshot-2",
|
|
39
|
+
"@instructure/ui-link": "10.10.1-snapshot-2",
|
|
40
|
+
"@instructure/ui-prop-types": "10.10.1-snapshot-2",
|
|
41
|
+
"@instructure/ui-react-utils": "10.10.1-snapshot-2",
|
|
42
|
+
"@instructure/ui-testable": "10.10.1-snapshot-2",
|
|
43
|
+
"@instructure/ui-truncate-text": "10.10.1-snapshot-2",
|
|
44
|
+
"@instructure/ui-utils": "10.10.1-snapshot-2",
|
|
45
|
+
"@instructure/ui-view": "10.10.1-snapshot-2",
|
|
46
46
|
"prop-types": "^15.8.1"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
@@ -54,8 +54,15 @@ class BreadcrumbLink extends Component<BreadcrumbLinkProps> {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
render() {
|
|
57
|
-
const {
|
|
58
|
-
|
|
57
|
+
const {
|
|
58
|
+
children,
|
|
59
|
+
href,
|
|
60
|
+
renderIcon,
|
|
61
|
+
iconPlacement,
|
|
62
|
+
onClick,
|
|
63
|
+
onMouseEnter,
|
|
64
|
+
isCurrentPage
|
|
65
|
+
} = this.props
|
|
59
66
|
|
|
60
67
|
const props = omitProps(this.props, BreadcrumbLink.allowedProps)
|
|
61
68
|
|
|
@@ -69,6 +76,7 @@ class BreadcrumbLink extends Component<BreadcrumbLinkProps> {
|
|
|
69
76
|
onMouseEnter={onMouseEnter}
|
|
70
77
|
isWithinText={false}
|
|
71
78
|
elementRef={this.handleRef}
|
|
79
|
+
{...(isCurrentPage && { 'aria-current': 'page' })}
|
|
72
80
|
>
|
|
73
81
|
<TruncateText>{children}</TruncateText>
|
|
74
82
|
</Link>
|
|
@@ -63,6 +63,11 @@ type BreadcrumbLinkOwnProps = {
|
|
|
63
63
|
* Place the icon before or after the text in the Breadcrumb.Link
|
|
64
64
|
*/
|
|
65
65
|
iconPlacement?: 'start' | 'end'
|
|
66
|
+
/**
|
|
67
|
+
* Whether the page this breadcrumb points to is the current one. If true, it sets aria-current="page".
|
|
68
|
+
* If this prop is not set to true on any breadcrumb element, the one recieving the aria-current="page" will always be the last element, unless the last element's isCurrentPage prop is explicity set to false.
|
|
69
|
+
*/
|
|
70
|
+
isCurrentPage?: boolean
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
type PropKeys = keyof BreadcrumbLinkOwnProps
|
|
@@ -89,7 +94,8 @@ const propTypes: PropValidators<PropKeys> = {
|
|
|
89
94
|
onMouseEnter: PropTypes.func,
|
|
90
95
|
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
|
91
96
|
renderIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
|
|
92
|
-
iconPlacement: PropTypes.oneOf(['start', 'end'])
|
|
97
|
+
iconPlacement: PropTypes.oneOf(['start', 'end']),
|
|
98
|
+
isCurrentPage: PropTypes.bool
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
const allowedProps: AllowedPropKeys = [
|
|
@@ -99,7 +105,8 @@ const allowedProps: AllowedPropKeys = [
|
|
|
99
105
|
'onClick',
|
|
100
106
|
'onMouseEnter',
|
|
101
107
|
'renderIcon',
|
|
102
|
-
'size'
|
|
108
|
+
'size',
|
|
109
|
+
'isCurrentPage'
|
|
103
110
|
]
|
|
104
111
|
|
|
105
112
|
export type { BreadcrumbLinkProps }
|
package/src/Breadcrumb/README.md
CHANGED
|
@@ -23,7 +23,7 @@ type: example
|
|
|
23
23
|
{(props, matches) => {
|
|
24
24
|
if (matches.includes('tablet')) {
|
|
25
25
|
return (
|
|
26
|
-
<Breadcrumb label="
|
|
26
|
+
<Breadcrumb label="breadcrumb">
|
|
27
27
|
<Breadcrumb.Link href="#">Student Forecast</Breadcrumb.Link>
|
|
28
28
|
<Breadcrumb.Link href="#">University of Utah</Breadcrumb.Link>
|
|
29
29
|
<Breadcrumb.Link href="#">University of Utah Colleges</Breadcrumb.Link>
|
|
@@ -52,7 +52,7 @@ Change the `size` prop to control the font-size of the breadcrumbs (default is `
|
|
|
52
52
|
type: example
|
|
53
53
|
---
|
|
54
54
|
<div>
|
|
55
|
-
<Breadcrumb size="small" label="
|
|
55
|
+
<Breadcrumb size="small" label="breadcrumb" margin="none none medium">
|
|
56
56
|
<Breadcrumb.Link href="https://instructure.github.io/instructure-ui/">English 204</Breadcrumb.Link>
|
|
57
57
|
<Breadcrumb.Link
|
|
58
58
|
onClick={function () {
|
|
@@ -65,7 +65,7 @@ type: example
|
|
|
65
65
|
<Breadcrumb.Link>Rabbit Is Rich</Breadcrumb.Link>
|
|
66
66
|
</Breadcrumb>
|
|
67
67
|
<View as="div" width="40rem">
|
|
68
|
-
<Breadcrumb label="
|
|
68
|
+
<Breadcrumb label="breadcrumb" margin="none none medium">
|
|
69
69
|
<Breadcrumb.Link href="https://instructure.github.io/instructure-ui/">English 204</Breadcrumb.Link>
|
|
70
70
|
<Breadcrumb.Link
|
|
71
71
|
onClick={function () {
|
|
@@ -78,7 +78,7 @@ type: example
|
|
|
78
78
|
<Breadcrumb.Link>Rabbit Is Rich</Breadcrumb.Link>
|
|
79
79
|
</Breadcrumb>
|
|
80
80
|
</View>
|
|
81
|
-
<Breadcrumb size="large" label="
|
|
81
|
+
<Breadcrumb size="large" label="breadcrumb">
|
|
82
82
|
<Breadcrumb.Link href="https://instructure.github.io/instructure-ui/">English 204</Breadcrumb.Link>
|
|
83
83
|
<Breadcrumb.Link
|
|
84
84
|
onClick={function () {
|
|
@@ -126,3 +126,16 @@ type: embed
|
|
|
126
126
|
</Figure>
|
|
127
127
|
</Guidelines>
|
|
128
128
|
```
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
---
|
|
132
|
+
type: embed
|
|
133
|
+
---
|
|
134
|
+
<Guidelines>
|
|
135
|
+
<Figure recommendation="a11y" title="Accessibility">
|
|
136
|
+
<Figure.Item>
|
|
137
|
+
To indicate the current element within a breadcrumb, the <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current">aria-current</a> attribute is used. In this component, aria-current="page" will automatically be applied to the last element, and we recommend that the current page always be the last element in the breadcrumb. If the last element is not the current page, the isCurrentPage property should be applied to the relevant Breadcrumb.Link to ensure compatibility with screen readers.
|
|
138
|
+
</Figure.Item>
|
|
139
|
+
</Figure>
|
|
140
|
+
</Guidelines>
|
|
141
|
+
```
|
|
@@ -111,4 +111,68 @@ describe('<Breadcrumb />', () => {
|
|
|
111
111
|
expect(icon).toBeInTheDocument()
|
|
112
112
|
expect(icon).toHaveAttribute('aria-hidden', 'true')
|
|
113
113
|
})
|
|
114
|
+
|
|
115
|
+
it('should add aria-current="page" to the last element by default', () => {
|
|
116
|
+
const { container } = render(
|
|
117
|
+
<Breadcrumb label={TEST_LABEL}>
|
|
118
|
+
<Breadcrumb.Link href={TEST_LINK}>{TEST_TEXT_01}</Breadcrumb.Link>
|
|
119
|
+
<Breadcrumb.Link>{TEST_TEXT_02}</Breadcrumb.Link>
|
|
120
|
+
</Breadcrumb>
|
|
121
|
+
)
|
|
122
|
+
const links = container.querySelectorAll('[class$="--block-link"]')
|
|
123
|
+
const firstLink = links[0]
|
|
124
|
+
const lastLink = links[links.length - 1]
|
|
125
|
+
|
|
126
|
+
expect(firstLink).not.toHaveAttribute('aria-current', 'page')
|
|
127
|
+
expect(lastLink).toHaveAttribute('aria-current', 'page')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should add aria-current="page" to the element if isCurrent is true', () => {
|
|
131
|
+
const { container } = render(
|
|
132
|
+
<Breadcrumb label={TEST_LABEL}>
|
|
133
|
+
<Breadcrumb.Link isCurrentPage href={TEST_LINK}>
|
|
134
|
+
{TEST_TEXT_01}
|
|
135
|
+
</Breadcrumb.Link>
|
|
136
|
+
<Breadcrumb.Link>{TEST_TEXT_02}</Breadcrumb.Link>
|
|
137
|
+
</Breadcrumb>
|
|
138
|
+
)
|
|
139
|
+
const links = container.querySelectorAll('[class$="--block-link"]')
|
|
140
|
+
const firstLink = links[0]
|
|
141
|
+
const lastLink = links[links.length - 1]
|
|
142
|
+
|
|
143
|
+
expect(firstLink).toHaveAttribute('aria-current', 'page')
|
|
144
|
+
expect(lastLink).not.toHaveAttribute('aria-current', 'page')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should throw a warning when multiple elements have isCurrent set to true ', () => {
|
|
148
|
+
render(
|
|
149
|
+
<Breadcrumb label={TEST_LABEL}>
|
|
150
|
+
<Breadcrumb.Link isCurrentPage href={TEST_LINK}>
|
|
151
|
+
{TEST_TEXT_01}
|
|
152
|
+
</Breadcrumb.Link>
|
|
153
|
+
<Breadcrumb.Link isCurrentPage>{TEST_TEXT_02}</Breadcrumb.Link>
|
|
154
|
+
</Breadcrumb>
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
expect(consoleWarningMock).toHaveBeenCalledWith(
|
|
158
|
+
expect.stringContaining(
|
|
159
|
+
'Warning: Multiple elements with isCurrentPage=true found. Only one element should be set to current.'
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should not add aria-current="page" to the last element if it set to false', () => {
|
|
165
|
+
const { container } = render(
|
|
166
|
+
<Breadcrumb label={TEST_LABEL}>
|
|
167
|
+
<Breadcrumb.Link href={TEST_LINK}>{TEST_TEXT_01}</Breadcrumb.Link>
|
|
168
|
+
<Breadcrumb.Link isCurrentPage={false}>{TEST_TEXT_02}</Breadcrumb.Link>
|
|
169
|
+
</Breadcrumb>
|
|
170
|
+
)
|
|
171
|
+
const links = container.querySelectorAll('[class$="--block-link"]')
|
|
172
|
+
const firstLink = links[0]
|
|
173
|
+
const lastLink = links[links.length - 1]
|
|
174
|
+
|
|
175
|
+
expect(firstLink).not.toHaveAttribute('aria-current', 'page')
|
|
176
|
+
expect(lastLink).not.toHaveAttribute('aria-current', 'page')
|
|
177
|
+
})
|
|
114
178
|
})
|
package/src/Breadcrumb/index.tsx
CHANGED
|
@@ -62,6 +62,16 @@ class Breadcrumb extends Component<BreadcrumbProps> {
|
|
|
62
62
|
this.ref = el
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
addAriaCurrent = (child: React.ReactNode) => {
|
|
66
|
+
const updatedChild = React.cloneElement(
|
|
67
|
+
child as React.ReactElement<{ 'aria-current'?: string }>,
|
|
68
|
+
{
|
|
69
|
+
'aria-current': 'page'
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
return updatedChild
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
componentDidMount() {
|
|
66
76
|
this.props.makeStyles?.()
|
|
67
77
|
}
|
|
@@ -77,10 +87,28 @@ class Breadcrumb extends Component<BreadcrumbProps> {
|
|
|
77
87
|
const inlineStyle = {
|
|
78
88
|
maxWidth: `${Math.floor(100 / numChildren)}%`
|
|
79
89
|
}
|
|
90
|
+
let isAriaCurrentSet = false
|
|
91
|
+
|
|
80
92
|
return React.Children.map(children, (child, index) => {
|
|
93
|
+
const isLastElement = index === numChildren - 1
|
|
94
|
+
if (React.isValidElement(child)) {
|
|
95
|
+
const isCurrentPage = child.props.isCurrentPage || false
|
|
96
|
+
if (isAriaCurrentSet && isCurrentPage) {
|
|
97
|
+
console.warn(
|
|
98
|
+
`Warning: Multiple elements with isCurrentPage=true found. Only one element should be set to current.`
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
if (isCurrentPage) {
|
|
102
|
+
isAriaCurrentSet = true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
81
105
|
return (
|
|
82
106
|
<li css={styles?.crumb} style={inlineStyle}>
|
|
83
|
-
{
|
|
107
|
+
{!isAriaCurrentSet &&
|
|
108
|
+
isLastElement &&
|
|
109
|
+
(child as React.ReactElement).props.isCurrentPage !== false
|
|
110
|
+
? this.addAriaCurrent(child)
|
|
111
|
+
: child}
|
|
84
112
|
{index < numChildren - 1 && (
|
|
85
113
|
<IconArrowOpenEndSolid color="auto" css={styles?.separator} />
|
|
86
114
|
)}
|