-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add switch component #440
Merged
Merged
Add switch component #440
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e1098a9
component setup
mkernohanbc 2a2521d
roughing out
mkernohanbc 263e63d
rename ToggleButton to Switch
mkernohanbc 926a1d6
first pass on styling
mkernohanbc 6652508
styling and storybook expansion
mkernohanbc 9525c3d
docs and stories
mkernohanbc a0f6be6
clean up SwitchPage on vite
mkernohanbc 9fee80e
fix hover styling
mkernohanbc 87d6da6
Merge branch 'main' into feature/switch
mkernohanbc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
90 changes: 90 additions & 0 deletions
90
packages/react-components/src/components/Switch/Switch.css
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,90 @@ | ||
.bcds-react-aria-Switch { | ||
display: flex; | ||
align-items: center; | ||
gap: var(--layout-margin-small); | ||
font: var(--typography-regular-small-body); | ||
color: var(--typography-color-secondary); | ||
forced-color-adjust: none; | ||
} | ||
|
||
/* Container */ | ||
.bcds-react-aria-Switch > .indicator { | ||
align-items: center; | ||
width: var(--layout-margin-xxlarge); | ||
height: var(--icons-size-medium); | ||
background-color: var(--surface-color-forms-disabled); | ||
border-radius: var(--icons-size-medium); | ||
transition: all 200ms; | ||
} | ||
|
||
/* Indicator */ | ||
.bcds-react-aria-Switch > .indicator::before { | ||
content: ""; | ||
display: block; | ||
box-sizing: border-box; | ||
width: var(--icons-size-medium); | ||
height: var(--icons-size-medium); | ||
background-color: var(--surface-color-background-light-gray); | ||
border: var(--layout-border-width-medium) solid | ||
var(--surface-color-border-medium); | ||
border-radius: var(--icons-size-small); | ||
transition: all 200ms; | ||
} | ||
|
||
/* Selected */ | ||
.bcds-react-aria-Switch[data-selected] > .indicator { | ||
background-color: var(--surface-color-primary-button-default); | ||
} | ||
|
||
.bcds-react-aria-Switch[data-selected] > .indicator::before { | ||
transform: translateX(100%); | ||
border: var(--layout-border-width-medium) solid | ||
var(--surface-color-primary-button-default); | ||
} | ||
|
||
/* Hover */ | ||
.bcds-react-aria-Switch[data-hovered] > .indicator { | ||
cursor: pointer; | ||
} | ||
|
||
.bcds-react-aria-Switch[data-hovered][data-selected] > .indicator { | ||
cursor: pointer; | ||
background-color: var(--surface-color-primary-button-hover); | ||
} | ||
|
||
.bcds-react-aria-Switch[data-hovered] > .indicator::before { | ||
border: var(--layout-border-width-medium) solid | ||
var(--surface-color-border-dark); | ||
} | ||
|
||
.bcds-react-aria-Switch[data-hovered][data-selected] > .indicator::before { | ||
border: var(--layout-border-width-medium) solid | ||
var(--surface-color-primary-button-default); | ||
} | ||
|
||
/* Focused */ | ||
.bcds-react-aria-Switch[data-focus-visible] > .indicator { | ||
outline: solid var(--layout-border-width-medium) | ||
var(--surface-color-border-active); | ||
outline-offset: var(--layout-margin-hair); | ||
} | ||
|
||
/* Disabled */ | ||
.bcds-react-aria-Switch[data-disabled] { | ||
color: var(--typography-color-disabled); | ||
} | ||
|
||
.bcds-react-aria-Switch[data-disabled] > .indicator { | ||
background-color: var(--surface-color-forms-disabled); | ||
cursor: not-allowed; | ||
} | ||
|
||
.bcds-react-aria-Switch[data-disabled] > .indicator::before { | ||
border: var(--layout-border-width-medium) solid | ||
var(--surface-color-border-default); | ||
} | ||
|
||
/* Read only */ | ||
.bcds-react-aria-Switch[data-readonly] > .indicator { | ||
cursor: not-allowed; | ||
} |
26 changes: 26 additions & 0 deletions
26
packages/react-components/src/components/Switch/Switch.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,26 @@ | ||
import { | ||
Switch as ReactAriaSwitch, | ||
SwitchProps as ReactAriaSwitchProps, | ||
} from "react-aria-components"; | ||
|
||
import "./Switch.css"; | ||
|
||
export interface SwitchProps extends ReactAriaSwitchProps { | ||
children?: React.ReactNode; | ||
/* Label positioning relative to switch */ | ||
labelPosition?: "left" | "right"; | ||
} | ||
|
||
export default function Switch({ | ||
labelPosition = "right", | ||
children, | ||
...props | ||
}: SwitchProps) { | ||
return ( | ||
<ReactAriaSwitch className={`bcds-react-aria-Switch`} {...props}> | ||
{labelPosition === "left" && <>{children}</>} | ||
<div className="indicator" /> | ||
{labelPosition === "right" && <>{children}</>} | ||
</ReactAriaSwitch> | ||
); | ||
} |
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,2 @@ | ||
export { default } from "./Switch"; | ||
export type { SwitchProps } from "./Switch"; |
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
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,23 @@ | ||
import { Switch } from "@/components"; | ||
|
||
export default function SwitchPage() { | ||
return ( | ||
<> | ||
<h2>Switch</h2> | ||
<div | ||
style={{ | ||
display: "flex", | ||
flexDirection: "column", | ||
gap: "var(--layout-margin-medium", | ||
}} | ||
> | ||
<Switch>Label</Switch> | ||
<Switch labelPosition="left">Reversed label position</Switch> | ||
<Switch isDisabled>Disabled switch</Switch> | ||
<Switch labelPosition="left" defaultSelected> | ||
Switch on by default | ||
</Switch> | ||
</div> | ||
</> | ||
); | ||
} |
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,3 @@ | ||
import SwitchPage from "./Switch"; | ||
|
||
export default SwitchPage; |
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
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,68 @@ | ||
{/* Switch.mdx */} | ||
|
||
import { | ||
Canvas, | ||
Controls, | ||
Meta, | ||
Primary, | ||
Source, | ||
Story, | ||
Subtitle, | ||
} from "@storybook/blocks"; | ||
|
||
import * as SwitchStories from "./Switch.stories"; | ||
|
||
<Meta of={SwitchStories} /> | ||
|
||
# Switch | ||
|
||
<Subtitle>A switch enables the user to toggle a setting on or off.</Subtitle> | ||
|
||
<Source | ||
code={`import { Switch } from "@bcgov/design-system-react-components"; `} | ||
language="typescript" | ||
/> | ||
|
||
## Usage and resources | ||
|
||
Learn more about working with the switch component: | ||
|
||
- [Usage and best practice guidance](https://www2.gov.bc.ca/gov/content?id=A2B9EFCADAC940AA8E7A0A7E91D49097) | ||
- [View the switch component in Figma](https://www2.gov.bc.ca/gov/content?id=8E36BE1D10E04A17B0CD4D913FA7AC43#designers) | ||
|
||
This component is based on [React Aria Switch](https://react-spectrum.adobe.com/react-aria/Switch.html). It renders an `<input>` with the [ARIA switch role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/switch_role). | ||
|
||
## Controls | ||
|
||
<Primary of={SwitchStories} /> | ||
<Controls of={SwitchStories.SwitchTemplate} /> | ||
|
||
## Configuration | ||
|
||
### Label | ||
|
||
Pass a text label to the `children` slot. This text will render as a `<label>`, and will be automatically associated to the switch. | ||
|
||
If you do not provide a visible label, you must use the `aria-label` or `aria-labelledby` props to label the switch for assistive technologies. | ||
|
||
#### Label position | ||
|
||
By default, the text label renders to the right of the switch. Use the `labelPosition` prop to move the label to the left: | ||
|
||
<Canvas of={SwitchStories.LabelReversed} /> | ||
|
||
### States | ||
|
||
You can use the `isSelected` prop to make a switch controlled. You can also use the `name` and `value` props to integrate a switch into an HTML form. Consult [the React Aria documentation](https://react-spectrum.adobe.com/react-aria/Switch.html#value) for more information. | ||
|
||
By default, a switch is not selected. Pass `defaultSelected` to make a switch selected by default: | ||
|
||
<Canvas of={SwitchStories.DefaultSelectedSwitch} /> | ||
|
||
Use `isDisabled` to disable a switch. A disabled switch cannot be focused or selected: | ||
|
||
<Canvas of={SwitchStories.DisabledSwitch} /> | ||
|
||
Use `isReadOnly` to lock a switch to its current value. A read-only switch can be focused, but cannot be selected: | ||
|
||
<Canvas of={SwitchStories.ReadOnlySwitch} /> |
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,79 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
|
||
import { Switch } from "../components"; | ||
import { SwitchProps } from "@/components/Switch"; | ||
|
||
const meta = { | ||
title: "Components/Switch/Switch", | ||
component: Switch, | ||
parameters: { | ||
layout: "centered", | ||
}, | ||
argTypes: { | ||
labelPosition: { | ||
options: ["left", "right"], | ||
control: { type: "radio" }, | ||
description: "Sets the position of the text label", | ||
}, | ||
isSelected: { | ||
control: "boolean", | ||
description: "Whether a switch is currently selected", | ||
}, | ||
isDisabled: { | ||
control: "boolean", | ||
description: "Disables the switch", | ||
}, | ||
isReadOnly: { | ||
control: "boolean", | ||
description: "Sets the switch to read-only", | ||
}, | ||
defaultSelected: { | ||
control: "boolean", | ||
description: "Sets the switch to 'on' by default", | ||
}, | ||
}, | ||
} satisfies Meta<typeof Switch>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const SwitchTemplate: Story = { | ||
args: { | ||
children: "Label text", | ||
labelPosition: "right", | ||
}, | ||
render: ({ ...args }: SwitchProps) => <Switch {...args} />, | ||
}; | ||
|
||
export const LabelReversed: Story = { | ||
...SwitchTemplate, | ||
args: { | ||
children: "Label position reversed", | ||
labelPosition: "left", | ||
}, | ||
}; | ||
|
||
export const DefaultSelectedSwitch: Story = { | ||
...SwitchTemplate, | ||
args: { | ||
children: "This switch is selected by default", | ||
defaultSelected: true, | ||
}, | ||
}; | ||
|
||
export const DisabledSwitch: Story = { | ||
...SwitchTemplate, | ||
args: { | ||
children: "Disabled switch", | ||
isDisabled: true, | ||
}, | ||
}; | ||
|
||
export const ReadOnlySwitch: Story = { | ||
...SwitchTemplate, | ||
args: { | ||
children: "Read-only switch", | ||
isSelected: true, | ||
isReadOnly: true, | ||
}, | ||
}; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ty2k this pair of conditionals (lines 21 and 23) for positioning the label feels like a pretty inelegant solution, if you have a better way to do this would love to hear it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could switch the flex direction of the thing like
flex-direction: row
andflex-direction: row-reverse
based on thatlabelPosition
prop, but this is probably the more clear solution. We can always complicate the heck out of this when we have to make all these components work forrtl
instead of justltr
some day. :)