Skip to content
Snippets Groups Projects
Unverified Commit 167153ca authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Mantine TextInput and NumberInput (#33381)

parent b316c604
No related branches found
No related tags found
No related merge requests found
Showing
with 1307 additions and 240 deletions
......@@ -30,21 +30,16 @@ export const TypeFilter: SearchFilterComponent<"type"> = ({
<LoadingSpinner />
) : (
<SearchFilterView data-testid={dataTestId} title={t`Type`}>
<Checkbox.Group
value={value}
onChange={onChange}
data-testid="type-filter-checkbox-group"
inputContainer={children => (
<TypeCheckboxGroupWrapper>{children}</TypeCheckboxGroupWrapper>
)}
>
{typeFilters.map(model => (
<Checkbox
key={model}
value={model}
label={getTranslatedEntityName(model)}
/>
))}
<Checkbox.Group value={value} onChange={onChange}>
<TypeCheckboxGroupWrapper data-testid="type-filter-checkbox-group">
{typeFilters.map(model => (
<Checkbox
key={model}
value={model}
label={getTranslatedEntityName(model)}
/>
))}
</TypeCheckboxGroupWrapper>
</Checkbox.Group>
</SearchFilterView>
);
......
......@@ -69,7 +69,7 @@ Not to use:
## Docs
- [Figma File](https://www.figma.com/file/Ey1rOyIxRHpmRvE9XrGyop/Buttons-%2F-Button?type=design&node-id=1-96&mode=design&t=JaO1GZU7AnpAmx4o-0)
- [Figma File](https://www.figma.com/file/Ey1rOyIxRHpmRvE9XrGyop/Buttons-%2F-Button?type=design&node-id=1-96&mode=design&t=yaNljw178EFJeU7k-0)
- [Mantine Button Docs](https://mantine.dev/core/button/)
## Caveats
......
......@@ -19,7 +19,7 @@ export const getButtonOverrides = (): MantineThemeOverride["components"] => ({
height: "auto",
padding: compact ? `${rem(3)} ${rem(7)}` : `${rem(11)} ${rem(15)}`,
fontSize: theme.fontSizes.md,
lineHeight: "1rem",
lineHeight: theme.lineHeight,
[`&:has(.${getStylesRef("label")}:empty)`]: {
padding: compact ? `${rem(3)} ${rem(3)}` : `${rem(11)} ${rem(11)}`,
[`.${getStylesRef("leftIcon")}`]: {
......
......@@ -27,7 +27,7 @@ Our themed wrapper around [Mantine Loader](https://mantine.dev/core/loader/).
## Docs
- [Figma File](https://www.figma.com/file/NUXRUa9Ot3HvgC1WwIA4UH/Loader?node-id=1%3A96)
- [Figma File](https://www.figma.com/file/NUXRUa9Ot3HvgC1WwIA4UH/Loader?type=design&node-id=1-96&mode=design&t=yaNljw178EFJeU7k-0)
- [Mantine Loader Docs](https://mantine.dev/core/loader/)
## Caveats
......
import { Loader as MantineLoader } from "@mantine/core";
import { Loader as MantineLoader, getSize } from "@mantine/core";
import type { LoaderProps } from "@mantine/core";
const SIZES: Record<string, string> = {
......@@ -10,5 +10,5 @@ const SIZES: Record<string, string> = {
};
export const Loader = ({ size = "md", ...props }: LoaderProps) => (
<MantineLoader {...props} size={SIZES[size] ?? size} />
<MantineLoader {...props} size={getSize({ size, sizes: SIZES })} />
);
import { Canvas, Story, Meta } from "@storybook/addon-docs";
import { Checkbox } from "./";
<Meta title="Inputs/Checkbox & Checkbox Group" component={Checkbox}
argTypes={
{
size: {
options: ["xs", "sm", "md", "lg", "xl"],
control: { type: 'inline-radio' }
},
}
}
import { Checkbox, Stack } from "metabase/ui";
export const args = {
label: "Label",
description: "",
disabled: false,
labelPosition: "right",
};
export const argTypes = {
label: {
control: { type: "text" },
},
description: {
control: { type: "text" },
},
disabled: {
control: { type: "boolean" },
},
labelPosition: {
options: ["left", "right"],
control: { type: "inline-radio" },
},
};
<Meta
title="Inputs/Checkbox"
component={Checkbox}
args={args}
argTypes={argTypes}
/>
export const Template = args => <Checkbox.Group defaultValue={["yes"]} {...args}>
<Checkbox label="Yes" value="yes" />
<Checkbox label="No" value="no" />
<Checkbox label="I'm not sure" indeterminate />
</Checkbox.Group>
# Checkbox & Checkbox.Group
# Checkbox
Our themed wrapper around [Mantine Checkbox](https://mantine.dev/core/checkbox/).
......@@ -29,87 +41,110 @@ Checkbox buttons allow users to select a single option from a list of mutually e
## Docs
- [Figma File](https://www.figma.com/file/sF1qSHk6yVqO1rFgmH0nzT/Input-%2F-Checkbox?type=design&node-id=1-96&t=4851xqAoQVenSrTX-11)
- [Figma File](https://www.figma.com/file/sF1qSHk6yVqO1rFgmH0nzT/Input-%2F-Checkbox?type=design&node-id=1-96&mode=design&t=yaNljw178EFJeU7k-0)
- [Mantine Checkbox Docs](https://mantine.dev/core/checkbox/)
## Caveats
- Please don't use the `error` prop on Checkbox components (We'll standardize on this as other inputs get converted).
## Usage guidelines
- **Use this component if there are more than 5 options**. If there are fewer options, feel free to check out Checkbox or Select.
- For option ordering, try to use your best judgement on a sensible order. For example, Yes should come before No. Alphabetical ordering is usually a good fallback if there's no inherent order in your set of choices.
- In almost all circumstances you'll want to use `<Checkbox.Group>` to provide a set of options and help with defaultValues and state management between them.
## General
## Examples
export const Template = args => (
<Stack>
<Checkbox {...args} label="Default checkbox" />
<Checkbox {...args} label="Indeterminate checkbox" indeterminate />
<Checkbox
{...args}
label="Indeterminate checked checkbox"
checked
indeterminate
/>
<Checkbox {...args} label="Checked checkbox" checked />
<Checkbox {...args} label="Disabled checkbox" disabled />
<Checkbox
{...args}
label="Disabled intermediate checkbox"
disabled
intermediate
/>
<Checkbox
{...args}
label="Disabled indeterminate checked checkbox"
disabled
checked
indeterminate
/>
<Checkbox {...args} label="Disabled checked checkbox" disabled checked />
</Stack>
);
export const Default = args => <Checkbox {...args} />;
<Canvas>
<Story name="Default">{Default}</Story>
</Canvas>
### Checkbox.Group
export const CheckboxGroup = args => (
<Checkbox.Group
defaultValue={["react"]}
label="An array of good frameworks"
description="But which one to use?"
>
<Stack mt="md">
<Checkbox value="react" label="React" />
<Checkbox value="svelte" label="Svelte" />
<Checkbox value="ng" label="Angular" />
<Checkbox value="vue" label="Vue" />
</Stack>
</Checkbox.Group>
);
<Canvas>
<Story name="Default">
{Template.bind({})}
</Story>
<Story name="Checkbox group">{CheckboxGroup}</Story>
</Canvas>
## Disabled
### Label
export const Label = args => <Template {...args} />;
<Canvas>
<Story name="Label">{Label}</Story>
</Canvas>
#### Left label position
export const LabelLeft = args => <Template {...args} labelPosition="left" />;
<Canvas>
<Story name="Disabled">
<Checkbox.Group value={["yes"]}>
<Checkbox label="Yes" value="yes" disabled />
<Checkbox label="No" value="no" disabled />
<Checkbox label="I'm not sure" indeterminate disabled />
</Checkbox.Group>
</Story>
<Story name="Label, left position">{LabelLeft}</Story>
</Canvas>
## Description
### Description
If needed you can add a description value to a Checkbox button to give more context about the choice.
export const Description = args => (
<Template {...args} description="Description" />
);
<Canvas>
<Story name="Descriptions">
<Checkbox.Group defaultValue={["chocolate"]}>
<Checkbox
label="Chocolate"
value="chocolate"
description="One of our most popular flavors"
/>
<Checkbox
label="Vanilla"
value="vanilla"
description="A classic for all seasons"
/>
<Checkbox
label="Strawberry, no description needed!"
value="strawberry"
/>
<Checkbox
label="Swirl"
value="swirl"
description="Why not have it both ways?"
/>
</Checkbox.Group>
</Story>
<Story name="Description">{Description}</Story>
</Canvas>
## Using the Checkbox group props
export const DescriptionLeft = args => (
<Template {...args} description="Description" labelPosition="left" />
);
You can use `label` and `description` on the Checkbox.Group component to provide a label and description for the set of choices.
#### Left label position
<Canvas>
<Story name="Checkbox Group props">
<Checkbox.Group
label="A set of options"
description="You could so something like this if there was a deprecated setting that a user needs to update."
defaultValue={["one"]}
>
<Checkbox label="Old bad setting" value="one" disabled />
<Checkbox label="A better newer setting" value="two" />
</Checkbox.Group>
</Story>
<Story name="Description, left position">{DescriptionLeft}</Story>
</Canvas>
## Related components
- Radio
- Select
- Checkbox
import type { MantineTheme, MantineThemeOverride } from "@mantine/core";
import { getStylesRef, getSize, rem } from "@mantine/core";
import type {
CheckboxStylesParams,
MantineTheme,
MantineThemeOverride,
} from "@mantine/core";
import { CheckboxIcon } from "./CheckboxIcon";
const SIZES = {
md: rem(20),
};
export const getCheckboxOverrides = (): MantineThemeOverride["components"] => ({
Checkbox: {
defaultProps: {
icon: CheckboxIcon,
size: "md",
},
styles: (theme: MantineTheme, params) => {
return {
root: {
marginBottom: theme.spacing.md,
},
label: {
fontWeight: 700,
color: theme.colors.text[2],
[`padding${params.labelPosition === "left" ? "Right" : "Left"}`]:
theme.spacing.sm,
},
input: {
borderRadius: theme.radius.xs,
"&:focus": {
outline: `2px solid ${theme.colors.brand[1]}`,
styles: (
theme: MantineTheme,
{ labelPosition }: CheckboxStylesParams,
{ size = "md" },
) => ({
root: {
[`&:has(.${getStylesRef("input")}:disabled)`]: {
[`.${getStylesRef("label")}`]: {
color: theme.colors.text[0],
},
"&:disabled": {
background: theme.colors.border[0],
border: 0,
"& + svg > *": {
fill: theme.colors.text[0],
},
[`.${getStylesRef("description")}`]: {
color: theme.colors.text[0],
},
[`.${getStylesRef("icon")}`]: {
color: theme.colors.text[0],
},
cursor: "pointer",
...(params.indeterminate && {
background: theme.colors.brand[1],
border: `1px solid ${theme.colors.brand[1]}`,
}),
transform: `scale(0.75)`,
},
icon: {
...(params.indeterminate && {
"& > *": {
fill: theme.white,
},
}),
},
};
},
},
CheckboxGroup: {
defaultProps: {
size: "md",
},
styles: (theme: MantineTheme) => {
/* Note: we need the ':has' selector to target the space just
* above the first checkbox since we don't seem to have selector
* or a way to use params to detect whether group label/description
* exists. This is a bit of a hack, but it works. */
},
inner: {
width: getSize({ size, sizes: SIZES }),
height: getSize({ size, sizes: SIZES }),
},
input: {
ref: getStylesRef("input"),
width: getSize({ size, sizes: SIZES }),
height: getSize({ size, sizes: SIZES }),
cursor: "pointer",
borderRadius: theme.radius.xs,
return {
label: {
fontWeight: 700,
color: theme.colors.text[2],
"&:has(+ .mantine-Checkbox-root)": {
marginBottom: theme.spacing.md,
"&:checked": {
borderColor: theme.colors.brand[1],
backgroundColor: theme.colors.brand[1],
[`.${getStylesRef("icon")}`]: {
color: theme.white,
},
},
description: {
"&:has(+ .mantine-Checkbox-root)": {
marginBottom: theme.spacing.md,
},
"&:disabled": {
borderColor: theme.colors.border[0],
backgroundColor: theme.colors.border[0],
},
};
},
},
label: {
ref: getStylesRef("label"),
color: theme.colors.text[2],
fontSize: theme.fontSizes.md,
fontWeight: "bold",
lineHeight: "1rem",
},
description: {
ref: getStylesRef("description"),
color: theme.colors.text[2],
fontSize: theme.fontSizes.sm,
lineHeight: "1rem",
marginTop: theme.spacing.xs,
},
icon: {
ref: getStylesRef("icon"),
color: theme.colors.text[0],
},
}),
},
});
import { getSize, rem } from "@mantine/core";
import type { InputStylesParams, MantineThemeOverride } from "@mantine/core";
const SIZES = {
xs: rem(28),
md: rem(40),
};
const PADDING = 12;
const DEFAULT_ICON_WIDTH = 40;
const UNSTYLED_ICON_WIDTH = 28;
const BORDER_WIDTH = 1;
export const getInputOverrides = (): MantineThemeOverride["components"] => ({
Input: {
defaultProps: {
size: "md",
},
styles: (theme, { multiline }: InputStylesParams, { size = "md" }) => ({
input: {
color: theme.colors.text[2],
borderRadius: theme.radius.xs,
height: multiline ? "auto" : getSize({ size, sizes: SIZES }),
minHeight: getSize({ size, sizes: SIZES }),
"&::placeholder": {
color: theme.colors.text[0],
},
"&:disabled": {
backgroundColor: theme.colors.bg[0],
},
"&[data-invalid]": {
color: theme.colors.error[0],
borderColor: theme.colors.error[0],
"&::placeholder": {
color: theme.colors.error[0],
},
},
},
icon: {
color: theme.colors.text[2],
},
rightSection: {
color: theme.colors.text[0],
},
}),
sizes: {
xs: theme => ({
input: {
fontSize: theme.fontSizes.sm,
lineHeight: theme.lineHeight,
},
}),
md: theme => ({
input: {
fontSize: theme.fontSizes.md,
lineHeight: rem(24),
},
}),
},
variants: {
default: (
theme,
{ withRightSection, rightSectionWidth }: InputStylesParams,
) => ({
input: {
paddingLeft: rem(PADDING - BORDER_WIDTH),
paddingRight: withRightSection
? typeof rightSectionWidth === "number"
? rem(rightSectionWidth - BORDER_WIDTH)
: `calc(${rightSectionWidth} - ${BORDER_WIDTH}px)`
: rem(PADDING - BORDER_WIDTH),
borderColor: theme.colors.border[0],
"&:focus": {
borderColor: theme.colors.brand[1],
},
"&:read-only:not(:disabled)": {
borderColor: theme.colors.text[0],
},
"&[data-with-icon]": {
paddingLeft: rem(DEFAULT_ICON_WIDTH - BORDER_WIDTH),
},
},
icon: {
width: rem(DEFAULT_ICON_WIDTH),
},
rightSection: {
width: rightSectionWidth ?? rem(DEFAULT_ICON_WIDTH),
},
}),
unstyled: (
theme,
{ withRightSection, rightSectionWidth }: InputStylesParams,
) => ({
input: {
paddingLeft: 0,
paddingRight: withRightSection ? rightSectionWidth : 0,
"&[data-with-icon]": {
paddingLeft: rem(UNSTYLED_ICON_WIDTH),
},
},
icon: {
width: rem(UNSTYLED_ICON_WIDTH),
justifyContent: "left",
},
rightSection: {
width: rightSectionWidth ?? rem(UNSTYLED_ICON_WIDTH),
justifyContent: "right",
},
}),
},
},
InputWrapper: {
defaultProps: {
size: "md",
inputWrapperOrder: ["label", "description", "error", "input"],
},
styles: theme => ({
label: {
color: theme.colors.text[2],
fontSize: theme.fontSizes.sm,
fontWeight: "bold",
lineHeight: "1rem",
},
description: {
color: theme.colors.text[2],
fontSize: theme.fontSizes.xs,
lineHeight: "1rem",
},
error: {
color: theme.colors.error[0],
fontSize: theme.fontSizes.xs,
lineHeight: "1rem",
},
required: {
color: theme.colors.error[0],
},
}),
},
});
export { getInputOverrides } from "./Input.styled";
import { Canvas, Story, Meta } from "@storybook/addon-docs";
import { Icon } from "metabase/core/components/Icon";
import { Stack } from "metabase/ui";
import { NumberInput } from "./";
export const args = {
variant: "default",
size: "md",
label: "Label",
description: "",
error: "",
placeholder: "Placeholder",
disabled: false,
withAsterisk: false,
min: undefined,
max: undefined,
step: undefined,
hideControls: true,
precision: undefined,
decimalSeparator: undefined,
};
export const sampleArgs = {
value: 1234,
label: "Goal value",
description: "Constant line added as a marker to the chart",
placeholder: "0",
error: "required",
min: 0,
max: 100,
};
export const argTypes = {
variant: {
options: ["default", "unstyled"],
control: { type: "inline-radio" },
},
size: {
options: ["xs", "md"],
control: { type: "inline-radio" },
},
label: {
control: { type: "number" },
},
description: {
control: { type: "number" },
},
placeholder: {
control: { type: "number" },
},
error: {
control: { type: "number" },
},
disabled: {
control: { type: "boolean" },
},
withAsterisk: {
control: { type: "boolean" },
},
min: {
control: { type: "number" },
},
max: {
control: { type: "number" },
},
step: {
control: { type: "number" },
},
hideControls: {
control: { type: "boolean" },
},
precision: {
control: { type: "number" },
},
decimalSeparator: {
control: { type: "text" },
},
};
<Meta
title="Inputs/NumberInput"
component={NumberInput}
args={args}
argTypes={argTypes}
/>
# NumberInput
Our themed wrapper around [Mantine NumberInput](https://mantine.dev/core/number-input/).
## Docs
- [Figma File](https://www.figma.com/file/YWyb541aUHtYXJVPzyYSbg/Input-%2F-Numbers?type=design&node-id=1-96&mode=design&t=1bDfUrJc5alZmVpx-0)
- [Mantine NumberInput Docs](https://mantine.dev/core/number-input/)
## Examples
export const Default = args => <NumberInput {...args} />;
export const Template = args => (
<Stack>
<NumberInput {...args} variant="default" />
<NumberInput {...args} variant="unstyled" />
</Stack>
);
export const Empty = args => (
<Template
{...args}
label={sampleArgs.label}
placeholder={sampleArgs.placeholder}
/>
);
export const Filled = args => (
<Template
{...args}
defaultValue={sampleArgs.value}
label={sampleArgs.label}
placeholder={sampleArgs.placeholder}
/>
);
export const Asterisk = args => (
<Template
{...args}
label={sampleArgs.label}
placeholder={sampleArgs.placeholder}
withAsterisk
/>
);
export const Description = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
/>
);
export const Disabled = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
disabled
withAsterisk
/>
);
export const Error = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
error={sampleArgs.error}
withAsterisk
/>
);
export const Icons = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
icon={<Icon name="int" />}
withAsterisk
/>
);
export const ReadOnly = args => (
<Template
{...args}
defaultValue={sampleArgs.value}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
icon={<Icon name="int" />}
readOnly
/>
);
export const MinMax = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={`${sampleArgs.min} to ${sampleArgs.max}`}
icon={<Icon name="int" />}
min={sampleArgs.min}
max={sampleArgs.max}
/>
);
export const Controls = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={`${sampleArgs.min} to ${sampleArgs.max}`}
icon={<Icon name="int" />}
min={sampleArgs.min}
max={sampleArgs.max}
hideControls={false}
/>
);
export const Precision = args => (
<Template
{...args}
defaultValue={sampleArgs.value}
label={sampleArgs.label}
description={sampleArgs.description}
icon={<Icon name="int" />}
precision={2}
decimalSeparator="."
/>
);
export const ParserFormatter = args => (
<Template
{...args}
defaultValue={sampleArgs.value}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
icon={<Icon name="int" />}
parser={value => value.replace(/^\$/, "")}
formatter={value => (value != null ? `$${value}` : "$")}
/>
);
<Canvas>
<Story name="Default">{Default}</Story>
</Canvas>
### Size - md
export const EmptyMd = args => <Empty {...args} size="md" />;
<Canvas>
<Story name="Empty, md">{EmptyMd}</Story>
</Canvas>
#### Filled
export const FilledMd = args => <Filled {...args} size="md" />;
<Canvas>
<Story name="Filled, md">{FilledMd}</Story>
</Canvas>
#### Asterisk
export const AsteriskMd = args => <Asterisk {...args} size="md" />;
<Canvas>
<Story name="Asterisk, md">{AsteriskMd}</Story>
</Canvas>
#### Description
export const DescriptionMd = args => <Description {...args} size="md" />;
<Canvas>
<Story name="Description, md">{DescriptionMd}</Story>
</Canvas>
#### Disabled
export const DisabledMd = args => <Disabled {...args} size="md" />;
<Canvas>
<Story name="Disabled, md">{Disabled}</Story>
</Canvas>
#### Error
export const ErrorMd = args => <Error {...args} size="md" />;
<Canvas>
<Story name="Error, md">{ErrorMd}</Story>
</Canvas>
#### Icon
export const IconMd = args => <Icons {...args} size="md" />;
<Canvas>
<Story name="Icon, md">{IconMd}</Story>
</Canvas>
#### Read only
export const ReadOnlyMd = args => <ReadOnly {...args} size="md" />;
<Canvas>
<Story name="Read only, md">{ReadOnlyMd}</Story>
</Canvas>
#### Min / max
export const MinMaxMd = args => <MinMax {...args} size="md" />;
<Canvas>
<Story name="Min / max, md">{MinMaxMd}</Story>
</Canvas>
#### Controls
export const ControlsMd = args => <Controls {...args} size="md" />;
<Canvas>
<Story name="Controls, md">{ControlsMd}</Story>
</Canvas>
#### Precision and decimal separator
export const PrecisionMd = args => <Precision {...args} size="md" />;
<Canvas>
<Story name="Precision and decimal separator, md">{PrecisionMd}</Story>
</Canvas>
#### Parser and formatter
export const ParserFormatterMd = args => (
<ParserFormatter {...args} size="md" />
);
<Canvas>
<Story name="Parser and formatter, md">{ParserFormatterMd}</Story>
</Canvas>
### Size - xs
export const EmptyXs = args => <Empty {...args} size="xs" />;
<Canvas>
<Story name="Empty, xs">{EmptyXs}</Story>
</Canvas>
#### Filled
export const FilledXs = args => <Filled {...args} size="xs" />;
<Canvas>
<Story name="Filled, xs">{FilledXs}</Story>
</Canvas>
#### Asterisk
export const AsteriskXs = args => <Asterisk {...args} size="xs" />;
<Canvas>
<Story name="Asterisk, xs">{AsteriskXs}</Story>
</Canvas>
#### Description
export const DescriptionXs = args => <Description {...args} size="xs" />;
<Canvas>
<Story name="Description, xs">{DescriptionXs}</Story>
</Canvas>
#### Disabled
export const DisabledXs = args => <Disabled {...args} size="xs" />;
<Canvas>
<Story name="Disabled, xs">{Disabled}</Story>
</Canvas>
#### Error
export const ErrorXs = args => <Error {...args} size="xs" />;
<Canvas>
<Story name="Error, xs">{ErrorXs}</Story>
</Canvas>
#### Icon
export const IconXs = args => <Icons {...args} size="xs" />;
<Canvas>
<Story name="Icon, xs">{IconXs}</Story>
</Canvas>
#### Read only
export const ReadOnlyXs = args => <ReadOnly {...args} size="xs" />;
<Canvas>
<Story name="Read only, xs">{ReadOnlyXs}</Story>
</Canvas>
#### Min / max
export const MinMaxXs = args => <MinMax {...args} size="xs" />;
<Canvas>
<Story name="Min / max, xs">{MinMaxXs}</Story>
</Canvas>
#### Controls
export const ControlsXs = args => <Controls {...args} size="xs" />;
<Canvas>
<Story name="Controls, xs">{ControlsXs}</Story>
</Canvas>
#### Precision and decimal separator
export const PrecisionXs = args => <Precision {...args} size="xs" />;
<Canvas>
<Story name="Precision and decimal separator, xs">{PrecisionXs}</Story>
</Canvas>
#### Parser and formatter
export const ParserFormatterXs = args => (
<ParserFormatter {...args} size="xs" />
);
<Canvas>
<Story name="Parser and formatter, xs">{ParserFormatterXs}</Story>
</Canvas>
import { getSize, rem } from "@mantine/core";
import type {
ContextStylesParams,
MantineThemeOverride,
NumberInputStylesParams,
} from "@mantine/core";
const CONTROL_SIZES = {
xs: rem(16),
md: rem(20),
};
export const getNumberInputOverrides =
(): MantineThemeOverride["components"] => ({
NumberInput: {
defaultProps: {
size: "md",
hideControls: true,
},
styles: (
theme,
params: NumberInputStylesParams,
{ size = "md" }: ContextStylesParams,
) => ({
wrapper: {
marginTop: theme.spacing.xs,
},
control: {
color: theme.colors.text[2],
width: getSize({ size, sizes: CONTROL_SIZES }),
borderColor: theme.colors.border[0],
"&:disabled": {
color: theme.colors.border[0],
backgroundColor: theme.colors.bg[0],
},
},
rightSection: {
width: "auto",
margin: 0,
borderTopRightRadius: theme.radius.xs,
borderBottomRightRadius: theme.radius.xs,
},
}),
},
});
export { NumberInput } from "@mantine/core";
export { getNumberInputOverrides } from "./NumberInput.styled";
import { Canvas, Story, Meta } from "@storybook/addon-docs";
import { Radio } from "./";
<Meta title="Inputs/Radio & Radio Group" component={Radio} />
# Radio & Radio.Group
import { Radio, Stack } from "metabase/ui";
export const args = {
label: "Label",
description: "",
disabled: false,
labelPosition: "right",
};
export const argTypes = {
label: {
control: { type: "text" },
},
description: {
control: { type: "text" },
},
disabled: {
control: { type: "boolean" },
},
labelPosition: {
options: ["left", "right"],
control: { type: "inline-radio" },
},
};
<Meta title="Inputs/Radio" component={Radio} args={args} argTypes={argTypes} />
# Radio
Our themed wrapper around [Mantine Radio](https://mantine.dev/core/radio/).
......@@ -13,86 +36,90 @@ Radio buttons allow users to select a single option from a list of mutually excl
## Docs
- [Figma File](https://www.figma.com/file/7LCGPhkbJdrhdIaeiU1O9c/Input-%2F-Radio?type=design&node-id=1%3A96&t=S6gieWhmvLP15ARp-1)
- [Figma File](https://www.figma.com/file/7LCGPhkbJdrhdIaeiU1O9c/Input-%2F-Radio?type=design&node-id=1-96&mode=design&t=yaNljw178EFJeU7k-0)
- [Mantine Radio Docs](https://mantine.dev/core/radio/)
## Caveats
- As of right now, don't use the size prop.
- Please don't use the `error` prop on Radio components (We'll standardize on this as other inputs get converted).
## Usage guidelines
- **Limit usage to 5 options max**. Radio buttons should be used when there are 2-3 options to choose from. If there are more than 5 options, consider using a [Select](../Select) or [Checkbox](../Checkbox) instead.
- **Use this component if there are more than 5 options**. If there are fewer options, feel free to check out Radio or Select.
- For option ordering, try to use your best judgement on a sensible order. For example, Yes should come before No. Alphabetical ordering is usually a good fallback if there's no inherent order in your set of choices.
- In almost all circumstances you'll want to use `<Raio.Group>` to provide a set of options and help with defaultValues and state management between them.
- In almost all circumstances you'll want to use `<Radio.Group>` to provide a set of options and help with defaultValues and state management between them.
## Examples
export const Template = args => (
<Stack>
<Radio {...args} label="Default radio" />
<Radio {...args} label="Checked radio" checked />
<Radio {...args} label="Disabled radio" disabled />
<Radio {...args} label="Disabled checked radio" disabled checked />
</Stack>
);
export const Default = args => <Radio {...args} />;
<Canvas>
<Story name="Default">{Default}</Story>
</Canvas>
## General
### Radio.Group
export const RadioGroup = args => (
<Radio.Group
defaultValue={"react"}
label="An array of good frameworks"
description="But which one to use?"
>
<Stack mt="md">
<Radio value="react" label="React" />
<Radio value="svelte" label="Svelte" />
<Radio value="ng" label="Angular" />
<Radio value="vue" label="Vue" />
</Stack>
</Radio.Group>
);
<Canvas>
<Story name="Default">
<Radio.Group defaultValue="yes">
<Radio label="Yes" value="yes" />
<Radio label="No" value="no" />
</Radio.Group>
</Story>
<Story name="Radio group">{RadioGroup}</Story>
</Canvas>
## Disabled
### Label
export const Label = args => <Template {...args} />;
<Canvas>
<Story name="Disabled">
<Radio.Group value="yes">
<Radio label="Yes" value="yes" disabled />
<Radio label="No" value="no" disabled />
</Radio.Group>
</Story>
<Story name="Label">{Label}</Story>
</Canvas>
## Description
#### Left label position
If needed you can add a description value to a radio button to give more context about the choice.
export const LabelLeft = args => <Template {...args} labelPosition="left" />;
<Canvas>
<Story name="Descriptions">
<Radio.Group defaultValue="chocolate">
<Radio
label="Chocolate"
value="chocolate"
description="One of our most popular flavors"
/>
<Radio
label="Vanilla"
value="vanilla"
description="A classic for all seasons"
/>
<Radio
label="Swirl"
value="swirl"
description="Why not have it both ways?"
/>
</Radio.Group>
</Story>
<Story name="Label, left position">{LabelLeft}</Story>
</Canvas>
## Using the Radio group props
### Description
You can use `label` and `description` on the Radio.Group component to provide a label and description for the set of choices.
export const Description = args => (
<Template {...args} description="Description" />
);
<Canvas>
<Story name="Radio Group props">
<Radio.Group
label="A set of options"
description="You could so something like this if there was a deprecated setting that a user needs to update."
defaultValue="one"
>
<Radio label="Old bad setting" value="one" disabled />
<Radio label="A better newer setting" value="two" />
</Radio.Group>
</Story>
<Story name="Description">{Description}</Story>
</Canvas>
export const DescriptionLeft = args => (
<Template {...args} description="Description" labelPosition="left" />
);
#### Left label position
<Canvas>
<Story name="Description, left position">{DescriptionLeft}</Story>
</Canvas>
## Related components
- Select
- Checkbox
- Select
import type { MantineThemeOverride } from "@mantine/core";
import { getStylesRef, getSize, rem } from "@mantine/core";
import type {
RadioStylesParams,
MantineTheme,
MantineThemeOverride,
} from "@mantine/core";
const SIZES = {
md: rem(20),
};
export const getRadioOverrides = (): MantineThemeOverride["components"] => ({
Radio: {
styles: theme => {
return {
root: {
marginBottom: theme.spacing.md,
defaultProps: {
size: "md",
},
styles: (
theme: MantineTheme,
{ labelPosition }: RadioStylesParams,
{ size = "md" },
) => ({
root: {
[`&:has(.${getStylesRef("input")}:disabled)`]: {
[`.${getStylesRef("label")}`]: {
color: theme.colors.text[0],
},
[`.${getStylesRef("description")}`]: {
color: theme.colors.text[0],
},
[`.${getStylesRef("icon")}`]: {
color: theme.white,
},
},
label: {
color: theme.colors.text[2],
fontWeight: 700,
},
inner: {
width: getSize({ size, sizes: SIZES }),
height: getSize({ size, sizes: SIZES }),
},
radio: {
ref: getStylesRef("input"),
width: getSize({ size, sizes: SIZES }),
height: getSize({ size, sizes: SIZES }),
cursor: "pointer",
borderColor: theme.colors.text[0],
"&:checked": {
borderColor: theme.colors.brand[1],
backgroundColor: theme.colors.brand[1],
},
};
},
},
RadioGroup: {
styles: theme => {
return {
label: {
fontWeight: 700,
color: theme.colors.text[2],
"&:disabled": {
opacity: 0.3,
},
description: {
marginBottom: theme.spacing.md,
"&:disabled:not(:checked)": {
borderColor: theme.colors.text[0],
backgroundColor: theme.colors.bg[1],
},
};
},
},
label: {
ref: getStylesRef("label"),
color: theme.colors.text[2],
fontSize: theme.fontSizes.md,
fontWeight: "bold",
lineHeight: theme.lineHeight,
},
description: {
ref: getStylesRef("description"),
color: theme.colors.text[2],
fontSize: theme.fontSizes.sm,
lineHeight: theme.lineHeight,
marginTop: theme.spacing.xs,
},
icon: {
ref: getStylesRef("icon"),
},
}),
},
});
import { Canvas, Story, Meta } from "@storybook/addon-docs";
import { Icon } from "metabase/core/components/Icon";
import { Stack } from "metabase/ui";
import { TextInput } from "./";
export const args = {
variant: "default",
size: "md",
label: "Label",
description: "",
error: "",
placeholder: "Placeholder",
disabled: false,
withAsterisk: false,
};
export const sampleArgs = {
value: "Metabase",
label: "Company or team name",
description: "Name used for this instance",
placeholder: "Department of awesome",
error: "required",
};
export const argTypes = {
variant: {
options: ["default", "unstyled"],
control: { type: "inline-radio" },
},
size: {
options: ["xs", "md"],
control: { type: "inline-radio" },
},
label: {
control: { type: "text" },
},
description: {
control: { type: "text" },
},
placeholder: {
control: { type: "text" },
},
error: {
control: { type: "text" },
},
disabled: {
control: { type: "boolean" },
},
withAsterisk: {
control: { type: "boolean" },
},
};
<Meta
title="Inputs/TextInput"
component={TextInput}
args={args}
argTypes={argTypes}
/>
# TextInput
Our themed wrapper around [Mantine TextInput](https://mantine.dev/core/text-input/).
## Docs
- [Figma File](https://www.figma.com/file/oIZhYS5OoRA7twd4KqN4Eu/Input-%2F-Text?type=design&node-id=1-96&mode=design&t=yaNljw178EFJeU7k-0)
- [Mantine TextInput Docs](https://mantine.dev/core/text-input/)
## Examples
export const Default = args => <TextInput {...args} />;
export const Template = args => (
<Stack>
<TextInput {...args} variant="default" />
<TextInput {...args} variant="unstyled" />
</Stack>
);
export const Empty = args => (
<Template
{...args}
label={sampleArgs.label}
placeholder={sampleArgs.placeholder}
/>
);
export const Filled = args => (
<Template
{...args}
defaultValue={sampleArgs.value}
label={sampleArgs.label}
placeholder={sampleArgs.placeholder}
/>
);
export const Asterisk = args => (
<Template
{...args}
label={sampleArgs.label}
placeholder={sampleArgs.placeholder}
withAsterisk
/>
);
export const Description = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
/>
);
export const Disabled = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
disabled
withAsterisk
/>
);
export const Error = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
error={sampleArgs.error}
withAsterisk
/>
);
export const Icons = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
icon={<Icon name="dashboard" />}
withAsterisk
/>
);
export const RightSection = args => (
<Template
{...args}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
rightSection={<Icon name="chevrondown" />}
withAsterisk
/>
);
export const ReadOnly = args => (
<Template
{...args}
defaultValue={sampleArgs.value}
label={sampleArgs.label}
description={sampleArgs.description}
placeholder={sampleArgs.placeholder}
rightSection={<Icon name="chevrondown" />}
readOnly
/>
);
<Canvas>
<Story name="Default">{Default}</Story>
</Canvas>
### Size - md
export const EmptyMd = args => <Empty {...args} size="md" />;
<Canvas>
<Story name="Empty, md">{EmptyMd}</Story>
</Canvas>
#### Filled
export const FilledMd = args => <Filled {...args} size="md" />;
<Canvas>
<Story name="Filled, md">{FilledMd}</Story>
</Canvas>
#### Asterisk
export const AsteriskMd = args => <Asterisk {...args} size="md" />;
<Canvas>
<Story name="Asterisk, md">{AsteriskMd}</Story>
</Canvas>
#### Description
export const DescriptionMd = args => <Description {...args} size="md" />;
<Canvas>
<Story name="Description, md">{DescriptionMd}</Story>
</Canvas>
#### Disabled
export const DisabledMd = args => <Disabled {...args} size="md" />;
<Canvas>
<Story name="Disabled, md">{Disabled}</Story>
</Canvas>
#### Error
export const ErrorMd = args => <Error {...args} size="md" />;
<Canvas>
<Story name="Error, md">{ErrorMd}</Story>
</Canvas>
#### Icon
export const IconMd = args => <Icons {...args} size="md" />;
<Canvas>
<Story name="Icon, md">{IconMd}</Story>
</Canvas>
#### Right section
export const RightSectionMd = args => <RightSection {...args} size="md" />;
<Canvas>
<Story name="Right section, md">{RightSectionMd}</Story>
</Canvas>
#### Read only
export const ReadOnlyMd = args => <ReadOnly {...args} size="md" />;
<Canvas>
<Story name="Read only, md">{ReadOnlyMd}</Story>
</Canvas>
### Size - xs
export const EmptyXs = args => <Empty {...args} size="xs" />;
<Canvas>
<Story name="Empty, xs">{EmptyXs}</Story>
</Canvas>
#### Filled
export const FilledXs = args => <Filled {...args} size="xs" />;
<Canvas>
<Story name="Filled, xs">{FilledXs}</Story>
</Canvas>
#### Asterisk
export const AsteriskXs = args => <Asterisk {...args} size="xs" />;
<Canvas>
<Story name="Asterisk, xs">{AsteriskXs}</Story>
</Canvas>
#### Description
export const DescriptionXs = args => <Description {...args} size="xs" />;
<Canvas>
<Story name="Description, xs">{DescriptionXs}</Story>
</Canvas>
#### Disabled
export const DisabledXs = args => <Disabled {...args} size="xs" />;
<Canvas>
<Story name="Disabled, xs">{Disabled}</Story>
</Canvas>
#### Error
export const ErrorXs = args => <Error {...args} size="xs" />;
<Canvas>
<Story name="Error, xs">{ErrorXs}</Story>
</Canvas>
#### Icon
export const IconXs = args => <Icons {...args} size="xs" />;
<Canvas>
<Story name="Icon, xs">{IconXs}</Story>
</Canvas>
#### Right section
export const RightSectionXs = args => <RightSection {...args} size="xs" />;
<Canvas>
<Story name="Right section, xs">{RightSectionXs}</Story>
</Canvas>
#### Read only
export const ReadOnlyXs = args => <ReadOnly {...args} size="xs" />;
<Canvas>
<Story name="Read only, xs">{ReadOnlyXs}</Story>
</Canvas>
import type { MantineThemeOverride } from "@mantine/core";
export const getTextInputOverrides =
(): MantineThemeOverride["components"] => ({
TextInput: {
defaultProps: {
size: "md",
},
styles: theme => ({
wrapper: {
marginTop: theme.spacing.xs,
},
}),
},
});
export { TextInput } from "@mantine/core";
export { getTextInputOverrides } from "./TextInput.styled";
export * from "./Radio";
export * from "./Checkbox";
export * from "./Input";
export * from "./NumberInput";
export * from "./Radio";
export * from "./TextInput";
......@@ -81,7 +81,7 @@ Not to use:
## Docs
- [Figma File](https://www.figma.com/file/MZhwfwmOaa8HeCBBUCeq7R/Menu?type=design&node-id=1-96&mode=design&t=Q0nq1hUkXN7VFjRt-0)
- [Figma File](https://www.figma.com/file/MZhwfwmOaa8HeCBBUCeq7R/Menu?type=design&node-id=1-96&mode=design&t=vj3dPYMbYVYVuKBy-0)
- [Mantine Menu Docs](https://mantine.dev/core/menu/)
## Caveats
......
......@@ -17,7 +17,7 @@ export const getMenuOverrides = (): MantineThemeOverride["components"] => ({
color: theme.colors.text[2],
fontSize: theme.fontSizes.md,
fontWeight: 700,
lineHeight: "1rem",
lineHeight: theme.lineHeight,
padding: theme.spacing.md,
"&:hover, &:focus": {
......@@ -41,7 +41,7 @@ export const getMenuOverrides = (): MantineThemeOverride["components"] => ({
color: theme.colors.text[0],
fontSize: theme.fontSizes.md,
fontWeight: 700,
lineHeight: "1rem",
lineHeight: theme.lineHeight,
padding: `0.375rem ${theme.spacing.md}`,
},
divider: {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment