From 03d068bd1121f9162b6ba700319a13a972fc39fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C4=8Curda?= Date: Mon, 15 Jul 2024 15:45:47 +0200 Subject: [PATCH] Feat(web-react): Introduce UNSTABLE_Toggle component #DS-1346 --- packages/web-react/scripts/entryPoints.js | 2 + .../src/components/UNSTABLE_Toggle/README.md | 110 +++++++++++++++++ .../UNSTABLE_Toggle/UNSTABLE_Toggle.tsx | 75 ++++++++++++ .../__tests__/UNSTABLE_Toggle.test.tsx | 111 ++++++++++++++++++ .../__tests__/useToggleStyleProps.test.ts | 62 ++++++++++ .../UNSTABLE_Toggle/demo/ToggleDefault.tsx | 11 ++ .../UNSTABLE_Toggle/demo/ToggleDisabled.tsx | 18 +++ .../UNSTABLE_Toggle/demo/ToggleFluid.tsx | 6 + .../UNSTABLE_Toggle/demo/ToggleHelperText.tsx | 17 +++ .../demo/ToggleHiddenLabel.tsx | 8 ++ .../UNSTABLE_Toggle/demo/ToggleIndicators.tsx | 11 ++ .../UNSTABLE_Toggle/demo/ToggleRequired.tsx | 6 + .../UNSTABLE_Toggle/demo/ToggleValidation.tsx | 34 ++++++ .../components/UNSTABLE_Toggle/demo/index.tsx | 40 +++++++ .../src/components/UNSTABLE_Toggle/index.html | 1 + .../src/components/UNSTABLE_Toggle/index.ts | 4 + .../stories/UNSTABLE_Toggle.stories.tsx | 44 +++++++ .../UNSTABLE_Toggle/useToggleStyleProps.ts | 64 ++++++++++ packages/web-react/src/components/index.ts | 1 + packages/web-react/src/types/index.ts | 1 + packages/web-react/src/types/toggle.ts | 35 ++++++ .../web-react-chromium-linux.png | Bin 98781 -> 99697 bytes 22 files changed, 661 insertions(+) create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/README.md create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/UNSTABLE_Toggle.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/__tests__/UNSTABLE_Toggle.test.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/__tests__/useToggleStyleProps.test.ts create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDefault.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDisabled.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleFluid.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleHelperText.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleHiddenLabel.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleIndicators.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleRequired.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleValidation.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/demo/index.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/index.html create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/index.ts create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/stories/UNSTABLE_Toggle.stories.tsx create mode 100644 packages/web-react/src/components/UNSTABLE_Toggle/useToggleStyleProps.ts create mode 100644 packages/web-react/src/types/toggle.ts diff --git a/packages/web-react/scripts/entryPoints.js b/packages/web-react/scripts/entryPoints.js index 49767f4e54..4cf53f2d76 100644 --- a/packages/web-react/scripts/entryPoints.js +++ b/packages/web-react/scripts/entryPoints.js @@ -52,6 +52,8 @@ const entryPoints = [ { dirs: ['components', 'UNSTABLE_PartnerLogo'] }, { dirs: ['components', 'UNSTABLE_ProductLogo'] }, { dirs: ['components', 'UNSTABLE_Slider'] }, + { dirs: ['components', 'UNSTABLE_Toggle'] }, + { dirs: ['components', 'UNSTABLE_Truncate'] }, { dirs: ['components', 'VisuallyHidden'] }, ]; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/README.md b/packages/web-react/src/components/UNSTABLE_Toggle/README.md new file mode 100644 index 0000000000..a7a2aa549c --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/README.md @@ -0,0 +1,110 @@ +# UNSTABLE Toggle + +> ⚠️ This component is UNSTABLE. It may significantly change at any point in the future. +> Please use it with caution. + +Toggle is a form control that allows users to switch between two states. + +## Basic Usage + +The Toggle component implements the HTML [checkbox input][mdn-checkbox] element. It uses +the native input element and styles it to look like a toggle switch. + +```jsx +import { UNSTABLE_Toggle } from '@lmc-eu/spirit-web-react/components'; + +; +``` + +## Indicators + +If you need to indicate the state of the toggle, you can add the `hasIndicators` prop. This will add a visual indicators to the toggle switch. + +```jsx + +``` + +## Required + +Add the `isRequired` prop to mark it as required. + +```jsx + +``` + +## Hidden Label + +```jsx + +``` + +## Fluid + +```jsx + +``` + +## Helper Text + +```jsx + +``` + +## Validation States + +Validation states can be presented by prop `validationState`. See Validation state [dictionary][dictionary-validation]. + +```jsx + + + +``` + +## Disabled State + +You can add `isDisabled` prop to disable Toggle. + +```jsx + +``` + +## API + +| Name | Type | Default | Required | Description | +| ----------------- | ---------------------------------------------- | ------- | -------- | ---------------------------------------------------- | +| `autoComplete` | `string` | - | ✕ | [Automated assistance in filling][autocomplete-attr] | +| `hasIndicators` | boolean | `false` | ✕ | Whether has visual indicators | +| `helperText` | string | - | ✕ | Helper text | +| `id` | string | - | ✓ | Input and label identification | +| `isChecked` | boolean | `false` | ✕ | Whether is toggle checked | +| `isDisabled` | boolean | `false` | ✕ | Whether is toggle disabled | +| `isFluid` | boolean | `false` | ✕ | Whether is toggle fluid | +| `isLabelHidden` | boolean | `false` | ✕ | Whether is label hidden | +| `label` | string | - | ✓ | Label text | +| `name` | string | - | ✕ | Input name | +| `onChange` | (event: ChangeEvent) => void | - | ✕ | Change event handler | +| `ref` | `ForwardedRef` | - | ✕ | Input element reference | +| `validationState` | [Validation dictionary][dictionary-validation] | - | ✕ | Type of validation state | +| `validationText` | `string` \| `string[]` | - | ✕ | Validation text | + +The components accept [additional attributes][readme-additional-attributes]. +If you need more control over the styling of a component, you can use [style props][readme-style-props] +and [escape hatches][readme-escape-hatches]. + +[autocomplete-attr]: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete +[dictionary-validation]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#validation +[mdn-checkbox]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox +[readme-additional-attributes]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#additional-attributes +[readme-escape-hatches]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#escape-hatches +[readme-style-props]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#style-props diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/UNSTABLE_Toggle.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/UNSTABLE_Toggle.tsx new file mode 100644 index 0000000000..f465f803b7 --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/UNSTABLE_Toggle.tsx @@ -0,0 +1,75 @@ +import classNames from 'classnames'; +import React, { ForwardedRef, forwardRef, useState } from 'react'; +import { useStyleProps } from '../../hooks'; +import { SpiritToggleProps } from '../../types'; +import { HelperText, useAriaIds, ValidationText } from '../Field'; +import { useToggleStyleProps } from './useToggleStyleProps'; + +/* We need an exception for components exported with forwardRef */ +/* eslint no-underscore-dangle: ['error', { allow: ['_UNSTABLE_Toggle'] }] */ +/* eslint-disable-next-line camelcase */ +const _UNSTABLE_Toggle = (props: SpiritToggleProps, ref: ForwardedRef) => { + const { classProps, props: modifiedProps } = useToggleStyleProps(props); + const { + 'aria-describedby': ariaDescribedBy = '', + id, + isDisabled, + isChecked = false, + isRequired, + label, + helperText, + onChange = () => {}, + validationState, + validationText, + ...restProps + } = modifiedProps; + + const { styleProps, props: otherProps } = useStyleProps(restProps); + + const [ids, register] = useAriaIds(ariaDescribedBy); + const [checked, setChecked] = useState(isChecked); + + const handleOnChange = (event: React.ChangeEvent) => { + setChecked(event.target.checked); + onChange(event); + }; + + return ( + + ); +}; + +export const UNSTABLE_Toggle = forwardRef(_UNSTABLE_Toggle); + +export default UNSTABLE_Toggle; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/__tests__/UNSTABLE_Toggle.test.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/__tests__/UNSTABLE_Toggle.test.tsx new file mode 100644 index 0000000000..6d1e057e66 --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/__tests__/UNSTABLE_Toggle.test.tsx @@ -0,0 +1,111 @@ +import '@testing-library/jest-dom'; +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest'; +import { validationStatePropsTest } from '../../../../tests/providerTests/dictionaryPropsTest'; +import { requiredPropsTest } from '../../../../tests/providerTests/requiredPropsTest'; +import { restPropsTest } from '../../../../tests/providerTests/restPropsTest'; +import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +describe('UNSTABLE_Toggle', () => { + classNamePrefixProviderTest(UNSTABLE_Toggle, 'UNSTABLE_Toggle'); + + stylePropsTest(UNSTABLE_Toggle); + + restPropsTest(UNSTABLE_Toggle, 'input'); + + validationStatePropsTest(UNSTABLE_Toggle, 'UNSTABLE_Toggle--'); + + requiredPropsTest(UNSTABLE_Toggle, 'checkbox', 'id', 'example-id'); + + it('should have correct className', () => { + render(); + + expect(screen.getByRole('checkbox').parentElement).toHaveClass('UNSTABLE_Toggle'); + }); + + it('should have label classname', () => { + render(); + + const label = screen.getByText('Toggle Label'); + + expect(label).toHaveClass('UNSTABLE_Toggle__label'); + expect(label).toContainHTML('label'); + }); + + it('should have label with required classname', () => { + render(); + + const label = screen.getByText('Toggle Label'); + + expect(label).toHaveClass('UNSTABLE_Toggle__label'); + expect(label).toHaveClass('UNSTABLE_Toggle__label--required'); + }); + + it('should have hidden classname', () => { + render(); + + const label = screen.getByText('Toggle Label'); + + expect(label).toHaveClass('UNSTABLE_Toggle__label'); + expect(label).toHaveClass('UNSTABLE_Toggle__label--hidden'); + }); + + it('should have input classname', () => { + render(); + + expect(screen.getByRole('checkbox')).toHaveClass('UNSTABLE_Toggle__input'); + }); + + it('should have helper text with correct classname', () => { + render(); + + const helperText = screen.getByText('Helper Text'); + + expect(helperText).toBeInTheDocument(); + expect(helperText).toHaveClass('UNSTABLE_Toggle__helperText'); + }); + + it('should have correct attribute when checked', () => { + render(); + + expect(screen.getByRole('checkbox')).toBeChecked(); + }); + + it('should have correct attribute when disabled', () => { + render(); + + expect(screen.getByRole('checkbox')).toBeDisabled(); + }); + + it('should have correct classname if fluid', () => { + render(); + + const checkbox = screen.getByRole('checkbox'); + + expect(checkbox.parentElement).toHaveClass('UNSTABLE_Toggle'); + expect(checkbox.parentElement).toHaveClass('UNSTABLE_Toggle--fluid'); + }); + + it('should have indicators classname', () => { + render(); + + const checkbox = screen.getByRole('checkbox'); + + expect(checkbox).toHaveClass('UNSTABLE_Toggle__input'); + expect(checkbox).toHaveClass('UNSTABLE_Toggle__input--indicators'); + }); + + it('should change the state of the checkbox when clicked', () => { + render(); + + const checkbox = screen.getByRole('checkbox'); + + expect(checkbox).not.toBeChecked(); + + fireEvent.click(checkbox); + + expect(checkbox).toBeChecked(); + }); +}); diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/__tests__/useToggleStyleProps.test.ts b/packages/web-react/src/components/UNSTABLE_Toggle/__tests__/useToggleStyleProps.test.ts new file mode 100644 index 0000000000..c2534ebb4b --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/__tests__/useToggleStyleProps.test.ts @@ -0,0 +1,62 @@ +import { renderHook } from '@testing-library/react'; +import { ValidationStates } from '../../../constants'; +import { SpiritToggleProps } from '../../../types'; +import { useToggleStyleProps } from '../useToggleStyleProps'; + +describe('useToggleStyleProps', () => { + it('should return defaults', () => { + const props = { id: 'toggle', label: 'text' }; + const { result } = renderHook(() => useToggleStyleProps(props)); + + expect(result.current.classProps).toEqual({ + root: 'UNSTABLE_Toggle', + text: 'UNSTABLE_Toggle__text', + input: 'UNSTABLE_Toggle__input', + label: 'UNSTABLE_Toggle__label', + helperText: 'UNSTABLE_Toggle__helperText', + validationText: 'UNSTABLE_Toggle__validationText', + }); + }); + + it('should return hidden label', () => { + const props = { id: 'toggle', label: 'text', isLabelHidden: true } as SpiritToggleProps; + const { result } = renderHook(() => useToggleStyleProps(props)); + + expect(result.current.classProps.label).toBe('UNSTABLE_Toggle__label UNSTABLE_Toggle__label--hidden'); + }); + + it('should return disabled', () => { + const props = { id: 'toggle', label: 'text', isDisabled: true } as SpiritToggleProps; + const { result } = renderHook(() => useToggleStyleProps(props)); + + expect(result.current.classProps.root).toBe('UNSTABLE_Toggle UNSTABLE_Toggle--disabled'); + }); + + it.each([Object.values(ValidationStates)])('should return field with %s', (state) => { + const props = { validationState: state } as SpiritToggleProps; + const { result } = renderHook(() => useToggleStyleProps(props)); + + expect(result.current.classProps.root).toBe(`UNSTABLE_Toggle UNSTABLE_Toggle--${state}`); + }); + + it('should return fluid', () => { + const props = { id: 'toggle', label: 'text', isFluid: true } as SpiritToggleProps; + const { result } = renderHook(() => useToggleStyleProps(props)); + + expect(result.current.classProps.root).toBe('UNSTABLE_Toggle UNSTABLE_Toggle--fluid'); + }); + + it('should return required', () => { + const props = { id: 'toggle', label: 'text', isRequired: true } as SpiritToggleProps; + const { result } = renderHook(() => useToggleStyleProps(props)); + + expect(result.current.classProps.label).toBe('UNSTABLE_Toggle__label UNSTABLE_Toggle__label--required'); + }); + + it('should return input with indicators', () => { + const props = { id: 'toggle', label: 'text', hasIndicators: true } as SpiritToggleProps; + const { result } = renderHook(() => useToggleStyleProps(props)); + + expect(result.current.classProps.input).toBe('UNSTABLE_Toggle__input UNSTABLE_Toggle__input--indicators'); + }); +}); diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDefault.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDefault.tsx new file mode 100644 index 0000000000..e4ee11f330 --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDefault.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { UNSTABLE_Toggle } from '..'; + +const ToggleDefault = () => ( + <> + + + +); + +export default ToggleDefault; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDisabled.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDisabled.tsx new file mode 100644 index 0000000000..b1183a0143 --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDisabled.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +const ToggleDisabled = () => ( + <> + + + + +); + +export default ToggleDisabled; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleFluid.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleFluid.tsx new file mode 100644 index 0000000000..238d23e277 --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleFluid.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +const ToggleFluid = () => ; + +export default ToggleFluid; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleHelperText.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleHelperText.tsx new file mode 100644 index 0000000000..99c01cb62d --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleHelperText.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +const ToggleHelperText = () => ( + <> + + + +); + +export default ToggleHelperText; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleHiddenLabel.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleHiddenLabel.tsx new file mode 100644 index 0000000000..920eda882c --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleHiddenLabel.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +const ToggleHiddenLabel = () => ( + +); + +export default ToggleHiddenLabel; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleIndicators.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleIndicators.tsx new file mode 100644 index 0000000000..5a3be49d6c --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleIndicators.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +const ToggleIndicators = () => ( + <> + + + +); + +export default ToggleIndicators; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleRequired.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleRequired.tsx new file mode 100644 index 0000000000..704a94902b --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleRequired.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +const ToggleRequired = () => ; + +export default ToggleRequired; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleValidation.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleValidation.tsx new file mode 100644 index 0000000000..f40637813a --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleValidation.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +const ToggleValidation = () => ( + <> + + + + + +); + +export default ToggleValidation; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/demo/index.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/demo/index.tsx new file mode 100644 index 0000000000..171510741f --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/demo/index.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import DocsSection from '../../../../docs/DocsSections'; +import ToggleDefault from './ToggleDefault'; +import ToggleDisabled from './ToggleDisabled'; +import ToggleFluid from './ToggleFluid'; +import ToggleHelperText from './ToggleHelperText'; +import ToggleHiddenLabel from './ToggleHiddenLabel'; +import ToggleIndicators from './ToggleIndicators'; +import ToggleRequired from './ToggleRequired'; +import ToggleValidation from './ToggleValidation'; + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + + + + + + + + + + + + + + + + + + + + + + + + , +); diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/index.html b/packages/web-react/src/components/UNSTABLE_Toggle/index.html new file mode 100644 index 0000000000..8ddd1b2783 --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/index.html @@ -0,0 +1 @@ +{{> web-react/demo }} diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/index.ts b/packages/web-react/src/components/UNSTABLE_Toggle/index.ts new file mode 100644 index 0000000000..e8c4332dcc --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/index.ts @@ -0,0 +1,4 @@ +export * from './UNSTABLE_Toggle'; +export { default as UNSTABLE_Toggle } from './UNSTABLE_Toggle'; +export * from './useToggleStyleProps'; +// some comment diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/stories/UNSTABLE_Toggle.stories.tsx b/packages/web-react/src/components/UNSTABLE_Toggle/stories/UNSTABLE_Toggle.stories.tsx new file mode 100644 index 0000000000..d3a6d44d52 --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/stories/UNSTABLE_Toggle.stories.tsx @@ -0,0 +1,44 @@ +import { Markdown } from '@storybook/blocks'; +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import { ValidationStates } from '../../../constants'; +import ReadMe from '../README.md'; +import UNSTABLE_Toggle from '../UNSTABLE_Toggle'; + +const meta: Meta = { + title: 'Experimental/UNSTABLE_Toggle', + component: UNSTABLE_Toggle, + parameters: { + docs: { + page: () => {ReadMe}, + }, + }, + argTypes: { + validationState: { + control: 'select', + options: [...Object.values(ValidationStates), undefined], + table: { + defaultValue: { summary: undefined }, + }, + }, + }, + args: { + hasIndicators: false, + helperText: '', + id: 'toggle', + isRequired: false, + isDisabled: false, + isFluid: false, + isChecked: false, + label: 'Toggle Label', + validationState: undefined, + validationText: '', + }, +}; + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + name: 'UNSTABLE_Toggle', +}; diff --git a/packages/web-react/src/components/UNSTABLE_Toggle/useToggleStyleProps.ts b/packages/web-react/src/components/UNSTABLE_Toggle/useToggleStyleProps.ts new file mode 100644 index 0000000000..aa174a6c15 --- /dev/null +++ b/packages/web-react/src/components/UNSTABLE_Toggle/useToggleStyleProps.ts @@ -0,0 +1,64 @@ +import classNames from 'classnames'; +import { useClassNamePrefix } from '../../hooks'; +import { SpiritToggleProps } from '../../types'; + +export interface ToggleStyles { + classProps: { + root: string; + label: string; + text: string; + helperText: string; + input: string; + validationText: string; + }; + props: T; +} + +export function useToggleStyleProps(props: SpiritToggleProps): ToggleStyles { + const { + isRequired = false, + isFluid = false, + isDisabled = false, + isLabelHidden = false, + validationState, + hasIndicators = false, + ...restProps + } = props; + + const toggleClass = useClassNamePrefix('UNSTABLE_Toggle'); + const toggleFluidClass = `${toggleClass}--fluid`; + const toggleDisabledClass = `${toggleClass}--disabled`; + const toggleTextClass = `${toggleClass}__text`; + const toggleLabelClass = `${toggleClass}__label`; + const toggleHiddenLabelClass = `${toggleLabelClass}--hidden`; + const toggleValidationClass = `${toggleClass}--${validationState}`; + const toggleRequiredClass = `${toggleLabelClass}--required`; + const toggleInputClass = `${toggleClass}__input`; + const toggleInputIndicatorsClass = `${toggleInputClass}--indicators`; + const toggleHelperTextClass = `${toggleClass}__helperText`; + const toggleValidationTextClass = `${toggleClass}__validationText`; + const rootClass = classNames(toggleClass, { + [toggleFluidClass]: isFluid, + [toggleDisabledClass]: isDisabled, + [toggleValidationClass]: validationState, + }); + const labelClass = classNames(toggleLabelClass, { + [toggleRequiredClass]: isRequired, + [toggleHiddenLabelClass]: isLabelHidden, + }); + const inputClass = classNames(toggleInputClass, { + [toggleInputIndicatorsClass]: hasIndicators, + }); + + return { + classProps: { + root: rootClass, + label: labelClass, + text: toggleTextClass, + helperText: toggleHelperTextClass, + input: inputClass, + validationText: toggleValidationTextClass, + }, + props: { ...restProps, validationState, isDisabled, isRequired }, + }; +} diff --git a/packages/web-react/src/components/index.ts b/packages/web-react/src/components/index.ts index bf43e42351..f83c1a8bf1 100644 --- a/packages/web-react/src/components/index.ts +++ b/packages/web-react/src/components/index.ts @@ -37,4 +37,5 @@ export * from './UNSTABLE_Avatar'; export * from './UNSTABLE_Divider'; export * from './UNSTABLE_PartnerLogo'; export * from './UNSTABLE_ProductLogo'; +export * from './UNSTABLE_Toggle'; export * from './VisuallyHidden'; diff --git a/packages/web-react/src/types/index.ts b/packages/web-react/src/types/index.ts index ba69f0b3d2..0ec027fd76 100644 --- a/packages/web-react/src/types/index.ts +++ b/packages/web-react/src/types/index.ts @@ -33,5 +33,6 @@ export * from './textArea'; export * from './textField'; export * from './textFieldBase'; export * from './toast'; +export * from './toggle'; export * from './tooltip'; export * from './visuallyHidden'; diff --git a/packages/web-react/src/types/toggle.ts b/packages/web-react/src/types/toggle.ts new file mode 100644 index 0000000000..1fdaf9e7a6 --- /dev/null +++ b/packages/web-react/src/types/toggle.ts @@ -0,0 +1,35 @@ +import { ChangeEvent } from 'react'; +import { LabelProps } from './label'; +import { + ChildrenProps, + HelperTextProps, + InputBaseProps, + RequiredProps, + SpiritInputElementPropsWithRef, + StyleProps, + Validation, + ValidationTextType, +} from './shared'; + +export type ToggleElementBaseProps = SpiritInputElementPropsWithRef; + +export interface ToggleProps + extends ToggleElementBaseProps, + ChildrenProps, + LabelProps, + RequiredProps, + InputBaseProps, + HelperTextProps, + StyleProps, + Validation { + hasIndicators?: boolean; + isChecked?: boolean; + isDisabled?: boolean; + isFluid?: boolean; + isLabelHidden?: boolean; + label: string; + onChange?: (event: ChangeEvent) => void; + validationText?: ValidationTextType; +} + +export interface SpiritToggleProps extends ToggleProps {} diff --git a/tests/e2e/demo-homepages.spec.ts-snapshots/web-react-chromium-linux.png b/tests/e2e/demo-homepages.spec.ts-snapshots/web-react-chromium-linux.png index 8b405c1c33dd2a138bbb043312cb513ec344257d..bc6c0eaa708d2e7c2ac6397aae5e187447676b26 100644 GIT binary patch delta 13148 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 BfRF$H delta 12225 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