UNPKG

@carbon/react

Version:

React components for the Carbon Design System

349 lines (345 loc) 10.9 kB
/** * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import React, { useState, useRef } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { useId } from '../../internal/useId.js'; import { deprecate } from '../../prop-types/deprecate.js'; import { usePrefix } from '../../internal/usePrefix.js'; import '../Text/index.js'; import { RadioButtonChecked, RadioButton } from '@carbon/icons-react'; import { useOutsideClick } from '../../internal/useOutsideClick.js'; import { Text } from '../Text/Text.js'; var _StructuredListCell; const GridSelectedRowStateContext = /*#__PURE__*/React.createContext(null); const GridSelectedRowDispatchContext = /*#__PURE__*/React.createContext(null); function StructuredListWrapper(props) { const { children, selection, className, ['aria-label']: ariaLabel = 'Structured list section', // @ts-expect-error: Deprecated prop ariaLabel: deprecatedAriaLabel, isCondensed, isFlush, selectedInitialRow, ...other } = props; const prefix = usePrefix(); const classes = cx(`${prefix}--structured-list`, { [`${prefix}--structured-list--selection`]: selection, [`${prefix}--structured-list--condensed`]: isCondensed, [`${prefix}--structured-list--flush`]: isFlush && !selection }, className); const [selectedRow, setSelectedRow] = React.useState(selectedInitialRow ?? null); return /*#__PURE__*/React.createElement(GridSelectedRowStateContext.Provider, { value: selectedRow }, /*#__PURE__*/React.createElement(GridSelectedRowDispatchContext.Provider, { value: setSelectedRow }, /*#__PURE__*/React.createElement("div", _extends({ role: "table", className: classes }, other, { "aria-label": deprecatedAriaLabel || ariaLabel }), children))); } StructuredListWrapper.propTypes = { /** * Specify a label to be read by screen readers on the container node */ ['aria-label']: PropTypes.string, /** * Deprecated, please use `aria-label` instead. * Specify a label to be read by screen readers on the container note. */ ariaLabel: deprecate(PropTypes.string, 'This prop syntax has been deprecated. Please use the new `aria-label`.'), /** * Provide the contents of your StructuredListWrapper */ children: PropTypes.node, /** * Specify an optional className to be applied to the container node */ className: PropTypes.string, /** * Specify if structured list is condensed, default is false */ isCondensed: PropTypes.bool, /** * Specify if structured list is flush, not valid for selection variant, default is false */ isFlush: PropTypes.bool, /** * Specify whether your StructuredListWrapper should have selections */ selection: PropTypes.bool, /** * Specify which row will be selected initially */ selectedInitialRow: PropTypes.string }; function StructuredListHead(props) { const { children, className, ...other } = props; const prefix = usePrefix(); const classes = cx(`${prefix}--structured-list-thead`, className); return /*#__PURE__*/React.createElement("div", _extends({ role: "rowgroup", className: classes }, other), children); } StructuredListHead.propTypes = { /** * Provide the contents of your StructuredListHead */ children: PropTypes.node, /** * Specify an optional className to be applied to the node */ className: PropTypes.string }; function StructuredListBody(props) { const { children, className, ...other } = props; const prefix = usePrefix(); const classes = cx(`${prefix}--structured-list-tbody`, className); return /*#__PURE__*/React.createElement("div", _extends({ className: classes, role: "rowgroup" }, other), children); } StructuredListBody.propTypes = { /** * Provide the contents of your StructuredListBody */ children: PropTypes.node, /** * Specify an optional className to be applied to the container node */ className: PropTypes.string, head: PropTypes.bool, /** * Provide a handler that is invoked on the key down event for the control */ onKeyDown: PropTypes.func }; const GridRowContext = /*#__PURE__*/React.createContext(null); function StructuredListRow(props) { const { onKeyDown, children, className, head, onClick, selection, id, ...other } = props; const [hasFocusWithin, setHasFocusWithin] = useState(false); const rowId = id ?? useId('grid-input'); const selectedRow = React.useContext(GridSelectedRowStateContext); const setSelectedRow = React.useContext(GridSelectedRowDispatchContext); const prefix = usePrefix(); const value = { id: rowId }; const classes = cx(`${prefix}--structured-list-row`, { [`${prefix}--structured-list-row--header-row`]: head, [`${prefix}--structured-list-row--focused-within`]: hasFocusWithin && !selection || hasFocusWithin && selection && (selectedRow === rowId || selectedRow === null), // Ensure focus on the first item when navigating through Tab keys and no row is selected (selectedRow === null) [`${prefix}--structured-list-row--selected`]: selectedRow === rowId }, className); const itemRef = useRef(null); const handleClick = () => { setHasFocusWithin(false); }; useOutsideClick(itemRef, handleClick); return head ? /*#__PURE__*/React.createElement("div", _extends({ role: "row" }, other, { className: classes }), selection && (_StructuredListCell || (_StructuredListCell = /*#__PURE__*/React.createElement(StructuredListCell, { head: true }))), children) : /*#__PURE__*/ // eslint-disable-next-line jsx-a11y/interactive-supports-focus React.createElement("div", _extends({}, other, { role: "row", className: classes, ref: itemRef, onClick: event => { setSelectedRow?.(rowId); onClick && onClick(event); if (selection) { // focus items only when selection is enabled setHasFocusWithin(true); } }, onFocus: event => { if (selection || event.currentTarget === event.target) { setHasFocusWithin(true); } }, onBlur: () => { setHasFocusWithin(false); }, onKeyDown: onKeyDown }), /*#__PURE__*/React.createElement(GridRowContext.Provider, { value: value }, selection && /*#__PURE__*/React.createElement(StructuredListCell, null, selectedRow === rowId ? /*#__PURE__*/React.createElement(RadioButtonChecked, { className: `${prefix}--structured-list__icon` }) : /*#__PURE__*/React.createElement(RadioButton, { className: `${prefix}--structured-list__icon` })), children)); } StructuredListRow.propTypes = { /** * Provide the contents of your StructuredListRow */ children: PropTypes.node, /** * Specify an optional className to be applied to the container node */ className: PropTypes.string, /** * Specify whether your StructuredListRow should be used as a header row */ head: PropTypes.bool, /** * Specify whether a `<label>` should be used */ label: deprecate(PropTypes.bool, `\nThe \`label\` prop is no longer needed and will be removed in the next major version of Carbon.`), /** * Provide a handler that is invoked on the click */ onClick: PropTypes.func, /** * Provide a handler that is invoked on the key down event for the control, */ onKeyDown: PropTypes.func, /** * Mark if this row should be selectable */ selection: PropTypes.bool, /** * Specify row id so that it can be used for initial selection */ id: PropTypes.string }; function StructuredListInput(props) { const defaultId = useId('structureListInput'); const { className, name = `structured-list-input-${defaultId}`, title, id, onChange, ...other } = props; const prefix = usePrefix(); const classes = cx(`${prefix}--structured-list-input`, `${prefix}--visually-hidden`, className); const row = React.useContext(GridRowContext); const selectedRow = React.useContext(GridSelectedRowStateContext); const setSelectedRow = React.useContext(GridSelectedRowDispatchContext); return /*#__PURE__*/React.createElement("input", _extends({}, other, { type: "radio", tabIndex: 0, checked: !!row && row.id === selectedRow, value: row?.id ?? '', onChange: event => { setSelectedRow?.(event.target.value); onChange && onChange(event); }, id: id ?? defaultId, className: classes, name: name, title: title })); } StructuredListInput.propTypes = { /** * Specify an optional className to be applied to the input */ className: PropTypes.string, /** * Specify whether the underlying input should be checked by default */ defaultChecked: deprecate(PropTypes.bool, `\nThe prop \`defaultChecked\` is no longer needed and will be removed in the next major version of Carbon.`), /** * Specify a custom `id` for the input */ id: PropTypes.string, /** * Provide a `name` for the input */ name: PropTypes.string, /** * Provide an optional hook that is called each time the input is updated */ onChange: PropTypes.func, /** * Provide a `title` for the input */ title: PropTypes.string, /** * Specify the value of the input */ value: deprecate(PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, `\nThe prop \`value\` will be removed in the next major version of Carbon.`) }; function StructuredListCell(props) { const { children, className, head, noWrap, ...other } = props; const prefix = usePrefix(); const classes = cx({ [`${prefix}--structured-list-th`]: head, [`${prefix}--structured-list-td`]: !head, [`${prefix}--structured-list-content--nowrap`]: noWrap }, className); if (head) { return /*#__PURE__*/React.createElement(Text, _extends({ className: classes, role: "columnheader" }, other), children); } return /*#__PURE__*/React.createElement(Text, _extends({ as: "div", className: classes, role: "cell" }, other), children); } StructuredListCell.propTypes = { /** * Provide the contents of your StructuredListCell */ children: PropTypes.node, /** * Specify an optional className to be applied to the container node */ className: PropTypes.string, /** * Specify whether your StructuredListCell should be used as a header cell */ head: PropTypes.bool, /** * Specify whether your StructuredListCell should have text wrapping */ noWrap: PropTypes.bool }; export { StructuredListBody, StructuredListCell, StructuredListHead, StructuredListInput, StructuredListRow, StructuredListWrapper };