@conduction/components 2.2.53 → 2.2.55
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/README.md +5 -0
- package/lib/components/card/cardHeader/CardHeader.module.css +2 -0
- package/lib/components/displaySwitch/DisplaySwitch.js +1 -1
- package/lib/components/formFields/select/select.js +44 -0
- package/lib/components/logo/Logo.d.ts +1 -0
- package/lib/components/logo/Logo.js +2 -2
- package/package.json +1 -1
- package/src/components/card/cardHeader/CardHeader.module.css +2 -0
- package/src/components/displaySwitch/DisplaySwitch.tsx +49 -49
- package/src/components/formFields/select/select.tsx +49 -0
- package/src/components/logo/Logo.tsx +24 -21
- package/src/components/topNav/primaryTopNav/PrimaryTopNav.tsx +133 -133
package/README.md
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
- **Version 2.2 (breaking changes from 2.1.x)**
|
|
6
6
|
|
|
7
|
+
- 2.2.55:
|
|
8
|
+
- Updated Logo to accept aria-label for accessibility.
|
|
9
|
+
- Fixed bug in DisplaySwitch where layoutClassName is added even when empty.
|
|
10
|
+
- Fixed color of the Select dropdown icon to be WCAG-AA compliant.
|
|
11
|
+
- 2.2.54: Updated CardHeader to allow padding-block-end on title.
|
|
7
12
|
- 2.2.53: Updated Pagination and PrimaryTopNav components to allow text-decorations and border-bottoms.
|
|
8
13
|
- 2.2.52: Added hover filter to Logo component.
|
|
9
14
|
- 2.2.51:
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
/* --conduction-card-header-title-hover-color: #000000; */
|
|
12
12
|
/* --conduction-card-header-title-hover-text-decoration: none; */
|
|
13
13
|
/* --conduction-card-header-title-hover-text-underline-offset: 2px; */
|
|
14
|
+
/* --conduction-card-header-title-padding-block-end: 0px; */
|
|
14
15
|
|
|
15
16
|
--conduction-card-header-date-color: #000000;
|
|
16
17
|
--conduction-card-header-date-font-size: 16px;
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
color: var(--conduction-card-header-title-color) !important;
|
|
36
37
|
text-decoration: var(--conduction-card-header-title-text-decoration) !important;
|
|
37
38
|
text-underline-offset: var(--conduction-card-header-title-text-underline-offset) !important;
|
|
39
|
+
padding-block-end: var(--conduction-card-header-title-padding-block-end) !important;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
.title:hover > * {
|
|
@@ -4,7 +4,7 @@ import clsx from "clsx";
|
|
|
4
4
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
5
5
|
import { Button, ButtonGroup } from "@utrecht/component-library-react/dist/css-module";
|
|
6
6
|
const DisplaySwitch = ({ layoutClassName, buttons }) => {
|
|
7
|
-
return (_jsx(ButtonGroup, { className: clsx(styles.displaySwitchButtons,
|
|
7
|
+
return (_jsx(ButtonGroup, { className: clsx(styles.displaySwitchButtons, layoutClassName && layoutClassName), children: buttons.map((button, idx) => {
|
|
8
8
|
// TODO: Once the Rotterdam design system supports the "pressed" state,
|
|
9
9
|
// remove the `appereance` switch, and use the same appearance for each button.
|
|
10
10
|
return (_jsxs(Button, { pressed: button.pressed, appearance: button.pressed ? "secondary-action-button" : "subtle-button", onClick: button.handleClick, className: styles.button, children: [button.icon && _jsx(FontAwesomeIcon, { icon: [button.icon.prefix, button.icon.name] }), _jsx("span", { children: button.label })] }, idx));
|
|
@@ -36,6 +36,13 @@ const selectStyles = {
|
|
|
36
36
|
fontFamily: `var(--conduction-input-select-placeholder-font-family, var(--utrecht-form-input-placeholder-font-family, ${base.fontFamily}))`,
|
|
37
37
|
color: `var(--conduction-input-select-placeholder-color, var(--utrecht-form-input-placeholder-color, ${base.color}) )`,
|
|
38
38
|
}),
|
|
39
|
+
dropdownIndicator: (base) => ({
|
|
40
|
+
...base,
|
|
41
|
+
color: "#949494",
|
|
42
|
+
"&:hover": {
|
|
43
|
+
color: "#949494",
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
39
46
|
};
|
|
40
47
|
const setAttributes = () => {
|
|
41
48
|
const setRoleToPresentation = (selector, role) => {
|
|
@@ -47,9 +54,46 @@ const setAttributes = () => {
|
|
|
47
54
|
element.removeAttribute("aria-live");
|
|
48
55
|
});
|
|
49
56
|
};
|
|
57
|
+
const updateIndicatorAttributes = (indicator, isInteractive) => {
|
|
58
|
+
if (isInteractive) {
|
|
59
|
+
indicator.setAttribute("role", "button");
|
|
60
|
+
indicator.setAttribute("tabindex", "0");
|
|
61
|
+
indicator.setAttribute("aria-label", "Clear selection");
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
indicator.setAttribute("role", "presentation");
|
|
65
|
+
indicator.removeAttribute("tabindex");
|
|
66
|
+
indicator.removeAttribute("aria-label");
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const setAriaLabelsForIndicators = () => {
|
|
70
|
+
document.querySelectorAll('[class*="control"]').forEach((control) => {
|
|
71
|
+
const indicatorsParent = control.querySelector('[class*="indicatorSeparator"]')?.parentElement;
|
|
72
|
+
if (!indicatorsParent)
|
|
73
|
+
return;
|
|
74
|
+
const indicators = indicatorsParent.querySelectorAll('[class*="indicatorContainer"]');
|
|
75
|
+
const hasSelection = indicators.length === 2;
|
|
76
|
+
indicators.forEach((indicator, index) => {
|
|
77
|
+
const isClearButton = hasSelection && index === 0;
|
|
78
|
+
updateIndicatorAttributes(indicator, isClearButton);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
// Initial static setup
|
|
50
83
|
setRoleToPresentation('[id*="live-region"]', "presentation");
|
|
51
84
|
setRoleToPresentation('[class*="indicatorSeparator"]', "separator");
|
|
52
85
|
setRoleToPresentation('[class*="a11yText"]', "presentation");
|
|
86
|
+
// Dynamic setup after render
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
setAriaLabelsForIndicators();
|
|
89
|
+
const observer = new MutationObserver(setAriaLabelsForIndicators);
|
|
90
|
+
document.querySelectorAll('[class*="control"]').forEach((control) => {
|
|
91
|
+
const indicatorsParent = control.querySelector('[class*="indicatorSeparator"]')?.parentElement;
|
|
92
|
+
if (indicatorsParent) {
|
|
93
|
+
observer.observe(indicatorsParent, { childList: true, subtree: false });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}, 100);
|
|
53
97
|
};
|
|
54
98
|
export const SelectMultiple = ({ id, name, options, errors, control, validation, defaultValue, disabled, hideErrorMessage, menuPlacement, placeholder, ariaLabel, }) => {
|
|
55
99
|
React.useEffect(() => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as styles from "./Logo.module.css";
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
-
export const Logo = ({ onClick, layoutClassName, variant = "header" }) => {
|
|
4
|
+
export const Logo = ({ onClick, layoutClassName, variant = "header", ariaLabel = "logo" }) => {
|
|
5
5
|
return (_jsx("div", { className: clsx(styles.container, styles[variant], [
|
|
6
6
|
onClick && styles.clickable,
|
|
7
7
|
layoutClassName && layoutClassName,
|
|
8
|
-
]), onClick }));
|
|
8
|
+
]), role: "img", "aria-label": ariaLabel, onClick }));
|
|
9
9
|
};
|
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
/* --conduction-card-header-title-hover-color: #000000; */
|
|
12
12
|
/* --conduction-card-header-title-hover-text-decoration: none; */
|
|
13
13
|
/* --conduction-card-header-title-hover-text-underline-offset: 2px; */
|
|
14
|
+
/* --conduction-card-header-title-padding-block-end: 0px; */
|
|
14
15
|
|
|
15
16
|
--conduction-card-header-date-color: #000000;
|
|
16
17
|
--conduction-card-header-date-font-size: 16px;
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
color: var(--conduction-card-header-title-color) !important;
|
|
36
37
|
text-decoration: var(--conduction-card-header-title-text-decoration) !important;
|
|
37
38
|
text-underline-offset: var(--conduction-card-header-title-text-underline-offset) !important;
|
|
39
|
+
padding-block-end: var(--conduction-card-header-title-padding-block-end) !important;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
.title:hover > * {
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import * as styles from "./DisplaySwitch.module.css";
|
|
3
|
-
import _ from "lodash";
|
|
4
|
-
import clsx from "clsx";
|
|
5
|
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
|
-
import { IconPrefix, IconName } from "@fortawesome/fontawesome-svg-core";
|
|
7
|
-
import { Button, ButtonGroup } from "@utrecht/component-library-react/dist/css-module";
|
|
8
|
-
|
|
9
|
-
interface DisplaySwitchProps {
|
|
10
|
-
buttons: DisplaySwitchButtonProps[];
|
|
11
|
-
layoutClassName?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface DisplaySwitchButtonProps {
|
|
15
|
-
label: string;
|
|
16
|
-
pressed: boolean;
|
|
17
|
-
handleClick: () => any;
|
|
18
|
-
icon?: {
|
|
19
|
-
name: IconName;
|
|
20
|
-
prefix: IconPrefix;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export declare type IDisplaySwitchButton = DisplaySwitchButtonProps;
|
|
25
|
-
|
|
26
|
-
const DisplaySwitch: React.FC<DisplaySwitchProps> = ({ layoutClassName, buttons }) => {
|
|
27
|
-
return (
|
|
28
|
-
<ButtonGroup className={clsx(styles.displaySwitchButtons,
|
|
29
|
-
{buttons.map((button, idx: number) => {
|
|
30
|
-
// TODO: Once the Rotterdam design system supports the "pressed" state,
|
|
31
|
-
// remove the `appereance` switch, and use the same appearance for each button.
|
|
32
|
-
return (
|
|
33
|
-
<Button
|
|
34
|
-
key={idx}
|
|
35
|
-
pressed={button.pressed}
|
|
36
|
-
appearance={button.pressed ? "secondary-action-button" : "subtle-button"}
|
|
37
|
-
onClick={button.handleClick}
|
|
38
|
-
className={styles.button}
|
|
39
|
-
>
|
|
40
|
-
{button.icon && <FontAwesomeIcon icon={[button.icon.prefix, button.icon.name]} />}
|
|
41
|
-
<span>{button.label}</span>
|
|
42
|
-
</Button>
|
|
43
|
-
);
|
|
44
|
-
})}
|
|
45
|
-
</ButtonGroup>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export default DisplaySwitch;
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as styles from "./DisplaySwitch.module.css";
|
|
3
|
+
import _ from "lodash";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
|
+
import { IconPrefix, IconName } from "@fortawesome/fontawesome-svg-core";
|
|
7
|
+
import { Button, ButtonGroup } from "@utrecht/component-library-react/dist/css-module";
|
|
8
|
+
|
|
9
|
+
interface DisplaySwitchProps {
|
|
10
|
+
buttons: DisplaySwitchButtonProps[];
|
|
11
|
+
layoutClassName?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface DisplaySwitchButtonProps {
|
|
15
|
+
label: string;
|
|
16
|
+
pressed: boolean;
|
|
17
|
+
handleClick: () => any;
|
|
18
|
+
icon?: {
|
|
19
|
+
name: IconName;
|
|
20
|
+
prefix: IconPrefix;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export declare type IDisplaySwitchButton = DisplaySwitchButtonProps;
|
|
25
|
+
|
|
26
|
+
const DisplaySwitch: React.FC<DisplaySwitchProps> = ({ layoutClassName, buttons }) => {
|
|
27
|
+
return (
|
|
28
|
+
<ButtonGroup className={clsx(styles.displaySwitchButtons, layoutClassName && layoutClassName)}>
|
|
29
|
+
{buttons.map((button, idx: number) => {
|
|
30
|
+
// TODO: Once the Rotterdam design system supports the "pressed" state,
|
|
31
|
+
// remove the `appereance` switch, and use the same appearance for each button.
|
|
32
|
+
return (
|
|
33
|
+
<Button
|
|
34
|
+
key={idx}
|
|
35
|
+
pressed={button.pressed}
|
|
36
|
+
appearance={button.pressed ? "secondary-action-button" : "subtle-button"}
|
|
37
|
+
onClick={button.handleClick}
|
|
38
|
+
className={styles.button}
|
|
39
|
+
>
|
|
40
|
+
{button.icon && <FontAwesomeIcon icon={[button.icon.prefix, button.icon.name]} />}
|
|
41
|
+
<span>{button.label}</span>
|
|
42
|
+
</Button>
|
|
43
|
+
);
|
|
44
|
+
})}
|
|
45
|
+
</ButtonGroup>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default DisplaySwitch;
|
|
@@ -56,6 +56,13 @@ const selectStyles: StylesConfig = {
|
|
|
56
56
|
fontFamily: `var(--conduction-input-select-placeholder-font-family, var(--utrecht-form-input-placeholder-font-family, ${base.fontFamily}))`,
|
|
57
57
|
color: `var(--conduction-input-select-placeholder-color, var(--utrecht-form-input-placeholder-color, ${base.color}) )`,
|
|
58
58
|
}),
|
|
59
|
+
dropdownIndicator: (base) => ({
|
|
60
|
+
...base,
|
|
61
|
+
color: "#949494",
|
|
62
|
+
"&:hover": {
|
|
63
|
+
color: "#949494",
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
59
66
|
};
|
|
60
67
|
|
|
61
68
|
const setAttributes = (): void => {
|
|
@@ -68,9 +75,51 @@ const setAttributes = (): void => {
|
|
|
68
75
|
});
|
|
69
76
|
};
|
|
70
77
|
|
|
78
|
+
const updateIndicatorAttributes = (indicator: HTMLElement, isInteractive: boolean) => {
|
|
79
|
+
if (isInteractive) {
|
|
80
|
+
indicator.setAttribute("role", "button");
|
|
81
|
+
indicator.setAttribute("tabindex", "0");
|
|
82
|
+
indicator.setAttribute("aria-label", "Clear selection");
|
|
83
|
+
} else {
|
|
84
|
+
indicator.setAttribute("role", "presentation");
|
|
85
|
+
indicator.removeAttribute("tabindex");
|
|
86
|
+
indicator.removeAttribute("aria-label");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const setAriaLabelsForIndicators = () => {
|
|
91
|
+
document.querySelectorAll('[class*="control"]').forEach((control) => {
|
|
92
|
+
const indicatorsParent = control.querySelector('[class*="indicatorSeparator"]')?.parentElement;
|
|
93
|
+
if (!indicatorsParent) return;
|
|
94
|
+
|
|
95
|
+
const indicators = indicatorsParent.querySelectorAll('[class*="indicatorContainer"]');
|
|
96
|
+
const hasSelection = indicators.length === 2;
|
|
97
|
+
|
|
98
|
+
indicators.forEach((indicator, index) => {
|
|
99
|
+
const isClearButton = hasSelection && index === 0;
|
|
100
|
+
updateIndicatorAttributes(indicator as HTMLElement, isClearButton);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Initial static setup
|
|
71
106
|
setRoleToPresentation('[id*="live-region"]', "presentation");
|
|
72
107
|
setRoleToPresentation('[class*="indicatorSeparator"]', "separator");
|
|
73
108
|
setRoleToPresentation('[class*="a11yText"]', "presentation");
|
|
109
|
+
|
|
110
|
+
// Dynamic setup after render
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
setAriaLabelsForIndicators();
|
|
113
|
+
|
|
114
|
+
const observer = new MutationObserver(setAriaLabelsForIndicators);
|
|
115
|
+
|
|
116
|
+
document.querySelectorAll('[class*="control"]').forEach((control) => {
|
|
117
|
+
const indicatorsParent = control.querySelector('[class*="indicatorSeparator"]')?.parentElement;
|
|
118
|
+
if (indicatorsParent) {
|
|
119
|
+
observer.observe(indicatorsParent, { childList: true, subtree: false });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}, 100);
|
|
74
123
|
};
|
|
75
124
|
|
|
76
125
|
export const SelectMultiple = ({
|
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import * as styles from "./Logo.module.css";
|
|
3
|
-
import clsx from "clsx";
|
|
4
|
-
|
|
5
|
-
interface LogoProps {
|
|
6
|
-
variant?: "header" | "footer" | "navbar";
|
|
7
|
-
onClick?: () => any;
|
|
8
|
-
layoutClassName?: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as styles from "./Logo.module.css";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
|
|
5
|
+
interface LogoProps {
|
|
6
|
+
variant?: "header" | "footer" | "navbar";
|
|
7
|
+
onClick?: () => any;
|
|
8
|
+
layoutClassName?: string;
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Logo: React.FC<LogoProps> = ({ onClick, layoutClassName, variant = "header", ariaLabel = "logo" }) => {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={clsx(styles.container, styles[variant], [
|
|
16
|
+
onClick && styles.clickable,
|
|
17
|
+
layoutClassName && layoutClassName,
|
|
18
|
+
])}
|
|
19
|
+
role="img"
|
|
20
|
+
aria-label={ariaLabel}
|
|
21
|
+
{...{ onClick }}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import * as styles from "./PrimaryTopNav.module.css";
|
|
3
|
-
import clsx from "clsx";
|
|
4
|
-
import { Link } from "@utrecht/component-library-react/dist/css-module";
|
|
5
|
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
|
-
import { faBars, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
|
7
|
-
import { IconPrefix, IconName } from "@fortawesome/fontawesome-svg-core";
|
|
8
|
-
|
|
9
|
-
interface ITopNavItemBase {
|
|
10
|
-
label: string;
|
|
11
|
-
icon?: JSX.Element;
|
|
12
|
-
current?: boolean | ICurrentItemJSONFormat;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface ICurrentItemJSONFormat {
|
|
16
|
-
pathname: string;
|
|
17
|
-
operator: "equals" | "includes";
|
|
18
|
-
filterCondition?: {
|
|
19
|
-
filter: string;
|
|
20
|
-
value: string;
|
|
21
|
-
isObject: boolean;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface IIconJSONFormat {
|
|
26
|
-
icon?: {
|
|
27
|
-
icon: IconName;
|
|
28
|
-
prefix: IconPrefix;
|
|
29
|
-
placement: "left" | "right";
|
|
30
|
-
};
|
|
31
|
-
customIcon?: {
|
|
32
|
-
icon: string;
|
|
33
|
-
placement: "left" | "right";
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface IHandleClickJSONFormat {
|
|
38
|
-
link: string;
|
|
39
|
-
setFilter?: {
|
|
40
|
-
filter: string;
|
|
41
|
-
value: string;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ITopNavItemWithSubItems extends ITopNavItemBase {
|
|
46
|
-
handleClick?: never;
|
|
47
|
-
subItems: ITopNavItem[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface ITopNavItemWithoutSubItems extends ITopNavItemBase {
|
|
51
|
-
handleClick?: () => any | IHandleClickJSONFormat;
|
|
52
|
-
type: "readme" | "internal" | "external";
|
|
53
|
-
subItems?: never;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type ITopNavItem = ITopNavItemWithSubItems | ITopNavItemWithoutSubItems;
|
|
57
|
-
|
|
58
|
-
export interface TopNavProps {
|
|
59
|
-
items: ITopNavItem[];
|
|
60
|
-
mobileLogo?: JSX.Element;
|
|
61
|
-
layoutClassName?: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export const PrimaryTopNav = ({ items, mobileLogo, layoutClassName }: TopNavProps): JSX.Element => {
|
|
65
|
-
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
66
|
-
const [isMobile, setIsMobile] = React.useState<boolean>(window.innerWidth < 992);
|
|
67
|
-
|
|
68
|
-
React.useEffect(() => {
|
|
69
|
-
const handleResize = () => {
|
|
70
|
-
setIsMobile(window.innerWidth < 992);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
window.addEventListener("resize", handleResize);
|
|
74
|
-
|
|
75
|
-
return () => window.removeEventListener("resize", handleResize);
|
|
76
|
-
}, []);
|
|
77
|
-
|
|
78
|
-
const handleSubItemClick = (handleClick: any) => {
|
|
79
|
-
setIsOpen(false);
|
|
80
|
-
|
|
81
|
-
handleClick();
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<div className={clsx(styles.container, layoutClassName && layoutClassName)}>
|
|
86
|
-
<div className={styles.menuToggleContainer}>
|
|
87
|
-
{mobileLogo}
|
|
88
|
-
|
|
89
|
-
<button className={styles.menuToggle} onClick={() => setIsOpen((o) => !o)}>
|
|
90
|
-
<FontAwesomeIcon icon={faBars} />
|
|
91
|
-
</button>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
<nav className={clsx(styles.primary, isOpen && styles.isOpen)}>
|
|
95
|
-
<ul className={styles.ul}>
|
|
96
|
-
{items.map(({ label, icon, current, handleClick, subItems }, idx) => (
|
|
97
|
-
<li onClick={handleClick} className={clsx(styles.li, current && styles.current)} key={idx}>
|
|
98
|
-
<Link
|
|
99
|
-
className={clsx(
|
|
100
|
-
styles.link,
|
|
101
|
-
styles.label,
|
|
102
|
-
subItems && styles.mobileLink,
|
|
103
|
-
current && styles.currentLink,
|
|
104
|
-
)}
|
|
105
|
-
>
|
|
106
|
-
{icon && icon}
|
|
107
|
-
{label}{" "}
|
|
108
|
-
{subItems && isMobile && <FontAwesomeIcon className={styles.toggleIcon} icon={faChevronRight} />}
|
|
109
|
-
</Link>
|
|
110
|
-
|
|
111
|
-
{subItems && (
|
|
112
|
-
<ul className={clsx(styles.dropdown, [subItems.length > 8 && styles.dropdownOverflow])}>
|
|
113
|
-
{subItems.map(({ label, icon, current, handleClick }, idx) => (
|
|
114
|
-
<li
|
|
115
|
-
key={idx}
|
|
116
|
-
className={clsx(styles.li, current && styles.dropdownCurrent)}
|
|
117
|
-
onClick={() => handleSubItemClick(handleClick)}
|
|
118
|
-
>
|
|
119
|
-
<Link className={clsx(styles.link, styles.label, current && styles.dropdownCurrentLink)}>
|
|
120
|
-
{icon}
|
|
121
|
-
{label}
|
|
122
|
-
</Link>
|
|
123
|
-
</li>
|
|
124
|
-
))}
|
|
125
|
-
</ul>
|
|
126
|
-
)}
|
|
127
|
-
</li>
|
|
128
|
-
))}
|
|
129
|
-
</ul>
|
|
130
|
-
</nav>
|
|
131
|
-
</div>
|
|
132
|
-
);
|
|
133
|
-
};
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as styles from "./PrimaryTopNav.module.css";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { Link } from "@utrecht/component-library-react/dist/css-module";
|
|
5
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
|
+
import { faBars, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
|
7
|
+
import { IconPrefix, IconName } from "@fortawesome/fontawesome-svg-core";
|
|
8
|
+
|
|
9
|
+
interface ITopNavItemBase {
|
|
10
|
+
label: string;
|
|
11
|
+
icon?: JSX.Element;
|
|
12
|
+
current?: boolean | ICurrentItemJSONFormat;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ICurrentItemJSONFormat {
|
|
16
|
+
pathname: string;
|
|
17
|
+
operator: "equals" | "includes";
|
|
18
|
+
filterCondition?: {
|
|
19
|
+
filter: string;
|
|
20
|
+
value: string;
|
|
21
|
+
isObject: boolean;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface IIconJSONFormat {
|
|
26
|
+
icon?: {
|
|
27
|
+
icon: IconName;
|
|
28
|
+
prefix: IconPrefix;
|
|
29
|
+
placement: "left" | "right";
|
|
30
|
+
};
|
|
31
|
+
customIcon?: {
|
|
32
|
+
icon: string;
|
|
33
|
+
placement: "left" | "right";
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface IHandleClickJSONFormat {
|
|
38
|
+
link: string;
|
|
39
|
+
setFilter?: {
|
|
40
|
+
filter: string;
|
|
41
|
+
value: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ITopNavItemWithSubItems extends ITopNavItemBase {
|
|
46
|
+
handleClick?: never;
|
|
47
|
+
subItems: ITopNavItem[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ITopNavItemWithoutSubItems extends ITopNavItemBase {
|
|
51
|
+
handleClick?: () => any | IHandleClickJSONFormat;
|
|
52
|
+
type: "readme" | "internal" | "external";
|
|
53
|
+
subItems?: never;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type ITopNavItem = ITopNavItemWithSubItems | ITopNavItemWithoutSubItems;
|
|
57
|
+
|
|
58
|
+
export interface TopNavProps {
|
|
59
|
+
items: ITopNavItem[];
|
|
60
|
+
mobileLogo?: JSX.Element;
|
|
61
|
+
layoutClassName?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const PrimaryTopNav = ({ items, mobileLogo, layoutClassName }: TopNavProps): JSX.Element => {
|
|
65
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
66
|
+
const [isMobile, setIsMobile] = React.useState<boolean>(window.innerWidth < 992);
|
|
67
|
+
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
const handleResize = () => {
|
|
70
|
+
setIsMobile(window.innerWidth < 992);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
window.addEventListener("resize", handleResize);
|
|
74
|
+
|
|
75
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
const handleSubItemClick = (handleClick: any) => {
|
|
79
|
+
setIsOpen(false);
|
|
80
|
+
|
|
81
|
+
handleClick();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className={clsx(styles.container, layoutClassName && layoutClassName)}>
|
|
86
|
+
<div className={styles.menuToggleContainer}>
|
|
87
|
+
{mobileLogo}
|
|
88
|
+
|
|
89
|
+
<button className={styles.menuToggle} onClick={() => setIsOpen((o) => !o)}>
|
|
90
|
+
<FontAwesomeIcon icon={faBars} />
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<nav className={clsx(styles.primary, isOpen && styles.isOpen)}>
|
|
95
|
+
<ul className={styles.ul}>
|
|
96
|
+
{items.map(({ label, icon, current, handleClick, subItems }, idx) => (
|
|
97
|
+
<li onClick={handleClick} className={clsx(styles.li, current && styles.current)} key={idx}>
|
|
98
|
+
<Link
|
|
99
|
+
className={clsx(
|
|
100
|
+
styles.link,
|
|
101
|
+
styles.label,
|
|
102
|
+
subItems && styles.mobileLink,
|
|
103
|
+
current && styles.currentLink,
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
{icon && icon}
|
|
107
|
+
{label}{" "}
|
|
108
|
+
{subItems && isMobile && <FontAwesomeIcon className={styles.toggleIcon} icon={faChevronRight} />}
|
|
109
|
+
</Link>
|
|
110
|
+
|
|
111
|
+
{subItems && (
|
|
112
|
+
<ul className={clsx(styles.dropdown, [subItems.length > 8 && styles.dropdownOverflow])}>
|
|
113
|
+
{subItems.map(({ label, icon, current, handleClick }, idx) => (
|
|
114
|
+
<li
|
|
115
|
+
key={idx}
|
|
116
|
+
className={clsx(styles.li, current && styles.dropdownCurrent)}
|
|
117
|
+
onClick={() => handleSubItemClick(handleClick)}
|
|
118
|
+
>
|
|
119
|
+
<Link className={clsx(styles.link, styles.label, current && styles.dropdownCurrentLink)}>
|
|
120
|
+
{icon}
|
|
121
|
+
{label}
|
|
122
|
+
</Link>
|
|
123
|
+
</li>
|
|
124
|
+
))}
|
|
125
|
+
</ul>
|
|
126
|
+
)}
|
|
127
|
+
</li>
|
|
128
|
+
))}
|
|
129
|
+
</ul>
|
|
130
|
+
</nav>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
};
|