UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

186 lines (182 loc) • 7.36 kB
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 };