Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/components/Toggle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ import { BBBToggle } from 'bbb-ui-components-react';
<BBBToggle label="Enable Notifications" helperText="This is a toggle to enable the notifications." textPosition="left" />
```

### Full-width toggle with the label at the start and the switch at the end
```jsx
import { BBBToggle } from 'bbb-ui-components-react';

<BBBToggle label="Join muted" helperText="Joining users are muted by default" textPosition="left" fullWidth />
```

### Toggle with label and helper text on the top
```jsx
import { BBBToggle } from 'bbb-ui-components-react';
Expand All @@ -41,6 +48,7 @@ import { BBBToggle } from 'bbb-ui-components-react';
| `label` | `string` | | The main label text displayed next to the toggle. |
| `helperText` | `string` | | The helper text displayed below the label. |
| `textPosition` | `keyof typeof TEXT_POSITIONS` | `'right'` | The position of the text labels relative to the toggle switch. |
| `fullWidth` | `boolean` | `false` | If `true`, the toggle stretches to its container, putting the text and the switch at opposite ends. Only used with `textPosition` `'left'` (text at the start) or `'right'` (text at the end). |
| `disabled` | `boolean` | `false` | If `true`, the toggle will be disabled and unresponsive. |
| `onChange` | `(event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => void` | | Callback function that is fired when the toggle state changes. |
| `ariaLabel` | `string` | | The accessible name for the toggle. |
Expand Down
25 changes: 25 additions & 0 deletions src/components/Toggle/component.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import BBBToggle from './component';
import { TEXT_POSITIONS_VALUES, DEFAULT_TEXT_POSITION } from './constants';
Expand All @@ -13,6 +14,11 @@ const meta = {
description: 'Position of label/helper relative to the toggle.',
table: { defaultValue: { summary: `${DEFAULT_TEXT_POSITION}` } },
},
fullWidth: {
control: 'boolean',
description: `Stretches the row to its container, putting the text and the switch at opposite ends. Only used with textPosition 'left' or 'right'.`,
table: { defaultValue: { summary: 'false' } },
},
label: {
control: 'text',
description: 'Main label text displayed next to the toggle.',
Expand Down Expand Up @@ -43,3 +49,22 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { args: { label: 'Enable feature' } };
/** Adds helper text below the label, with the label/helper positioned to the right of the toggle. */
export const WithHelper: Story = { args: { label: 'Enable', helperText: 'Recommended', textPosition: 'right' } };
/** Shows a `fullWidth` toggle in a settings row: label and helper text at the start, switch at the end. */
export const FullWidth: Story = {
args: {
label: 'Join muted',
helperText: 'Joining users are muted by default',
textPosition: 'left',
fullWidth: true,
},
render: (args) => (
<div style={{ width: '24rem' }}>
<BBBToggle {...args} />
</div>
),
};
/** Shows a `fullWidth` toggle with the text on the right: switch at the start, label and helper text at the end. */
export const FullWidthTextRight: Story = {
...FullWidth,
args: { ...FullWidth.args, textPosition: 'right' },
};
7 changes: 5 additions & 2 deletions src/components/Toggle/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { JSX, useId } from 'react';
import { SwitchOwnerState } from '@mui/material';
import { ToggleProps } from './types';
import * as Styled from './styles';
import { DEFAULT_TEXT_POSITION } from './constants';
import { DEFAULT_TEXT_POSITION, TEXT_POSITIONS } from './constants';

/**
* A customizable Toggle switch component.
Expand All @@ -15,6 +15,7 @@ function Toggle ({
label,
helperText,
textPosition = DEFAULT_TEXT_POSITION,
fullWidth = false,
onChange,
ariaLabel,
ariaLabelledBy,
Expand All @@ -27,6 +28,7 @@ function Toggle ({
const id = providedId ?? generatedUniqueId;
const labelId = label ? `${id}-label` : undefined;
const helperTextId = helperText ? `${id}-helper` : undefined;
const isFullWidth = fullWidth && (textPosition === TEXT_POSITIONS.LEFT || textPosition === TEXT_POSITIONS.RIGHT);

const ariaLabelledByValue = ariaLabel ? undefined : ariaLabelledBy || labelId;
// Without a name source, the <label htmlFor> names the input from the helper text,
Expand All @@ -44,10 +46,11 @@ function Toggle ({
: { ...a11yInputProps, ...consumerInputSlotProps };

return (
<Styled.ToggleWrapper $textPosition={textPosition}>
<Styled.ToggleWrapper $textPosition={textPosition} $fullWidth={isFullWidth}>
{(label || helperText) && (
<Styled.TextWrapper
$textPosition={textPosition}
$fullWidth={isFullWidth}
htmlFor={id}
$hasLabel={!!label}
$hasHelperText={!!helperText}
Expand Down
15 changes: 13 additions & 2 deletions src/components/Toggle/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export const ToggleWrapper = styled.div<StyledToggleWrapperProps>`
}
return horizontalStyles;
}};
${({ $fullWidth }) => $fullWidth && css`
width: 100%;
justify-content: space-between;
`}
`;

export const ToggleSwitchWrapper = styled.div`
Expand All @@ -51,13 +55,20 @@ export const TextWrapper = styled.label<StyledTextWrapperProps>`
}};
cursor: pointer;
${({ $hasLabel, $hasHelperText }) => (!$hasLabel || !$hasHelperText) && `justify-content: center;`}
${({ $textPosition }) => {
${({ $textPosition, $fullWidth }) => {
if ($textPosition === TEXT_POSITIONS.TOP || $textPosition === TEXT_POSITIONS.BOTTOM) {
return css`
align-items: center;
`;
}
if ($textPosition === TEXT_POSITIONS.LEFT) return css`align-items: end;`;
// A full-width row aligns the text to the container's edge; otherwise it hugs the switch.
if ($fullWidth && $textPosition === TEXT_POSITIONS.RIGHT) {
return css`
align-items: end;
text-align: end;
`;
}
if ($textPosition === TEXT_POSITIONS.LEFT && !$fullWidth) return css`align-items: end;`;

return css`
align-items: start;
Expand Down
4 changes: 4 additions & 0 deletions src/components/Toggle/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type LabelPlacement = 'top' | 'bottom' | 'start' | 'end';

export interface StyledToggleWrapperProps {
$textPosition: LayoutType;
$fullWidth: boolean;
}

export interface StyledTextWrapperProps extends StyledToggleWrapperProps{
Expand All @@ -24,6 +25,9 @@ export interface ToggleProps extends Omit<SwitchProps, 'inputProps'> {
/** Position of the label/helper text relative to the toggle. @default 'right' */
textPosition?: LayoutType;

/** Stretches the row to its container, putting the text and the switch at opposite ends; only used with `textPosition` 'left' or 'right'. @default false */
fullWidth?: boolean;

/** Accessible name for the toggle. */
ariaLabel?: string;

Expand Down
Loading