-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(web-react): Introduce UNSTABLE_Toggle component #DS-1346
- Loading branch information
Showing
22 changed files
with
660 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
packages/web-react/src/components/UNSTABLE_Toggle/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'; | ||
|
||
<UNSTABLE_Toggle id="toggle-default" label="Toggle Label" />; | ||
``` | ||
|
||
## 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 | ||
<UNSTABLE_Toggle id="toggle-indicators" label="Toggle Label" hasIndicators /> | ||
``` | ||
|
||
## Required | ||
|
||
Add the `isRequired` prop to mark it as required. | ||
|
||
```jsx | ||
<UNSTABLE_Toggle id="toggle-required" label="Toggle Label" isRequired /> | ||
``` | ||
|
||
## Hidden Label | ||
|
||
```jsx | ||
<UNSTABLE_Toggle id="toggle-hidden-label" label="Toggle Label" isLabelHidden /> | ||
``` | ||
|
||
## Fluid | ||
|
||
```jsx | ||
<UNSTABLE_Toggle id="toggle-fluid" label="Toggle Label" isFluid /> | ||
``` | ||
|
||
## Helper Text | ||
|
||
```jsx | ||
<UNSTABLE_Toggle id="toggle-helper-text" label="Toggle Label" helperText="Helper text" /> | ||
``` | ||
|
||
## Validation States | ||
|
||
Validation states can be presented by prop `validationState`. See Validation state [dictionary][dictionary-validation]. | ||
|
||
```jsx | ||
<UNSTABLE_Toggle id="toggle-success" label="Toggle Label" validationState="success" /> | ||
<UNSTABLE_Toggle | ||
id="toggle-warning" | ||
label="Toggle Label" | ||
validationText="Validation text" | ||
validationState="warning" | ||
isChecked | ||
/> | ||
<UNSTABLE_Toggle | ||
id="toggle-danger" | ||
label="Toggle Label" | ||
validationText={['First validation text', 'Second validation text']} | ||
validationState="danger" | ||
/> | ||
``` | ||
|
||
## Disabled State | ||
|
||
You can add `isDisabled` prop to disable Toggle. | ||
|
||
```jsx | ||
<UNSTABLE_Toggle id="toggle-disabled" label="Toggle Label" isDisabled /> | ||
``` | ||
|
||
## 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<HTMLInputElement>) => void | - | ✕ | Change event handler | | ||
| `ref` | `ForwardedRef<HTMLInputElement>` | - | ✕ | 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 |
75 changes: 75 additions & 0 deletions
75
packages/web-react/src/components/UNSTABLE_Toggle/UNSTABLE_Toggle.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<HTMLInputElement>) => { | ||
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<HTMLInputElement>) => { | ||
setChecked(event.target.checked); | ||
onChange(event); | ||
}; | ||
|
||
return ( | ||
<label {...styleProps} htmlFor={id} className={classNames(classProps.root, styleProps.className)}> | ||
<span className={classProps.text}> | ||
<span className={classProps.label}>{label}</span> | ||
<HelperText | ||
className={classProps.helperText} | ||
elementType="span" | ||
id={`${id}__helperText`} | ||
registerAria={register} | ||
helperText={helperText} | ||
/> | ||
{validationState && ( | ||
<ValidationText | ||
className={classProps.validationText} | ||
id={`${id}__validationText`} | ||
validationText={validationText} | ||
registerAria={register} | ||
/> | ||
)} | ||
</span> | ||
<input | ||
{...otherProps} | ||
aria-describedby={ids.join(' ')} | ||
type="checkbox" | ||
id={id} | ||
className={classProps.input} | ||
disabled={isDisabled} | ||
checked={checked} | ||
required={isRequired} | ||
onChange={handleOnChange} | ||
ref={ref} | ||
/> | ||
</label> | ||
); | ||
}; | ||
|
||
export const UNSTABLE_Toggle = forwardRef<HTMLInputElement, SpiritToggleProps>(_UNSTABLE_Toggle); | ||
|
||
export default UNSTABLE_Toggle; |
111 changes: 111 additions & 0 deletions
111
packages/web-react/src/components/UNSTABLE_Toggle/__tests__/UNSTABLE_Toggle.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" />); | ||
|
||
expect(screen.getByRole('checkbox').parentElement).toHaveClass('UNSTABLE_Toggle'); | ||
}); | ||
|
||
it('should have label classname', () => { | ||
render(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" />); | ||
|
||
const label = screen.getByText('Toggle Label'); | ||
|
||
expect(label).toHaveClass('UNSTABLE_Toggle__label'); | ||
expect(label).toContainHTML('label'); | ||
}); | ||
|
||
it('should have label with required classname', () => { | ||
render(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" isRequired />); | ||
|
||
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(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" isLabelHidden />); | ||
|
||
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(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" />); | ||
|
||
expect(screen.getByRole('checkbox')).toHaveClass('UNSTABLE_Toggle__input'); | ||
}); | ||
|
||
it('should have helper text with correct classname', () => { | ||
render(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" helperText="Helper Text" />); | ||
|
||
const helperText = screen.getByText('Helper Text'); | ||
|
||
expect(helperText).toBeInTheDocument(); | ||
expect(helperText).toHaveClass('UNSTABLE_Toggle__helperText'); | ||
}); | ||
|
||
it('should have correct attribute when checked', () => { | ||
render(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" isChecked />); | ||
|
||
expect(screen.getByRole('checkbox')).toBeChecked(); | ||
}); | ||
|
||
it('should have correct attribute when disabled', () => { | ||
render(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" isDisabled />); | ||
|
||
expect(screen.getByRole('checkbox')).toBeDisabled(); | ||
}); | ||
|
||
it('should have correct classname if fluid', () => { | ||
render(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" isFluid />); | ||
|
||
const checkbox = screen.getByRole('checkbox'); | ||
|
||
expect(checkbox.parentElement).toHaveClass('UNSTABLE_Toggle'); | ||
expect(checkbox.parentElement).toHaveClass('UNSTABLE_Toggle--fluid'); | ||
}); | ||
|
||
it('should have indicators classname', () => { | ||
render(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" hasIndicators />); | ||
|
||
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(<UNSTABLE_Toggle id="test-toggle" label="Toggle Label" />); | ||
|
||
const checkbox = screen.getByRole('checkbox'); | ||
|
||
expect(checkbox).not.toBeChecked(); | ||
|
||
fireEvent.click(checkbox); | ||
|
||
expect(checkbox).toBeChecked(); | ||
}); | ||
}); |
62 changes: 62 additions & 0 deletions
62
packages/web-react/src/components/UNSTABLE_Toggle/__tests__/useToggleStyleProps.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'); | ||
}); | ||
}); |
11 changes: 11 additions & 0 deletions
11
packages/web-react/src/components/UNSTABLE_Toggle/demo/ToggleDefault.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import React from 'react'; | ||
import { UNSTABLE_Toggle } from '..'; | ||
|
||
const ToggleDefault = () => ( | ||
<> | ||
<UNSTABLE_Toggle id="toggle-default" label="Toggle Label" name="default" /> | ||
<UNSTABLE_Toggle id="toggle-default-checked" label="Toggle Label" name="default" isChecked /> | ||
</> | ||
); | ||
|
||
export default ToggleDefault; |
Oops, something went wrong.