UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

274 lines (261 loc) 10.1 kB
/** * Copyright IBM Corp. 2020, 2025 * * 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 { Section, Heading, IconButton, Button } from '@carbon/react'; import { Themes, Kinds } from './Checklist.types.js'; import React__default, { useState, useRef, useEffect } from 'react'; import { ChecklistChart } from './ChecklistChart.js'; import { ChecklistIcon } from './ChecklistIcon.js'; import { ChevronUp } from '@carbon/react/icons'; import PropTypes from '../../_virtual/index.js'; import cx from 'classnames'; import { getDevtoolsProps } from '../../global/js/utils/devtools.js'; import { pkg } from '../../settings.js'; import uuidv4 from '../../global/js/utils/uuidv4.js'; // The block part of our conventional BEM class names (blockClass__E--M). const blockClass = `${pkg.prefix}--checklist`; const componentName = 'Checklist'; // NOTE: the component SCSS is not imported here: it is rolled up separately. // Default values can be included here and then assigned to the prop params, // e.g. prop = defaults.prop, // This gathers default values together neatly and ensures non-primitive // values are initialized early to avoid react making unnecessary re-renders. // Note that default values are not required for props that are 'required', // nor for props where the component can apply undefined values reasonably. // Default values should be provided when the component needs to make a choice // or assumption when a prop is not supplied. // Default values for props const defaults = { checklistAriaLabel: 'Checklist', checklistToggleAriaLabel: 'Checklist toggle', onClickViewAll: () => {}, onToggle: () => {}, open: true, enableToggle: true, taskLists: [], theme: Themes.light, toggleLabel: 'Toggle' }; /** * The Checklist tracks a user's progress much like Your Learning or * WalkMe. Each item in the list can be clickable, and each item has * an icon that defines the item's state as "not started", "in progress", * and "complete". */ let Checklist = /*#__PURE__*/React__default.forwardRef((_ref, ref) => { let { chartValue, chartLabel, checklistAriaLabel = defaults.checklistAriaLabel, checklistToggleAriaLabel = defaults.checklistToggleAriaLabel, className, onClickViewAll = defaults.onClickViewAll, onToggle = defaults.onToggle, open = defaults.open, enableToggle = defaults.enableToggle, taskLists = defaults.taskLists, theme = defaults.theme, title, toggleLabel = defaults.toggleLabel, toggleLabelAlign = 'top', viewAllLabel, ...rest } = _ref; const [isOpen, setIsOpen] = useState(open); const listContainerId = useRef(uuidv4()).current; const chartLabelId = useRef(uuidv4()).current; // Don't use this test: {chartValue && chartLabel && (render...)}. // If `chartValue` is 0 (zero) - a valid value - then it will render 0 and skip the remaining statement. // Use this test instead: {chartLabelAndValue && (render...)}. // The ChecklistChart component will validate `chartValue`. const chartLabelAndValue = typeof chartValue === 'number' && chartLabel; const handleClickToggle = () => { setIsOpen(prevOpen => !prevOpen); }; const handleClickViewAll = () => { onClickViewAll(); }; // If state changes, then trigger callback. useEffect(() => { onToggle(isOpen); }, [isOpen, onToggle]); // If the "open" prop is changed after initialization, // then update internal state. useEffect(() => { setIsOpen(open); }, [open]); return /*#__PURE__*/React__default.createElement(Section, _extends({}, rest, { "aria-label": checklistAriaLabel, as: "aside", className: cx(blockClass, // Apply the block class to the main HTML element className, // Apply any supplied class names to the main HTML element. // example: `${blockClass}__template-string-class-${kind}-n-${size}`, { [`${blockClass}__closed`]: !isOpen }), ref: ref }, getDevtoolsProps(componentName)), (title || chartLabelAndValue) && /*#__PURE__*/React__default.createElement("header", { className: `${blockClass}__header` }, chartLabelAndValue && /*#__PURE__*/React__default.createElement(ChecklistChart, { "aria-labelledby": chartLabelId, theme: theme, value: chartValue }), /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__titles` }, title && /*#__PURE__*/React__default.createElement(Heading, { className: `${blockClass}__title` }, title), chartLabelAndValue && /*#__PURE__*/React__default.createElement("p", { id: chartLabelId, className: `${blockClass}__chart-label` }, chartLabel)), enableToggle && /*#__PURE__*/React__default.createElement(IconButton, { align: toggleLabelAlign, "aria-controls": listContainerId, "aria-expanded": isOpen, "aria-label": checklistToggleAriaLabel, className: `${blockClass}__toggle`, kind: "ghost", label: toggleLabel, onClick: handleClickToggle, size: "sm" }, /*#__PURE__*/React__default.createElement(ChevronUp, { size: 16, className: cx(`${blockClass}__chevron`) }))), /*#__PURE__*/React__default.createElement("div", { id: listContainerId, className: `${blockClass}__content-outer` }, /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__content-inner` }, /*#__PURE__*/React__default.createElement("div", { className: cx(`${blockClass}__body`) }, taskLists.map((list, index) => { return /*#__PURE__*/React__default.createElement(Section, { className: `${blockClass}__list-group`, key: `${list.title}-${index}` }, list.title && /*#__PURE__*/React__default.createElement(Heading, { title: list.title, className: `${blockClass}__list-title` }, list.title), /*#__PURE__*/React__default.createElement("ol", { className: `${blockClass}__list` }, list.tasks.map((item, index) => { return /*#__PURE__*/React__default.createElement("li", { className: `${blockClass}__list-item`, key: `${item.label}-${index}` }, /*#__PURE__*/React__default.createElement(ChecklistIcon, { kind: item.kind, theme: theme }), typeof item.onClick === 'function' ? /*#__PURE__*/React__default.createElement(Button, { className: cx(`${blockClass}__button`, { [`${blockClass}__button--error`]: item.kind === 'error' }), onClick: () => { item.onClick?.(item); }, size: "sm", title: item.label }, /*#__PURE__*/React__default.createElement("div", null, item.label)) : /*#__PURE__*/React__default.createElement("div", { className: cx(`${blockClass}__label`, `${blockClass}__label--${item.kind}`), title: item.label }, item.label)); }))); })), viewAllLabel && /*#__PURE__*/React__default.createElement("footer", { className: `${blockClass}__footer` }, /*#__PURE__*/React__default.createElement(Button, { className: cx(`${blockClass}__button`, `${blockClass}__view-all`), onClick: handleClickViewAll, size: "sm" }, /*#__PURE__*/React__default.createElement("div", null, viewAllLabel)))))); }); // Return a placeholder if not released and not enabled by feature flag Checklist = pkg.checkComponentEnabled(Checklist, componentName); Checklist.displayName = componentName; // The types and DocGen commentary for the component props, // in alphabetical order (for consistency). // See https://www.npmjs.com/package/prop-types#usage. Checklist.propTypes = { /** * Define both `chartLabel` and `chartValue` to show the chart and its label together. */ chartLabel: PropTypes.string, /** * A number between 0 and 1. * * Define both `chartLabel` and `chartValue` to show the chart and its label together. */ chartValue: PropTypes.number, /** * Aria-label for the Checklist component. */ checklistAriaLabel: PropTypes.string, /** * Aria-label for the Checklist's toggle component. */ checklistToggleAriaLabel: PropTypes.string, /** * Provide an optional class to be applied to the containing node. */ className: PropTypes.string, /** * Whether or not to show the open/close toggle. */ enableToggle: PropTypes.bool, /** * Callback for the "View all" button. See also `viewAllLabel`. */ onClickViewAll: PropTypes.func, /** * Optional callback for when the list is opened/closed. */ onToggle: PropTypes.func, /** * Specifies whether the component is opened or closed. * This can be set during initialization, or changed after being rendered. */ open: PropTypes.bool, /** * The task list can be broken down into sub-lists. * * Each sub-list can include an optional `title`. * * Each task must specify the appropriate icon (`kind`) and `label`. * * If any task has an `onClick` callback function defined, * then the `label` will be rendered as a button, * else the `label` will be rendered as plain text. */ /**@ts-ignore */ taskLists: PropTypes.arrayOf(PropTypes.shape({ title: PropTypes.string, tasks: PropTypes.arrayOf(PropTypes.shape({ kind: PropTypes.oneOf([Kinds.unchecked, Kinds.indeterminate, Kinds.checked, Kinds.disabled, Kinds.error]).isRequired, label: PropTypes.string.isRequired, onClick: PropTypes.func })).isRequired })).isRequired, /** * Determines the theme of the component. */ theme: PropTypes.oneOf([Themes.light, Themes.dark]), /** * The title of the component. */ title: PropTypes.node, /** * The label for the toggle's tooltip. */ toggleLabel: PropTypes.string, /** * The alignment of the toggle's tooltip. */ toggleLabelAlign: PropTypes.oneOf(['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'right']), /** * If defined, will show and enable the "View all (#)" button at the bottom of the list. */ viewAllLabel: PropTypes.string }; export { Checklist };