Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ const GaEventPropTypes = {
text: PropTypes.string,
};

const RfiLabel = ({ label, name, id = undefined, requiredIcon = undefined }) => (
const RfiLabel = ({
label,
name,
id = undefined,
requiredIcon = undefined,
}) => (
<label htmlFor={id || name}>
{requiredIcon && (
<>
Expand Down
1 change: 0 additions & 1 deletion packages/app-webdir-ui/src/QuickLinks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,3 @@ const QuickLinks = () => {
};

export { QuickLinks };

7 changes: 6 additions & 1 deletion packages/component-footer/src/components/Contact/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import { ColumnSection } from "../ColumnSection";
*/

const Contact = ({
contact: { title = "", contactLink = "", contributionLink = "", columns = []},
contact: {
title = "",
contactLink = "",
contributionLink = "",
columns = [],
},
}) => {
return (
<div className="wrapper" id="wrapper-footer-columns" data-testid="contact">
Expand Down
8 changes: 3 additions & 5 deletions packages/component-footer/src/components/Social/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@ const DEF_MEDIA_LINKS = {
* @returns {JSX.Element}
*/

const Social = ({ social: {
logoUrl,
unitLogo = endorsedLogo,
mediaLinks = DEF_MEDIA_LINKS,
}, }) => {
const Social = ({
social: { logoUrl, unitLogo = endorsedLogo, mediaLinks = DEF_MEDIA_LINKS },
}) => {
return (
<div className="wrapper" id="wrapper-endorsed-footer" data-testid="social">
<div className="container" id="endorsed-footer">
Expand Down
24 changes: 23 additions & 1 deletion packages/component-header-footer/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,33 @@ const config = {
],
stories: ["../src/**/*.stories.js"],
core: {
builder: '@storybook/builder-vite'
builder: "@storybook/builder-vite",
},
framework: {
name: "@storybook/react-vite",
},
async viteFinal(config) {
// Configure optimizeDeps to handle JSX in .js files
config.optimizeDeps = {
...config.optimizeDeps,
esbuildOptions: {
...config.optimizeDeps?.esbuildOptions,
loader: {
".js": "jsx",
".jsx": "jsx",
},
},
};

// Configure esbuild for build
config.esbuild = {
...config.esbuild,
loader: "jsx",
include: /\.(jsx?|tsx?)$/,
};

return config;
},
};

export default config;
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ButtonWrapper } from "./index.styles";
* @param {string} props.text - The text content to display inside the button
* @param {string} [props.classes] - Additional CSS classes to apply to the button
* @param {function} [props.onClick] - Event handler function called when the button is clicked
* @param {function} [props.onKeyDown] - Event handler function called when a key is pressed while the button is focused
* @param {function} [props.onFocus] - Event handler function called when the button receives focus
* @param {string|React.Component} [props.as] - The element type or component to render as
* @returns {JSX.Element} The rendered button component
Expand All @@ -27,6 +28,7 @@ const Button = ({
text,
classes,
onClick,
onKeyDown,
onFocus,
as,
...props
Expand All @@ -35,8 +37,9 @@ const Button = ({
<ButtonWrapper
href={href}
className={`button-${color} ${classes ?? ""}`}
onClick={onClick ? event => onClick(event) : undefined}
onFocus={onFocus ? event => onFocus(event) : undefined}
onClick={onClick}
onKeyDown={onKeyDown}
onFocus={onFocus}
as={as}
{...props}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,24 @@ const LINK_DEFAULT_PROPS = {
text: "",
};

const HeadingItem = ({ text }) => <h3 className={CLASS_NAMES.UL_HEADING}>{text}</h3>;
const HeadingItem = ({ text }) => (
<h3 className={CLASS_NAMES.UL_HEADING}>{text}</h3>
);

HeadingItem.propTypes = {
text: PropTypes.string,
};

const ButtonItem = ({ link, dropdownName, handleLinkEvent }) => (
<li className={CLASS_NAMES.NAV_BUTTON}>
<div className={CLASS_NAMES.NAV_BUTTON}>
<Button
text={link.text}
color={link.color || "dark"}
color={link.color || "maroon"}
href={link.href}
onClick={e => handleLinkEvent(e, link)}
onKeyDown={e => handleLinkEvent(e, link)}
/>
</li>
</div>
);

ButtonItem.propTypes = {
Expand Down Expand Up @@ -72,11 +75,11 @@ LinkItem.propTypes = {
* @typedef { import("../../../../core/models/types").Button } Button
* @typedef {{
* dropdownName: string
* items: [object][]
* items: Array<Array<object>>
* buttons: Button[]
* classes?: string,
* listId: string
* setItemOpened: Function
* opened: boolean
* parentLink: React.RefObject<HTMLElement> | null
* }} DropdownItemProps
*/
Expand All @@ -91,11 +94,22 @@ const DropdownItem = ({
buttons,
classes,
listId,
setItemOpened,
opened,
parentLink,
}) => {
const { breakpoint } = useAppContext();
const isMega = items?.length > 2;
const {
breakpoint,
headerHeight,
setItemOpened,
setMobileMenuOpen,
mobileMenuOpen,
} = useAppContext();
let cols = 0;
items.map(lists => {
cols += lists[0].span || 1;
});

const isMega = cols > 2;
/**
* @type {React.MutableRefObject<HTMLDivElement|null>}
*/
Expand All @@ -110,6 +124,16 @@ const DropdownItem = ({
setAlignedRight(elPosition > breakpointPosition);
}
}, []);
useEffect(() => {
if (opened && dropdownRef?.current?.parentElement) {
dropdownRef.current.parentElement.scrollIntoView(
/** @type {ScrollIntoViewOptions} */ {
behavior: "smooth",
block: "start",
}
);
}
}, [dropdownRef, opened]);

const stopPropagation = e => e.stopPropagation();

Expand All @@ -119,14 +143,13 @@ const DropdownItem = ({

const focusNextLink = () => {
const nextLink = parentElement.nextElementSibling?.firstChild;
if (nextLink) nextLink.focus();
if (typeof nextLink?.focus === "function") nextLink.focus();
};

const focusPrevLink = () => {
const prevLink = parentElement.previousElementSibling?.firstChild;
if (prevLink) prevLink.focus();
if (typeof prevLink?.focus === "function") prevLink.focus();
};

stopPropagation(e);

if (key === "ArrowDown") {
Expand All @@ -135,17 +158,25 @@ const DropdownItem = ({
} else if (key === "ArrowUp") {
e.preventDefault();
focusPrevLink();
} else if (key === "Escape") {
} else if (key === "Escape" && opened) {
setItemOpened();
if (parentLink?.current) parentLink.current.focus();
if (typeof parentLink?.current?.focus === "function") {
parentLink.current.focus();
}
} else if (key === "Escape" && !opened && mobileMenuOpen) {
setMobileMenuOpen(false);
} else if (key === "Enter" || key === " " || type === "click") {
// Single page apps do not leave the page on link click,
// so we need to manually close the menu and trigger the onClick event
setMobileMenuOpen(false);
setItemOpened();
link?.onClick?.(e);
trackGAEvent({ ...LINK_DEFAULT_PROPS, text: link.text });
}
};

const renderItem = (link, index) => {
const key = `${link.text}-${link.href || index}`;
const key = `${link.text}-${link.href}-${index}`;
if (link.type === "heading")
return <HeadingItem key={key} text={link.text} />;
if (link.type === "button")
Expand Down Expand Up @@ -174,17 +205,68 @@ const DropdownItem = ({
isMega ? " mega" : ""
}`}
breakpoint={breakpoint}
headerHeight={headerHeight}
>
<div id={MULTIPLE_SUBMENUS ? listId : ""} className={CLASS_NAMES.DROPDOWN_CONTAINER}>
{items?.map((item, index0) => {
const genKey = idGenerator(`dropdown-item-${index0}-`);
const key = genKey.next().value;
return (
<ul id={MULTIPLE_SUBMENUS ? `${listId}-${key}` : listId} key={key}>
{item.map((link, index) => renderItem(link, index))}
</ul>
);
})}
<div
style={{ "--cols": cols < 3 ? 4 : cols }}
id={MULTIPLE_SUBMENUS ? listId : ""}
className={CLASS_NAMES.DROPDOWN_CONTAINER}
>
<>
{items?.map((item, index0) => {
const genKey = idGenerator(`dropdown-item-${index0}-`);
const key = genKey.next().value;
return (
<div
className={CLASS_NAMES.DROPDOWN_CONTAINER_COLUMN}
style={{ "--span": item[0].span || 1 }}
key={`${listId}-${key}`}
id={MULTIPLE_SUBMENUS ? `${listId}-${key}` : listId}
>
{(() => {
let currentUl = [];
const uls = [];
item.forEach((link, index) => {
if (link.type === "heading") {
if (currentUl.length > 0) {
uls.push(currentUl);
currentUl = [];
}
uls.push([link]);
} else if (link.type === "button") {
if (currentUl.length > 0) {
uls.push(currentUl);
currentUl = [];
}
uls.push([link]);
} else {
currentUl.push(link);
}
});

if (currentUl.length > 0) {
uls.push(currentUl);
}

return uls.map((group, groupIndex) => {
const groupKey = `${key}-group-${groupIndex}`;
if (group.length === 1 && group[0].type === "heading") {
return renderItem(group[0], groupIndex);
}
if (group.length === 1 && group[0].type === "button") {
return renderItem(group[0], groupIndex);
}
return (
<ul key={groupKey}>
{group.map((link, index) => renderItem(link, index))}
</ul>
);
});
})()}
</div>
);
})}
</>
</div>
{buttons && (
<div className={CLASS_NAMES.DROPDOWN_BUTTON_CONTAINER}>
Expand All @@ -208,19 +290,21 @@ const DropdownItem = ({
DropdownItem.propTypes = {
dropdownName: PropTypes.string,
items: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string,
selected: PropTypes.bool,
onClick: PropTypes.func,
href: PropTypes.string,
})
PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string,
selected: PropTypes.bool,
onClick: PropTypes.func,
href: PropTypes.string,
})
)
),
buttons: PropTypes.arrayOf(PropTypes.shape(ButtonPropTypes)),
classes: PropTypes.string,
listId: PropTypes.string,
opened: PropTypes.bool,
setItemOpened: PropTypes.func,
parentLink: PropTypes.shape({
focus: PropTypes.func,
current: PropTypes.instanceOf(HTMLElement),
}),
};
Expand Down
Loading