diff --git a/frontend/src/metabase/ui/components/inputs/Switch/Switch.stories.mdx b/frontend/src/metabase/ui/components/inputs/Switch/Switch.stories.mdx new file mode 100644 index 0000000000000000000000000000000000000000..b8a3122cc475ff0cd88a653fc441db7418c271c2 --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/Switch/Switch.stories.mdx @@ -0,0 +1,118 @@ +import { Canvas, Story, Meta } from "@storybook/addon-docs"; + +import { Switch, Stack } from "metabase/ui"; + +export const args = { + labelPosition: "right", + label: "Eat all the cheese", + description: undefined, + size: "md", + disabled: false, +}; + +export const argTypes = { + labelPosition: { + control: { type: "inline-radio" }, + options: ["left", "right"], + }, + label: { + control: { type: "text" }, + }, + description: { + control: { type: "text" }, + }, + size: { + control: { type: "inline-radio" }, + options: ["xs", "sm", "md"], + }, + disabled: { + control: { type: "boolean" }, + }, +}; + +<Meta + title="Inputs/Switch" + component={Switch} + args={args} + argTypes={argTypes} +/> + +# Switch + +Our themed wrapper around [Mantine Switch](https://mantine.dev/core/switch/). + +## When to use Switch + +Use Switch when you have a setting that can be only either on or off, or enabled/disabled. If there are multiple related options in a list that could be selected or chosen, use a checkbox group instead. + +## Docs + +- [Figma File](https://www.figma.com/file/xdNKMROC99J6Z4Sqg6V3JI/Input-%2F-Switch?type=design&node-id=1-96&mode=design&t=0K7GSP6rqj8M2muz-0) +- [Mantine Checkbox Docs](https://mantine.dev/core/switch/) + +## Usage guidelines + +In most situations you should use the `md` size variant, with the label on the left and the toggle on the right. (This allows users to read what the setting or option is first.) + +## Examples + +export const DefaultTemplate = args => <Switch {...args} />; + +export const StateTemplate = args => ( + <Stack> + <Switch {...args} label="Unchecked switch" checked={false} /> + <Switch {...args} label="Checked switch" checked /> + <Switch {...args} label="Disabled unchecked switch" disabled /> + <Switch {...args} label="Disabled checked switch" disabled checked /> + </Stack> +); + +export const Default = DefaultTemplate.bind({}); + +<Canvas> + <Story name="Default">{Default}</Story> +</Canvas> + +### Label + +export const Label = StateTemplate.bind({}); + +<Canvas> + <Story name="Label">{Label}</Story> +</Canvas> + +### Left label position + +export const LabelLeft = StateTemplate.bind({}); +LabelLeft.args = { + labelPosition: "left", +}; + +<Canvas> + <Story name="Label, left position" args={LabelLeft.args}> + {LabelLeft} + </Story> +</Canvas> + +### Description + +export const Description = StateTemplate.bind({}); +Description.args = { + description: "Every type of cheese will be consumed, regardless of stink.", +}; + +<Canvas> + <Story name="Description">{Description}</Story> +</Canvas> + +### Left description position + +export const DescriptionLeft = StateTemplate.bind({}); +DescriptionLeft.args = { + labelPosition: "left", + description: "Every type of cheese will be consumed, regardless of stink.", +}; + +<Canvas> + <Story name="Description, left position">{DescriptionLeft}</Story> +</Canvas> diff --git a/frontend/src/metabase/ui/components/inputs/Switch/Switch.styled.tsx b/frontend/src/metabase/ui/components/inputs/Switch/Switch.styled.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dedf2f604b9a554625278703f070f462989b1a94 --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/Switch/Switch.styled.tsx @@ -0,0 +1,120 @@ +import type { MantineThemeOverride, SwitchStylesParams } from "@mantine/core"; +import { rem, getSize, getStylesRef } from "@mantine/core"; + +import { color } from "metabase/lib/colors"; + +const LABEL_FONT_SIZES = { + xs: rem(12), + sm: rem(14), + md: rem(16), +}; + +const LABEL_LINE_HEIGHT = { + xs: rem(16), + sm: rem(24), + md: rem(24), +}; + +const SWITCH_PADDING = { + xs: rem(8), + sm: rem(8), + md: rem(16), +}; + +const TRACK_HEIGHTS = { + xs: rem(16), + sm: rem(20), + md: rem(24), +}; + +const TRACK_WIDTHS = { + xs: rem(32), + sm: rem(40), + md: rem(48), + lg: rem(64), +}; + +const THUMB_SIZES = { + xs: rem(12), + sm: rem(14), + md: rem(18), +}; + +const TRACK_PADDING_TOP = { + xs: rem(0), + sm: rem(2), + md: rem(0), +}; + +export const getSwitchOverrides = (): MantineThemeOverride["components"] => ({ + Switch: { + defaultProps: { + color: "brand", + size: "md", + }, + styles: ( + theme, + { error, labelPosition }: SwitchStylesParams, + { size = "md" }, + ) => { + return { + labelWrapper: { + [labelPosition === "left" ? "paddingRight" : "paddingLeft"]: getSize({ + size, + sizes: SWITCH_PADDING, + }), + "&:empty": { + padding: 0, + }, + }, + label: { + padding: 0, + fontWeight: 700, + fontSize: getSize({ size, sizes: LABEL_FONT_SIZES }), + lineHeight: getSize({ size, sizes: LABEL_LINE_HEIGHT }), + color: theme.colors.text[2], + cursor: "pointer", + "&[data-disabled]": { + color: theme.colors.text[0], + cursor: "default", + }, + }, + description: { + padding: 0, + marginTop: rem(8), + fontSize: rem(12), + color: theme.colors.text[1], + }, + error: { + padding: 0, + marginTop: rem(8), + fontSize: rem(12), + color: theme.colors.error[0], + }, + track: { + backgroundColor: theme.colors.bg[1], + border: error ? `1px solid ${color("accent3")}` : "none", + boxSizing: "border-box", + borderRadius: rem(24), + height: getSize({ size, sizes: TRACK_HEIGHTS }), + width: getSize({ size, sizes: TRACK_WIDTHS }), + cursor: "pointer", + [`${getStylesRef("input")}:disabled + &`]: { + backgroundColor: theme.colors.bg[1], + }, + marginTop: getSize({ size, sizes: TRACK_PADDING_TOP }), + }, + thumb: { + backgroundColor: theme.white, + border: "none", + borderRadius: rem(22), + height: getSize({ size, sizes: THUMB_SIZES }), + width: getSize({ size, sizes: THUMB_SIZES }), + [`${getStylesRef("input")}:disabled + * > &`]: { + backgroundColor: theme.colors.bg[0], + }, + }, + }; + }, + }, +}); diff --git a/frontend/src/metabase/ui/components/inputs/Switch/index.ts b/frontend/src/metabase/ui/components/inputs/Switch/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e040ba4f9dd490784ece72bfb66a4ff84061348 --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/Switch/index.ts @@ -0,0 +1,3 @@ +export { Switch } from "@mantine/core"; +export type { SwitchProps, SwitchGroupProps } from "@mantine/core"; +export { getSwitchOverrides } from "./Switch.styled"; diff --git a/frontend/src/metabase/ui/components/inputs/index.ts b/frontend/src/metabase/ui/components/inputs/index.ts index b9e0952b33d4704e9dea8024791c2c2131881ce3..c277fe05c323a8d7bdd0d11f65cf0f03bb871f10 100644 --- a/frontend/src/metabase/ui/components/inputs/index.ts +++ b/frontend/src/metabase/ui/components/inputs/index.ts @@ -5,4 +5,5 @@ export * from "./NumberInput"; export * from "./Radio"; export * from "./Select"; export * from "./Textarea"; +export * from "./Switch"; export * from "./TextInput"; diff --git a/frontend/src/metabase/ui/theme.ts b/frontend/src/metabase/ui/theme.ts index 790e56d7917fd378a55cbf4177a2ffa82ff9ffd7..31463cbed368c15df5e0587336b00cef98d2358e 100644 --- a/frontend/src/metabase/ui/theme.ts +++ b/frontend/src/metabase/ui/theme.ts @@ -15,6 +15,7 @@ import { getPaperOverrides, getSelectOverrides, getTextareaOverrides, + getSwitchOverrides, getTextInputOverrides, getTextOverrides, getTitleOverrides, @@ -115,6 +116,7 @@ export const getThemeOverrides = (): MantineThemeOverride => ({ ...getPaperOverrides(), ...getSelectOverrides(), ...getTextareaOverrides(), + ...getSwitchOverrides(), ...getTextInputOverrides(), ...getTextOverrides(), ...getTitleOverrides(),