diff --git a/packages/react-components/src/App.tsx b/packages/react-components/src/App.tsx index c52b11ac..a53ff93f 100644 --- a/packages/react-components/src/App.tsx +++ b/packages/react-components/src/App.tsx @@ -11,9 +11,10 @@ import { InlineAlertPage, SelectPage, TagGroupPage, - TooltipPage, TextAreaPage, TextFieldPage, + SwitchPage, + TooltipPage, } from "@/pages"; // This icon is available as a plain SVG at src/assets/icon-menu.svg @@ -147,6 +148,7 @@ function App() {

Components

+ diff --git a/packages/react-components/src/components/Switch/Switch.css b/packages/react-components/src/components/Switch/Switch.css new file mode 100644 index 00000000..dc250580 --- /dev/null +++ b/packages/react-components/src/components/Switch/Switch.css @@ -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; +} diff --git a/packages/react-components/src/components/Switch/Switch.tsx b/packages/react-components/src/components/Switch/Switch.tsx new file mode 100644 index 00000000..e982cc7f --- /dev/null +++ b/packages/react-components/src/components/Switch/Switch.tsx @@ -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 ( + + {labelPosition === "left" && <>{children}} +
+ {labelPosition === "right" && <>{children}} + + ); +} diff --git a/packages/react-components/src/components/Switch/index.ts b/packages/react-components/src/components/Switch/index.ts new file mode 100644 index 00000000..35c1e17b --- /dev/null +++ b/packages/react-components/src/components/Switch/index.ts @@ -0,0 +1,2 @@ +export { default } from "./Switch"; +export type { SwitchProps } from "./Switch"; diff --git a/packages/react-components/src/components/index.ts b/packages/react-components/src/components/index.ts index a2714636..b4bd744e 100644 --- a/packages/react-components/src/components/index.ts +++ b/packages/react-components/src/components/index.ts @@ -17,4 +17,5 @@ export { default as TagGroup } from "./TagGroup"; export { default as TagList } from "./TagList"; export { default as TextArea } from "./TextArea"; export { default as TextField } from "./TextField"; +export { default as Switch } from "./Switch"; export { default as Tooltip, TooltipTrigger } from "./Tooltip"; diff --git a/packages/react-components/src/pages/Switch/Switch.tsx b/packages/react-components/src/pages/Switch/Switch.tsx new file mode 100644 index 00000000..b4e00313 --- /dev/null +++ b/packages/react-components/src/pages/Switch/Switch.tsx @@ -0,0 +1,23 @@ +import { Switch } from "@/components"; + +export default function SwitchPage() { + return ( + <> +

Switch

+
+ Label + Reversed label position + Disabled switch + + Switch on by default + +
+ + ); +} diff --git a/packages/react-components/src/pages/Switch/index.ts b/packages/react-components/src/pages/Switch/index.ts new file mode 100644 index 00000000..2fb72e5b --- /dev/null +++ b/packages/react-components/src/pages/Switch/index.ts @@ -0,0 +1,3 @@ +import SwitchPage from "./Switch"; + +export default SwitchPage; diff --git a/packages/react-components/src/pages/index.ts b/packages/react-components/src/pages/index.ts index 7f18be03..e5b630d0 100644 --- a/packages/react-components/src/pages/index.ts +++ b/packages/react-components/src/pages/index.ts @@ -4,6 +4,7 @@ import SelectPage from "./Select"; import TagGroupPage from "./TagGroup"; import TextAreaPage from "./TextArea"; import TextFieldPage from "./TextField"; +import SwitchPage from "./Switch"; import TooltipPage from "./Tooltip"; export { @@ -13,5 +14,6 @@ export { TagGroupPage, TextAreaPage, TextFieldPage, + SwitchPage, TooltipPage, }; diff --git a/packages/react-components/src/stories/Switch.mdx b/packages/react-components/src/stories/Switch.mdx new file mode 100644 index 00000000..843508bd --- /dev/null +++ b/packages/react-components/src/stories/Switch.mdx @@ -0,0 +1,68 @@ +{/* Switch.mdx */} + +import { + Canvas, + Controls, + Meta, + Primary, + Source, + Story, + Subtitle, +} from "@storybook/blocks"; + +import * as SwitchStories from "./Switch.stories"; + + + +# Switch + +A switch enables the user to toggle a setting on or off. + + + +## 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 `` with the [ARIA switch role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/switch_role). + +## Controls + + + + +## Configuration + +### Label + +Pass a text label to the `children` slot. This text will render as a `