@primer/react
Version:
An implementation of GitHub's Primer Design System using React
186 lines (182 loc) • 7.36 kB
JavaScript
import React from 'react';
import { useId } from '../hooks/useId.js';
import { ListContext } from './shared.js';
import { useSlots } from '../hooks/useSlots.js';
import { invariant } from '../utils/invariant.js';
import { clsx } from 'clsx';
import classes from './ActionList.module.css.js';
import groupClasses from './Group.module.css.js';
import { BoxWithFallback } from '../internal/components/BoxWithFallback.js';
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
const Heading = ({
as: Component = 'h3',
className,
children,
id,
...rest
}) => {
return (
/*#__PURE__*/
// Box is temporary to support lingering sx usage
jsx(BoxWithFallback, {
as: Component,
className: className,
id: id,
...rest,
children: children
})
);
};
Heading.displayName = "Heading";
const HeadingWrap = ({
as = 'div',
children,
className,
...rest
}) => {
return /*#__PURE__*/React.createElement(as, {
...rest,
className
}, children);
};
const GroupContext = /*#__PURE__*/React.createContext({
groupHeadingId: undefined,
selectionVariant: undefined
});
const Group = ({
title,
variant = 'subtle',
auxiliaryText,
selectionVariant,
role,
className,
'aria-label': ariaLabel,
...props
}) => {
var _slots$groupHeading;
const id = useId();
const {
role: listRole
} = React.useContext(ListContext);
const [slots, childrenWithoutSlots] = useSlots(props.children, {
groupHeading: GroupHeading
});
let groupHeadingId = undefined;
// ActionList.GroupHeading
if (slots.groupHeading) {
var _slots$groupHeading$p;
// If there is an id prop passed in the ActionList.GroupHeading, use it otherwise use the generated id.
groupHeadingId = (_slots$groupHeading$p = slots.groupHeading.props.id) !== null && _slots$groupHeading$p !== void 0 ? _slots$groupHeading$p : id;
}
// Supports the deprecated `title` prop
if (title) {
groupHeadingId = id;
}
return /*#__PURE__*/jsx(BoxWithFallback, {
as: "li",
className: clsx(className, groupClasses.Group),
role: listRole ? 'none' : undefined,
...props,
children: /*#__PURE__*/jsxs(GroupContext.Provider, {
value: {
selectionVariant,
groupHeadingId
},
children: [title && !slots.groupHeading ?
/*#__PURE__*/
// Escape hatch: supports old API <ActionList.Group title="group title"> in a non breaking way
jsx(GroupHeading, {
variant: variant,
auxiliaryText: auxiliaryText,
_internalBackwardCompatibleTitle: title
}) : null, !title && slots.groupHeading ? /*#__PURE__*/React.cloneElement(slots.groupHeading) : null, /*#__PURE__*/jsx("ul", {
// if listRole is set (listbox or menu), we don't label the list with the groupHeadingId
// because the heading is hidden from the accessibility tree and only used for presentation role.
// We will instead use aria-label to label the list. See a line below.
"aria-labelledby": listRole ? undefined : groupHeadingId,
"aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : listRole ? title !== null && title !== void 0 ? title : (_slots$groupHeading = slots.groupHeading) === null || _slots$groupHeading === void 0 ? void 0 : _slots$groupHeading.props.children : undefined,
role: role || listRole && 'group',
className: groupClasses.GroupList,
children: slots.groupHeading ? childrenWithoutSlots : props.children
})]
})
});
};
Group.displayName = "Group";
/**
* Heading of a `Group`.
*
* As default, the role of ActionList is "list" and therefore group heading is rendered as a proper heading tag.
* If the role is "listbox" or "menu" (ActionMenu), the group heading is rendered as a div with presentation role and it is
* hidden from the accessibility tree due to the limitation of listbox children. https://w3c.github.io/aria/#listbox
* groups under menu or listbox are labelled by `aria-label`
*/
const GroupHeading = ({
as,
variant = 'subtle',
// We are not recommending this prop to be used, it should only be used internally for incremental rollout.
_internalBackwardCompatibleTitle,
auxiliaryText,
children,
className,
sx,
headingWrapElement = 'div',
...props
}) => {
const {
role: listRole
} = React.useContext(ListContext);
const {
groupHeadingId
} = React.useContext(GroupContext);
// for list role, the headings are proper heading tags, for menu and listbox, they are just representational and divs
const missingAsForList = (listRole === undefined || listRole === 'list') && children !== undefined && as === undefined;
const unnecessaryAsForListboxOrMenu = listRole !== undefined && listRole !== 'list' && children !== undefined && as !== undefined;
!
// 'as' prop is required for list roles. <GroupHeading as="h2">...</GroupHeading>
!missingAsForList ? process.env.NODE_ENV !== "production" ? invariant(false, `You are setting a heading for a list, that requires a heading level. Please use 'as' prop to set a proper heading level.`) : invariant(false) : void 0;
!
// 'as' prop on listbox or menu roles are not needed since they are rendered as divs and they could be misleading.
!unnecessaryAsForListboxOrMenu ? process.env.NODE_ENV !== "production" ? invariant(false, `Looks like you are trying to set a heading level to a ${listRole} role. Group headings for ${listRole} type action lists are for representational purposes, and rendered as divs. Therefore they don't need a heading level.`) : invariant(false) : void 0;
return /*#__PURE__*/jsx(Fragment, {
children: listRole && listRole !== 'list' ? /*#__PURE__*/jsxs(HeadingWrap, {
role: "presentation",
className: groupClasses.GroupHeadingWrap,
"aria-hidden": "true",
"data-variant": variant,
"data-component": "GroupHeadingWrap",
as: headingWrapElement,
...props,
children: [/*#__PURE__*/jsx("span", {
className: clsx(className, groupClasses.GroupHeading),
id: groupHeadingId,
children: _internalBackwardCompatibleTitle !== null && _internalBackwardCompatibleTitle !== void 0 ? _internalBackwardCompatibleTitle : children
}), auxiliaryText && /*#__PURE__*/jsx("div", {
className: classes.Description,
children: auxiliaryText
})]
}) :
/*#__PURE__*/
// for explicit (role="list" is passed as prop) and implicit list roles (ActionList ins rendered as list by default), group titles are proper heading tags.
jsxs(HeadingWrap, {
className: groupClasses.GroupHeadingWrap,
"data-variant": variant,
as: headingWrapElement,
"data-component": "GroupHeadingWrap",
children: [/*#__PURE__*/jsx(Heading, {
className: clsx(className, groupClasses.GroupHeading),
as: as || 'h3',
id: groupHeadingId,
sx: sx,
...props,
children: _internalBackwardCompatibleTitle !== null && _internalBackwardCompatibleTitle !== void 0 ? _internalBackwardCompatibleTitle : children
}), auxiliaryText && /*#__PURE__*/jsx("div", {
className: classes.Description,
children: auxiliaryText
})]
})
});
};
GroupHeading.displayName = 'ActionList.GroupHeading';
Group.displayName = 'ActionList.Group';
export { Group, GroupContext, GroupHeading };