@carbon/react
Version:
React components for the Carbon Design System
349 lines (345 loc) • 10.9 kB
JavaScript
/**
* 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 };