Skip to content

Commit

Permalink
feat(toggle): allow to be labelled by another element (#12974)
Browse files Browse the repository at this point in the history
* feat(toggle): allow to be labelled by another element

* fix(toggle): don't render label element when no labelText is provided
  • Loading branch information
janhassel authored Jan 19, 2023
1 parent b568e8a commit d620d10
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 22 deletions.
8 changes: 4 additions & 4 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8498,6 +8498,9 @@ Map {
},
"Toggle" => Object {
"propTypes": Object {
"aria-labelledby": Object {
"type": "string",
},
"className": Object {
"type": "string",
},
Expand All @@ -8520,10 +8523,7 @@ Map {
"labelB": Object {
"type": "node",
},
"labelText": Object {
"isRequired": true,
"type": "node",
},
"labelText": [Function],
"onClick": Object {
"type": "func",
},
Expand Down
22 changes: 20 additions & 2 deletions packages/react/src/components/Toggle/Toggle-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,28 @@ describe('Toggle', () => {
.classList.contains(`${prefix}--visually-hidden`)
).toBe(true);
expect(
wrapper.container.querySelector(`.${prefix}--toggle__label-text`)
.textContent
wrapper.container.querySelector(`.${prefix}--toggle__text`).textContent
).toBe(props.labelText);
});

it("doesn't render sideLabel if props.hideLabel and props['aria-labelledby'] are provided", () => {
const externalElementId = 'external-element-id';
wrapper.rerender(
<Toggle {...props} hideLabel aria-labelledby={externalElementId} />
);

expect(
wrapper.container.querySelector(`.${prefix}--toggle__text`)
).toBeNull();

expect(wrapper.getByRole('switch').getAttribute('aria-labelledby')).toBe(
externalElementId
);

expect(
wrapper.container.querySelector(`.${prefix}--toggle__label`).tagName
).toBe('DIV');
});
});

describe('behaves as expected', () => {
Expand Down
37 changes: 29 additions & 8 deletions packages/react/src/components/Toggle/Toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useControllableState } from '../../internal/useControllableState';
import { usePrefix } from '../../internal/usePrefix';

export function Toggle({
'aria-labelledby': ariaLabelledby,
className,
defaultToggled = false,
disabled = false,
Expand Down Expand Up @@ -45,6 +46,8 @@ export function Toggle({

const isSm = size === 'sm';
const sideLabel = hideLabel ? labelText : checked ? labelB : labelA;
const renderSideLabel = !(hideLabel && ariaLabelledby);
const LabelComponent = ariaLabelledby ? 'div' : 'label';

const wrapperClasses = classNames(
`${prefix}--toggle`,
Expand Down Expand Up @@ -76,11 +79,14 @@ export function Toggle({
role="switch"
type="button"
aria-checked={checked}
aria-labelledby={ariaLabelledby}
disabled={disabled}
onClick={handleClick}
/>
<label htmlFor={id} className={`${prefix}--toggle__label`}>
<span className={labelTextClasses}>{labelText}</span>
<LabelComponent
htmlFor={ariaLabelledby ? null : id}
className={`${prefix}--toggle__label`}>
{labelText && <span className={labelTextClasses}>{labelText}</span>}
<div className={appearanceClasses}>
<div className={switchClasses}>
{isSm && (
Expand All @@ -93,16 +99,23 @@ export function Toggle({
</svg>
)}
</div>
<span className={`${prefix}--toggle__text`} aria-hidden="true">
{sideLabel}
</span>
{renderSideLabel && (
<span className={`${prefix}--toggle__text`} aria-hidden="true">
{sideLabel}
</span>
)}
</div>
</label>
</LabelComponent>
</div>
);
}

Toggle.propTypes = {
/**
* Specify another element's id to be used as the label for this toggle
*/
'aria-labelledby': PropTypes.string,

/**
* Specify a custom className to apply to the form-item node
*/
Expand Down Expand Up @@ -140,9 +153,17 @@ Toggle.propTypes = {

/**
* Provide the text that will be read by a screen reader when visiting this
* control
* control. This is required unless 'aria-labelledby' is provided instead
*/
labelText: PropTypes.node.isRequired,
labelText: (props, ...rest) => {
if (!props['aria-labelledby'] && !props.labelText) {
return new Error(
'labelText property is required if no aria-labelledby is provided.'
);
}

return PropTypes.node(props, ...rest);
},

/**
* Provide an event listener that is called when the control is clicked
Expand Down
9 changes: 1 addition & 8 deletions packages/react/src/components/Toggle/Toggle.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,7 @@ export const SmallToggle = () => (
);

export const Playground = (args) => (
<Toggle
labelText="Toggle element label"
labelA="Off"
labelB="On"
defaultToggled
id="toggle-1"
{...args}
/>
<Toggle labelA="Off" labelB="On" defaultToggled id="toggle-1" {...args} />
);

Playground.argTypes = {
Expand Down

0 comments on commit d620d10

Please sign in to comment.