From fff99dbe78e2384e64ae52fb3412479c5de22cf2 Mon Sep 17 00:00:00 2001 From: Alessandra Davila Date: Fri, 31 Mar 2023 10:50:03 -0500 Subject: [PATCH] feat(checkbox): add checkbox group and validation states (#13394) * feat(checkbox): add checkbox group and invalid states * feat(checkbox): add readonly and helper text to checkbox group * chore(checkbox): update tests * feat(checkbox): add validation states to individual checkbox * chore(checkbox): update snapshots * chore(checkbox): yarn format * Update Checkbox.tsx * test(checkbox): add tests to checkbox group and checkbox * Update packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/Checkbox/__tests__/Checkbox-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/Checkbox/__tests__/Checkbox-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/Checkbox/__tests__/Checkbox-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/Checkbox/__tests__/Checkbox-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * Update packages/react/src/components/Checkbox/__tests__/Checkbox-test.js Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * chore(checkbox): move checkbox stories * chore(checkbox): update checkbox stories * chore(checkbox): update checkbox stories --------- Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> Co-authored-by: TJ Egan Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../__snapshots__/PublicAPI-test.js.snap | 50 ++++++ packages/react/src/__tests__/index-test.js | 1 + .../components/Checkbox/Checkbox.stories.js | 151 +++++++++++++++--- .../src/components/Checkbox/Checkbox.tsx | 95 +++++++++++ .../Checkbox/__tests__/Checkbox-test.js | 89 +++++++++++ .../CheckboxGroup/CheckboxGroup-test.js | 150 +++++++++++++++++ .../components/CheckboxGroup/CheckboxGroup.js | 141 ++++++++++++++++ .../src/components/CheckboxGroup/index.js | 11 ++ .../src/components/Skeleton/docs/overview.mdx | 20 --- packages/react/src/index.js | 1 + packages/react/src/index.ts | 1 + .../scss/components/checkbox/_checkbox.scss | 108 +++++++++++++ 12 files changed, 778 insertions(+), 40 deletions(-) create mode 100644 packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js create mode 100644 packages/react/src/components/CheckboxGroup/CheckboxGroup.js create mode 100644 packages/react/src/components/CheckboxGroup/index.js delete mode 100644 packages/react/src/components/Skeleton/docs/overview.mdx diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index ecceda987880..254fc3b09a0f 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -456,6 +456,9 @@ Map { "disabled": Object { "type": "bool", }, + "helperText": Object { + "type": "node", + }, "hideLabel": Object { "type": "bool", }, @@ -466,6 +469,12 @@ Map { "indeterminate": Object { "type": "bool", }, + "invalid": Object { + "type": "bool", + }, + "invalidText": Object { + "type": "node", + }, "labelText": Object { "isRequired": true, "type": "node", @@ -479,9 +488,50 @@ Map { "title": Object { "type": "string", }, + "warn": Object { + "type": "bool", + }, + "warnText": Object { + "type": "node", + }, }, "render": [Function], }, + "CheckboxGroup" => Object { + "propTypes": Object { + "children": Object { + "type": "node", + }, + "className": Object { + "type": "string", + }, + "helperText": Object { + "type": "node", + }, + "invalid": Object { + "type": "bool", + }, + "invalidText": Object { + "type": "node", + }, + "legendId": Object { + "type": "node", + }, + "legendText": Object { + "isRequired": true, + "type": "node", + }, + "readOnly": Object { + "type": "bool", + }, + "warn": Object { + "type": "bool", + }, + "warnText": Object { + "type": "node", + }, + }, + }, "CheckboxSkeleton" => Object { "propTypes": Object { "className": Object { diff --git a/packages/react/src/__tests__/index-test.js b/packages/react/src/__tests__/index-test.js index 475bd9305453..3a3f3069adf4 100644 --- a/packages/react/src/__tests__/index-test.js +++ b/packages/react/src/__tests__/index-test.js @@ -27,6 +27,7 @@ describe('Carbon Components React', () => { "ButtonSet", "ButtonSkeleton", "Checkbox", + "CheckboxGroup", "CheckboxSkeleton", "ClassPrefix", "ClickableTile", diff --git a/packages/react/src/components/Checkbox/Checkbox.stories.js b/packages/react/src/components/Checkbox/Checkbox.stories.js index a469ddf6e592..cab31f4a1bc6 100644 --- a/packages/react/src/components/Checkbox/Checkbox.stories.js +++ b/packages/react/src/components/Checkbox/Checkbox.stories.js @@ -8,13 +8,23 @@ import React from 'react'; import { default as Checkbox, CheckboxSkeleton } from './'; import mdx from './Checkbox.mdx'; +import CheckboxGroup from '../CheckboxGroup'; -const prefix = 'cds'; +const checkboxEvents = { + className: 'some-class', + labelText: 'Checkbox label', +}; + +const fieldsetCheckboxProps = () => ({ + className: 'some-class', + legendText: 'Group label', +}); export default { title: 'Components/Checkbox', component: Checkbox, subcomponents: { + CheckboxGroup, CheckboxSkeleton, }, parameters: { @@ -26,55 +36,156 @@ export default { export const Default = () => { return ( -
- Group label + -
+ + ); +}; + +export const Single = () => { + return ( + <> + +

+ +

+ +

+ + ); }; export const Skeleton = () => ; export const Playground = (args) => ( -
- Group label - - -
+ + + + + ); Playground.argTypes = { - checked: { + helperText: { + description: 'Provide text for the form group for additional help', + control: { + type: 'text', + }, + defaultValue: 'Helper text goes here', + }, + invalid: { + description: 'Specify whether the form group is currently invalid', control: { type: 'boolean', }, + defaultValue: false, }, - className: { - control: false, + invalidText: { + description: + 'Provide the text that is displayed when the form group is in an invalid state', + control: { + type: 'text', + }, + defaultValue: 'Invalid message goes here', }, - defaultChecked: { - control: false, + legendText: { + description: + 'Provide the text to be rendered inside of the fieldset ', + control: { + type: 'text', + }, }, - disabled: { + readOnly: { + description: 'Specify whether the CheckboxGroup is read-only', control: { type: 'boolean', }, + defaultValue: false, }, - hideLabel: { + warn: { + description: 'Specify whether the form group is currently in warning state', control: { type: 'boolean', }, + defaultValue: false, + }, + warnText: { + description: + 'Provide the text that is displayed when the form group is in warning state', + control: { + type: 'text', + }, + defaultValue: 'Warning message goes here', + }, + checked: { + table: { + disable: true, + }, + }, + className: { + table: { + disable: true, + }, + }, + defaultChecked: { + table: { + disable: true, + }, + }, + disabled: { + table: { + disable: true, + }, + }, + hideLabel: { + table: { + disable: true, + }, }, id: { - control: false, + table: { + disable: true, + }, }, indeterminate: { - control: { - type: 'boolean', + table: { + disable: true, }, }, labelText: { - control: false, + table: { + disable: true, + }, + }, + onChange: { + table: { + disable: true, + }, + }, + title: { + table: { + disable: true, + }, }, }; diff --git a/packages/react/src/components/Checkbox/Checkbox.tsx b/packages/react/src/components/Checkbox/Checkbox.tsx index c6258936ab9a..c375b2fdde1c 100644 --- a/packages/react/src/components/Checkbox/Checkbox.tsx +++ b/packages/react/src/components/Checkbox/Checkbox.tsx @@ -10,6 +10,10 @@ import React from 'react'; import classNames from 'classnames'; import { Text } from '../Text'; import { usePrefix } from '../../internal/usePrefix'; +import { WarningFilled, WarningAltFilled } from '@carbon/icons-react'; +import setupGetInstanceId from '../../tools/setupGetInstanceId'; + +const getInstanceId = setupGetInstanceId(); type ExcludedAttributes = 'id' | 'onChange' | 'onClick' | 'type'; @@ -39,6 +43,11 @@ export interface CheckboxProps */ disabled?: boolean; + /** + * Provide text for the form group for additional help + */ + helperText?: React.ReactNode; + /** * Specify whether the label should be hidden, or not */ @@ -49,6 +58,26 @@ export interface CheckboxProps */ indeterminate?: boolean; + /** + * Specify whether the Checkbox is currently invalid + */ + invalid?: boolean; + + /** + * Provide the text that is displayed when the Checkbox is in an invalid state + */ + invalidText: React.ReactNode; + + /** + * Specify whether the Checkbox is currently invalid + */ + warn?: boolean; + + /** + * Provide the text that is displayed when the Checkbox is in an invalid state + */ + warnText: React.ReactNode; + /** * Provide an optional handler that is called when the internal state of * Checkbox changes. This handler is called with event and state info. @@ -69,25 +98,48 @@ const Checkbox = React.forwardRef( ( { className, + helperText, id, labelText, onChange, onClick, indeterminate, + invalid, + invalidText, hideLabel, readOnly, title = '', + warn, + warnText, ...other }: CheckboxProps, ref ) => { const prefix = usePrefix(); + + const showWarning = !readOnly && !invalid && warn; + const showHelper = !invalid && !warn; + + const checkboxGroupInstanceId = getInstanceId(); + + const helperId = !helperText + ? undefined + : `checkbox-helper-text-${checkboxGroupInstanceId}`; + + const helper = helperText ? ( +
+ {helperText} +
+ ) : null; + const wrapperClasses = classNames( `${prefix}--form-item`, `${prefix}--checkbox-wrapper`, className, { [`${prefix}--checkbox-wrapper--readonly`]: readOnly, + [`${prefix}--checkbox-wrapper--invalid`]: !readOnly && invalid, + [`${prefix}--checkbox-wrapper--warning`]: showWarning, } ); const innerLabelClasses = classNames(`${prefix}--checkbox-label-text`, { @@ -99,6 +151,7 @@ const Checkbox = React.forwardRef( { if (!readOnly && onChange) { onChange(evt, { checked: evt.target.checked, id }); @@ -136,6 +189,23 @@ const Checkbox = React.forwardRef( title={title}> {labelText} +
+ {!readOnly && invalid && ( + <> + +
{invalidText}
+ + )} + {showWarning && ( + <> + +
{warnText}
+ + )} +
+ {showHelper && helper} ); } @@ -162,6 +232,11 @@ Checkbox.propTypes = { */ disabled: PropTypes.bool, + /** + * Provide text for the form group for additional help + */ + helperText: PropTypes.node, + /** * Specify whether the label should be hidden, or not */ @@ -177,6 +252,16 @@ Checkbox.propTypes = { */ indeterminate: PropTypes.bool, + /** + * Specify whether the Checkbox is currently invalid + */ + invalid: PropTypes.bool, + + /** + * Provide the text that is displayed when the Checkbox is in an invalid state + */ + invalidText: PropTypes.node, + /** * Provide a label to provide a description of the Checkbox input that you are * exposing to the user @@ -199,6 +284,16 @@ Checkbox.propTypes = { * Specify a title for the