Skip to content

Commit

Permalink
Merge pull request #415 from Automattic/forno-1690/add/toggle-group
Browse files Browse the repository at this point in the history
FORNO-1690: Add toggle-group component
  • Loading branch information
aswasif007 authored Jul 4, 2024
2 parents 2232498 + f25ad0f commit d82b3ee
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 9 deletions.
108 changes: 100 additions & 8 deletions src/system/Form/RadioBoxGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,103 @@ const RadioOption = ( {
);
};

RadioOption.propTypes = {
const ChipOption = ( {
defaultValue,
option: { id, value, label },
name,
disabled,
onChangeHandler,
} ) => {
const checked = `${ defaultValue }` === `${ value }`;
const forLabel = id || value;
const ref = React.useRef( null );
const describedById = `input-radio-box-${ forLabel }-description`;

return (
<div
id={ `o${ forLabel }` }
onClick={ () => {
ref.current?.click();
} }
sx={ {
display: 'inline-flex',
position: 'relative',
background: checked ? 'layer.4' : undefined,
color: 'text',
minHeight: '32px',
boxShadow: checked ? 'low' : undefined,
'&:hover': {
background: checked ? 'layer.4' : 'layer.1',
},
borderRadius: 1,
} }
>
<input
ref={ ref }
type="radio"
id={ forLabel }
disabled={ disabled }
name={ name }
checked={ checked }
aria-checked={ checked }
value={ value }
onChange={ onChangeHandler }
aria-labelledby={ describedById }
sx={ {
opacity: 0,
height: 0,
width: 0,
position: 'absolute',
'&:focus-visible + label': theme => theme.outline,
} }
/>

<label
id={ describedById }
htmlFor={ forLabel }
sx={ {
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
width: '100%',
px: 3,
fontWeight: 400,
fontSize: 2,
cursor: 'pointer',
borderRadius: 1,
} }
>
{ label }
</label>
</div>
);
};

ChipOption.propTypes = RadioOption.propTypes = {
defaultValue: PropTypes.string,
option: PropTypes.object,
name: PropTypes.string,
onChangeHandler: PropTypes.func,
checked: PropTypes.bool,
disabled: PropTypes.bool,
width: PropTypes.string,
};

const groupStyleOverrides = {
chip: {
background: 'layer.3',
p: 1,
display: 'inline-flex',
gap: 1,
borderRadius: 1,
},
primary: {
display: 'inline-block',
mb: 2,
p: 0,
},
};

const RadioBoxGroup = React.forwardRef(
(
{
Expand All @@ -117,6 +204,7 @@ const RadioBoxGroup = React.forwardRef(
errorMessage,
hasError,
required,
variant = 'primary',
...props
},
forwardRef
Expand All @@ -131,8 +219,13 @@ const RadioBoxGroup = React.forwardRef(
[ onChange ]
);

let Option = RadioOption;
if ( variant === 'chip' ) {
Option = ChipOption;
}

const renderedOptions = options.map( option => (
<RadioOption
<Option
defaultValue={ defaultValue }
disabled={ disabled }
key={ option?.id || option?.value }
Expand All @@ -148,11 +241,9 @@ const RadioBoxGroup = React.forwardRef(
<fieldset
sx={ {
border: 0,
p: hasError ? 2 : 0,
display: 'inline-block',
mb: 2,
...groupStyleOverrides[ variant ],
...( hasError
? { border: '1px solid', borderColor: 'input.border.error', borderRadius: 2 }
? { border: '1px solid', borderColor: 'input.border.error', borderRadius: 2, p: 2 }
: {} ),
} }
ref={ forwardRef }
Expand All @@ -171,7 +262,7 @@ const RadioBoxGroup = React.forwardRef(
<div
sx={ {
display: 'flex',
gap: 2,
gap: variant === 'chip' ? 1 : 2,
} }
>
{ renderedOptions }
Expand Down Expand Up @@ -202,6 +293,7 @@ RadioBoxGroup.propTypes = {
errorMessage: PropTypes.string,
hasError: PropTypes.bool,
required: PropTypes.bool,
variant: PropTypes.oneOf( [ 'primary', 'chip' ] ),
};

export { RadioBoxGroup };
51 changes: 51 additions & 0 deletions src/system/Form/RadioBoxGroup.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,31 @@ import { RadioBoxGroup } from '..';
export default {
title: 'RadioBoxGroup',
component: RadioBoxGroup,
parameters: {
docs: {
description: {
component: `
A radio-box-group is a group of radio buttons that are styled as boxes. This component is used
to allow users to select one option from a list of options.
## Guidance
### When to use the component
- <strong>Select an option in a form.</strong> Use a radio-box-group when you want users to select
a single option from a list of options.
- <strong>Use as a toggle-group.</strong> Use a radio-box-group with the chip variant when you want
to allow users to toggle between different options.
-------
This documentation is heavily inspired by the [U.S Web Design System (USWDS)](https://designsystem.digital.gov/components/tooltip/#package). We use USWDS as trusted source of truth for accessibility and usability best practices.
## Component Properties
`,
},
},
},
};

const options = [
Expand Down Expand Up @@ -58,3 +83,29 @@ export const Errors = () => {
/>
);
};

export const ChipVariant = () => {
const [ value, setValue ] = useState( 'table' );

return (
<RadioBoxGroup
defaultValue={ value }
onChange={ e => setValue( e.target.value ) }
options={ [
{
label: 'Table',
value: 'table',
},
{
label: 'Grid',
value: 'grid',
},
{
label: 'Card',
value: 'card',
},
] }
variant="chip"
/>
);
};
47 changes: 47 additions & 0 deletions src/system/Form/RadioBoxGroup.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
import { axe } from 'jest-axe';

/**
* Internal dependencies
*/
import { RadioBoxGroup } from './RadioBoxGroup';

const defaultProps = {
options: [
{
label: 'One',
value: 'one',
description: 'This is desc 1',
},
{
label: 'Two',
value: 'two',
description: 'This is desc 2',
},
{
label: 'Three',
value: 'three',
description: 'This is desc 3',
},
],
onChange: jest.fn(),
};

describe( '<RadioBoxGroup />', () => {
it.each( [ 'primary', 'chip' ] )( 'renders the default variant', async variant => {
const { container } = render( <RadioBoxGroup { ...defaultProps } variant={ variant } /> );

const dom = await screen.findAllByRole( 'radio' );

expect( dom ).toHaveLength( 3 );
expect( dom[ 0 ] ).toHaveAttribute( 'value', 'one' );
expect( dom[ 1 ] ).toHaveAttribute( 'value', 'two' );
expect( dom[ 2 ] ).toHaveAttribute( 'value', 'three' );

// Check for accessibility issues
expect( await axe( container ) ).toHaveNoViolations();
} );
} );
2 changes: 1 addition & 1 deletion src/system/Tooltip/Tooltip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A tooltip is a short descriptive message that appears when a user hovers or focu
element. Our tooltip aims to be 100% CSS-only. It uses a global css approach to inject the
tooltip styles.
## Kwown issues
## Known issues
- Storybook uses iframes to render the components. This means that the tooltip box will overlap in the demos, but you can click on each demo page to see the correct render.
- The current implementation of this component is <strong>NOT</strong> protected from viewport
Expand Down

0 comments on commit d82b3ee

Please sign in to comment.