diff --git a/frontend/src/metabase/ui/components/inputs/Calendar/Calendar.styled.tsx b/frontend/src/metabase/ui/components/inputs/Calendar/Calendar.styled.tsx new file mode 100644 index 0000000000000000000000000000000000000000..42973a1a025efc3ea113d9b58cb94208db8689db --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/Calendar/Calendar.styled.tsx @@ -0,0 +1,197 @@ +import { getStylesRef, rem } from "@mantine/core"; +import type { MantineThemeOverride } from "@mantine/core"; + +export const getCalendarOverrides = (): MantineThemeOverride["components"] => ({ + Day: { + styles: theme => ({ + day: { + width: rem(40), + height: rem(40), + color: theme.colors.text[2], + fontSize: theme.fontSizes.md, + lineHeight: rem(24), + borderRadius: theme.radius.xs, + + "&:hover": { + backgroundColor: theme.colors.bg[0], + }, + "&[data-disabled]": { + color: theme.colors.bg[2], + }, + "&[data-weekend]": { + color: theme.colors.text[2], + }, + "&[data-outside]": { + color: theme.colors.bg[2], + }, + "&[data-in-range]": { + color: theme.colors.text[1], + borderRadius: 0, + backgroundColor: theme.colors.brand[0], + "&:hover": { + backgroundColor: theme.colors.brand[0], + }, + }, + "&[data-first-in-range]": { + borderTopLeftRadius: theme.radius.xs, + borderBottomLeftRadius: theme.radius.xs, + }, + "&[data-last-in-range]": { + borderTopRightRadius: theme.radius.xs, + borderBottomRightRadius: theme.radius.xs, + }, + "&[data-selected]": { + color: theme.white, + backgroundColor: theme.colors.brand[1], + "&:hover": { + backgroundColor: theme.colors.brand[1], + }, + }, + }, + }), + }, + WeekdaysRow: { + styles: theme => ({ + weekday: { + width: rem(40), + height: rem(32), + color: theme.colors.text[0], + fontSize: theme.fontSizes.sm, + lineHeight: rem(24), + textAlign: "center", + paddingBottom: 0, + }, + }), + }, + PickerControl: { + styles: theme => ({ + pickerControl: { + color: theme.colors.text[2], + fontSize: theme.fontSizes.md, + lineHeight: rem(24), + width: rem(80), + height: rem(32), + borderRadius: theme.radius.sm, + + "&:hover": { + backgroundColor: theme.colors.bg[0], + }, + "&[data-disabled]": { + color: theme.colors.bg[2], + }, + "&[data-weekend]": { + color: theme.colors.text[2], + }, + "&[data-outside]": { + color: theme.colors.bg[2], + }, + "&[data-in-range]": { + color: theme.colors.text[1], + borderRadius: 0, + backgroundColor: theme.colors.brand[0], + "&:hover": { + backgroundColor: theme.colors.brand[0], + }, + }, + }, + }), + }, + Month: { + styles: () => + getListStyles({ + rowClass: "monthRow", + cellClass: "monthCell", + horizontalPadding: rem(1), + verticalPadding: rem(1), + }), + }, + MonthsList: { + styles: theme => + getListStyles({ + rowClass: "monthsListRow", + cellClass: "monthsListCell", + horizontalPadding: theme.spacing.sm, + verticalPadding: theme.spacing.xs, + }), + }, + YearsList: { + styles: theme => + getListStyles({ + rowClass: "yearsListRow", + cellClass: "yearsListCell", + horizontalPadding: theme.spacing.sm, + verticalPadding: theme.spacing.xs, + }), + }, + CalendarHeader: { + styles: theme => ({ + calendarHeader: { + marginBottom: 0, + }, + calendarHeaderLevel: { + height: rem(32), + color: theme.colors.text[2], + fontSize: theme.fontSizes.md, + fontWeight: "bold", + lineHeight: rem(24), + + "&:hover": { + backgroundColor: theme.colors.bg[0], + }, + }, + calendarHeaderControl: { + width: rem(32), + height: rem(32), + borderRadius: theme.radius.xs, + color: theme.colors.bg[2], + "&:hover": { + backgroundColor: theme.colors.bg[0], + }, + }, + }), + }, + MonthLevel: { + styles: () => ({ + calendarHeader: { + marginBottom: 0, + }, + }), + }, +}); + +interface ListStylesParams { + rowClass: string; + cellClass: string; + horizontalPadding: string; + verticalPadding: string; +} + +const getListStyles = ({ + rowClass, + cellClass, + horizontalPadding, + verticalPadding, +}: ListStylesParams) => ({ + [cellClass]: { + ref: getStylesRef(cellClass), + + "&[data-with-spacing]": { + padding: 0, + + "&:not(:first-of-type)": { + paddingLeft: horizontalPadding, + }, + "&:not(:last-of-type)": { + paddingRight: horizontalPadding, + }, + }, + }, + [rowClass]: { + [`&:not(:first-of-type) .${getStylesRef(cellClass)}`]: { + paddingTop: verticalPadding, + }, + [`&:not(:last-of-type) .${getStylesRef("monthCell")}`]: { + paddingBottom: verticalPadding, + }, + }, +}); diff --git a/frontend/src/metabase/ui/components/inputs/Calendar/index.ts b/frontend/src/metabase/ui/components/inputs/Calendar/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b99d3134020cc6625f6a5a90606658e9281193e --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/Calendar/index.ts @@ -0,0 +1 @@ +export { getCalendarOverrides } from "./Calendar.styled"; diff --git a/frontend/src/metabase/ui/components/inputs/DateInput/DateInput.stories.mdx b/frontend/src/metabase/ui/components/inputs/DateInput/DateInput.stories.mdx new file mode 100644 index 0000000000000000000000000000000000000000..6f52c7c103252ac9c7a84db86683542075cd727a --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/DateInput/DateInput.stories.mdx @@ -0,0 +1,322 @@ +import { Canvas, Story, Meta } from "@storybook/addon-docs"; +import { Icon } from "metabase/core/components/Icon"; +import { DateInput, Stack } from "metabase/ui"; + +export const args = { + variant: "default", + size: "md", + label: "Label", + description: undefined, + error: undefined, + placeholder: "Placeholder", + disabled: false, + readOnly: false, + withAsterisk: false, +}; + +export const sampleArgs = { + value: new Date(2023, 9, 8), + label: "Event date", + description: + "The event is visible if the date falls within the chart’s time range", + placeholder: "Enter date", + 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" }, + }, + readOnly: { + control: { type: "boolean" }, + }, + withAsterisk: { + control: { type: "boolean" }, + }, +}; + +<Meta + title="Inputs/DateInput" + component={DateInput} + args={args} + argTypes={argTypes} +/> + +# DateInput + +Our themed wrapper around [Mantine DateInput](https://v6.mantine.dev/dates/date-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 DateInput Docs](https://v6.mantine.dev/dates/date-input/) + +## Examples + +export const DefaultTemplate = args => <DateInput {...args} />; + +export const VariantTemplate = args => ( + <Stack> + <DateInput {...args} variant="default" /> + <DateInput {...args} variant="unstyled" /> + </Stack> +); + +export const IconTemplate = args => ( + <VariantTemplate {...args} icon={<Icon name="calendar" />} /> +); + +export const Default = DefaultTemplate.bind({}); + +<Canvas> + <Story name="Default">{Default}</Story> +</Canvas> + +### Size - md + +export const EmptyMd = VariantTemplate.bind({}); + +<Canvas> + <Story name="Empty, md">{EmptyMd}</Story> +</Canvas> + +#### Filled + +export const FilledMd = VariantTemplate.bind({}); +FilledMd.args = { + defaultValue: sampleArgs.value, + label: sampleArgs.label, + placeholder: sampleArgs.placeholder, +}; + +<Canvas> + <Story name="Filled, md">{FilledMd}</Story> +</Canvas> + +#### Asterisk + +export const AsteriskMd = VariantTemplate.bind({}); +AsteriskMd.args = { + label: sampleArgs.label, + placeholder: sampleArgs.placeholder, + withAsterisk: true, +}; + +<Canvas> + <Story name="Asterisk, md">{AsteriskMd}</Story> +</Canvas> + +#### Description + +export const DescriptionMd = VariantTemplate.bind({}); +DescriptionMd.args = { + label: sampleArgs.label, + description: sampleArgs.description, + placeholder: sampleArgs.placeholder, +}; + +<Canvas> + <Story name="Description, md">{DescriptionMd}</Story> +</Canvas> + +#### Disabled + +export const DisabledMd = VariantTemplate.bind({}); +DisabledMd.args = { + label: sampleArgs.label, + description: sampleArgs.description, + placeholder: sampleArgs.placeholder, + disabled: true, + withAsterisk: true, +}; + +<Canvas> + <Story name="Disabled, md">{DisabledMd}</Story> +</Canvas> + +#### Error + +export const ErrorMd = VariantTemplate.bind({}); +ErrorMd.args = { + label: sampleArgs.label, + description: sampleArgs.description, + placeholder: sampleArgs.placeholder, + error: sampleArgs.error, + withAsterisk: true, +}; + +<Canvas> + <Story name="Error, md">{ErrorMd}</Story> +</Canvas> + +#### Icon + +export const IconMd = IconTemplate.bind({}); +IconMd.args = { + label: sampleArgs.label, + description: sampleArgs.description, + placeholder: sampleArgs.placeholder, + withAsterisk: true, +}; + +<Canvas> + <Story name="Icon, md">{IconMd}</Story> +</Canvas> + +#### Read only + +export const ReadOnlyMd = VariantTemplate.bind({}); +ReadOnlyMd.args = { + defaultValue: sampleArgs.value, + label: sampleArgs.label, + description: sampleArgs.description, + placeholder: sampleArgs.placeholder, + readOnly: true, +}; + +<Canvas> + <Story name="Read only, md">{ReadOnlyMd}</Story> +</Canvas> + +#### No popover + +export const NoPopoverMd = VariantTemplate.bind({}); +NoPopoverMd.args = { + defaultValue: sampleArgs.value, + label: sampleArgs.label, + description: sampleArgs.description, + placeholder: sampleArgs.placeholder, + popoverProps: { opened: false }, +}; + +<Canvas> + <Story name="No popover, md">{NoPopoverMd}</Story> +</Canvas> + +### Size - xs + +export const EmptyXs = VariantTemplate.bind({}); +EmptyXs.args = { + ...EmptyMd.args, + size: "xs", +}; + +<Canvas> + <Story name="Empty, xs">{EmptyXs}</Story> +</Canvas> + +#### Filled + +export const FilledXs = VariantTemplate.bind({}); +FilledXs.args = { + ...FilledMd.args, + size: "xs", +}; + +<Canvas> + <Story name="Filled, xs">{FilledXs}</Story> +</Canvas> + +#### Asterisk + +export const AsteriskXs = VariantTemplate.bind({}); +AsteriskXs.args = { + ...AsteriskMd.args, + size: "xs", +}; + +<Canvas> + <Story name="Asterisk, xs">{AsteriskXs}</Story> +</Canvas> + +#### Description + +export const DescriptionXs = VariantTemplate.bind({}); +DescriptionXs.args = { + ...DescriptionMd.args, + size: "xs", +}; + +<Canvas> + <Story name="Description, xs">{DescriptionXs}</Story> +</Canvas> + +#### Disabled + +export const DisabledXs = VariantTemplate.bind({}); +DisabledXs.args = { + ...DisabledMd.args, + size: "xs", +}; + +<Canvas> + <Story name="Disabled, xs">{DisabledXs}</Story> +</Canvas> + +#### Error + +export const ErrorXs = VariantTemplate.bind({}); +ErrorXs.args = { + ...ErrorMd.args, + size: "xs", +}; + +<Canvas> + <Story name="Error, xs">{ErrorXs}</Story> +</Canvas> + +#### Icon + +export const IconXs = IconTemplate.bind({}); +IconXs.args = { + ...IconMd.args, + size: "xs", +}; + +<Canvas> + <Story name="Icon, xs">{IconXs}</Story> +</Canvas> + +#### Read only + +export const ReadOnlyXs = VariantTemplate.bind({}); +ReadOnlyXs.args = { + ...ReadOnlyMd.args, + size: "xs", +}; + +<Canvas> + <Story name="Read only, xs">{ReadOnlyXs}</Story> +</Canvas> + +#### No popover + +export const NoPopoverXs = VariantTemplate.bind({}); +NoPopoverXs.args = { + ...NoPopoverMd.args, + size: "xs", +}; + +<Canvas> + <Story name="No popover, xs">{NoPopoverXs}</Story> +</Canvas> diff --git a/frontend/src/metabase/ui/components/inputs/DateInput/DateInput.styled.tsx b/frontend/src/metabase/ui/components/inputs/DateInput/DateInput.styled.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b5d381de307ab5b9cfbd519e51facae53494aec0 --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/DateInput/DateInput.styled.tsx @@ -0,0 +1,20 @@ +import type { MantineThemeOverride } from "@mantine/core"; + +export const getDateInputOverrides = + (): MantineThemeOverride["components"] => ({ + DateInput: { + defaultProps: { + size: "md", + }, + styles: theme => ({ + wrapper: { + "&:not(:only-child)": { + marginTop: theme.spacing.xs, + }, + }, + calendar: { + padding: `${theme.spacing.sm} ${theme.spacing.md}`, + }, + }), + }, + }); diff --git a/frontend/src/metabase/ui/components/inputs/DateInput/index.ts b/frontend/src/metabase/ui/components/inputs/DateInput/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..bf629feff05819809a237de38f57160c66c9d18d --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/DateInput/index.ts @@ -0,0 +1,3 @@ +export { DateInput } from "@mantine/dates"; +export type { DateInputProps } from "@mantine/dates"; +export { getDateInputOverrides } from "./DateInput.styled"; diff --git a/frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.stories.mdx b/frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.stories.mdx new file mode 100644 index 0000000000000000000000000000000000000000..c9adc5f1f4b5049f5036546f1691886e64771d29 --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.stories.mdx @@ -0,0 +1,130 @@ +import { Fragment } from "react"; +import { Canvas, Story, Meta } from "@storybook/addon-docs"; +import { DatePicker } from "metabase/ui"; + +export const args = { + type: "default", + allowDeselect: undefined, + allowSingleDateInRange: undefined, + numberOfColumns: undefined, +}; + +export const sampleArgs = { + date1: new Date(2023, 9, 8), + date2: new Date(2023, 9, 24), + date3: new Date(2023, 9, 16), +}; + +export const argTypes = { + type: { + options: ["default", "multiple", "range"], + control: { type: "inline-radio" }, + }, + allowDeselect: { + control: { type: "boolean" }, + }, + allowSingleDateInRange: { + control: { type: "boolean" }, + }, + numberOfColumns: { + control: { type: "number" }, + }, +}; + +<Meta + title="Inputs/DatePicker" + component={DatePicker} + args={args} + argTypes={argTypes} +/> + +# DatePicker + +Our themed wrapper around [Mantine DatePicker](https://v6.mantine.dev/dates/date-picker/). + +## Docs + +- [Figma File](https://www.figma.com/file/cncRbkG7XJQs144EG9r9hM/Date-Picker?type=design&node-id=0-1&mode=design&t=v00tLhbD9OFEgy25-0) +- [Mantine DatePicker Docs](https://v6.mantine.dev/dates/date-picker/) + +## Examples + +export const DefaultTemplate = args => { + return <DatePicker {...args} />; +}; + +export const Default = DefaultTemplate.bind({}); +Default.args = { + defaultDate: sampleArgs.date1, +}; + +<Canvas> + <Story name="Default">{Default}</Story> +</Canvas> + +### Allow deselect + +export const AllowDeselect = DefaultTemplate.bind({}); +AllowDeselect.args = { + allowDeselect: true, + defaultDate: sampleArgs.date1, + defaultValue: sampleArgs.date1, +}; + +<Canvas> + <Story name="Allow deselect">{AllowDeselect}</Story> +</Canvas> + +### Multiple dates + +export const MultipleDates = DefaultTemplate.bind({}); +MultipleDates.args = { + type: "multiple", + defaultValue: [sampleArgs.date1, sampleArgs.date2], + defaultDate: sampleArgs.date1, +}; + +<Canvas> + <Story name="Multiple dates">{MultipleDates}</Story> +</Canvas> + +### Dates range + +export const DatesRange = DefaultTemplate.bind({}); +DatesRange.args = { + type: "range", + defaultValue: [sampleArgs.date1, sampleArgs.date2], + defaultDate: sampleArgs.date1, +}; + +<Canvas> + <Story name="Dates range">{DatesRange}</Story> +</Canvas> + +### Single date in range + +export const SingleDateInRange = DefaultTemplate.bind({}); +SingleDateInRange.args = { + type: "range", + defaultValue: [sampleArgs.date1, sampleArgs.date1], + defaultDate: sampleArgs.date1, + allowSingleDateInRange: true, +}; + +<Canvas> + <Story name="Single date in range">{SingleDateInRange}</Story> +</Canvas> + +### Number of columns + +export const NumberOfColumns = DefaultTemplate.bind({}); +NumberOfColumns.args = { + type: "range", + defaultValue: [sampleArgs.date1, sampleArgs.date3], + defaultDate: sampleArgs.date1, + numberOfColumns: 2, +}; + +<Canvas> + <Story name="Number of columns">{NumberOfColumns}</Story> +</Canvas> diff --git a/frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.styled.tsx b/frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.styled.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f03c42d8addca74dba31fead91fa1481b287cfeb --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/DatePicker/DatePicker.styled.tsx @@ -0,0 +1,10 @@ +import type { MantineThemeOverride } from "@mantine/core"; + +export const getDatePickerOverrides = + (): MantineThemeOverride["components"] => ({ + DatePicker: { + defaultProps: { + size: "md", + }, + }, + }); diff --git a/frontend/src/metabase/ui/components/inputs/DatePicker/index.ts b/frontend/src/metabase/ui/components/inputs/DatePicker/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d06d87b5fff0097084f4b97bfd7ad5f19ce684a --- /dev/null +++ b/frontend/src/metabase/ui/components/inputs/DatePicker/index.ts @@ -0,0 +1,3 @@ +export { DatePicker } from "@mantine/dates"; +export type { DatePickerProps } from "@mantine/dates"; +export { getDatePickerOverrides } from "./DatePicker.styled"; diff --git a/frontend/src/metabase/ui/components/inputs/TextInput/TextInput.stories.mdx b/frontend/src/metabase/ui/components/inputs/TextInput/TextInput.stories.mdx index 60ca0c1fc711d269409a068843d31d0ec665a884..1cd8a477963a2b524b08b70655e026fad60205a3 100644 --- a/frontend/src/metabase/ui/components/inputs/TextInput/TextInput.stories.mdx +++ b/frontend/src/metabase/ui/components/inputs/TextInput/TextInput.stories.mdx @@ -203,7 +203,7 @@ RightSectionMd.args = { #### Read only export const ReadOnlyMd = RightSectionTemplate.bind({}); -RightSectionMd.args = { +ReadOnlyMd.args = { defaultValue: sampleArgs.value, label: sampleArgs.label, description: sampleArgs.description, diff --git a/frontend/src/metabase/ui/components/inputs/index.ts b/frontend/src/metabase/ui/components/inputs/index.ts index ecef1e575a6a94a97e5f4fd870849c7c079acb59..e524d132c6472e9cc2be7be8230b0467a6f193a5 100644 --- a/frontend/src/metabase/ui/components/inputs/index.ts +++ b/frontend/src/metabase/ui/components/inputs/index.ts @@ -1,4 +1,7 @@ +export * from "./Calendar"; export * from "./Checkbox"; +export * from "./DateInput"; +export * from "./DatePicker"; export * from "./FileInput"; export * from "./Input"; export * from "./NumberInput"; diff --git a/frontend/src/metabase/ui/theme.ts b/frontend/src/metabase/ui/theme.ts index 5cdb072893df6f8e48e9efe581cbb208342b60e8..33774c85444e4c73cf2d608f826b23b8b37c7dda 100644 --- a/frontend/src/metabase/ui/theme.ts +++ b/frontend/src/metabase/ui/theme.ts @@ -5,8 +5,11 @@ import { getAccordionOverrides, getAnchorOverrides, getButtonOverrides, + getCalendarOverrides, getCardOverrides, getCheckboxOverrides, + getDateInputOverrides, + getDatePickerOverrides, getDividerOverrides, getFileInputOverrides, getInputOverrides, @@ -111,8 +114,11 @@ export const getThemeOverrides = (): MantineThemeOverride => ({ ...getAccordionOverrides(), ...getAnchorOverrides(), ...getButtonOverrides(), + ...getCalendarOverrides(), ...getCardOverrides(), ...getCheckboxOverrides(), + ...getDateInputOverrides(), + ...getDatePickerOverrides(), ...getDividerOverrides(), ...getFileInputOverrides(), ...getInputOverrides(),