From d87f7ccd6217f586c1ebbf532cb8a3f4b57f014f Mon Sep 17 00:00:00 2001 From: Pawan Kumar Date: Fri, 22 Nov 2024 15:50:43 +0530 Subject: [PATCH] chore: add datepicker component (#37563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added daterpicker component along with other components needed for it like Calendar andTimeField Datepicker ![CleanShot 2024-11-21 at 12 38 20@2x](https://github.com/user-attachments/assets/334cdbb9-10f8-442a-93d8-c62b83668972) Calendar ![CleanShot 2024-11-21 at 12 38 54@2x](https://github.com/user-attachments/assets/6123a842-5c32-48f1-86f9-56ffe249fedf) Timefield ![CleanShot 2024-11-21 at 12 41 56@2x](https://github.com/user-attachments/assets/b9b2dc3c-91fd-4b90-99f4-461685be7b37) /ok-to-test tags="@tag.Anvil" ## Summary by CodeRabbit ## Release Notes - **New Features** - Introduced a `Calendar` component for date selection and display. - Added a `DatePicker` component for selecting dates and times with enhanced error handling. - Launched a `TimeField` component for time input with optional prefix and suffix. - Updated `TextField` component replacing the previous `TextInput` for improved usability. - **Bug Fixes** - Enhanced styling and responsiveness of input components. - **Documentation** - Added Storybook stories for `Calendar`, `DatePicker`, and `TimeField` components to showcase functionalities and configurations. - **Chores** - Refactored imports to utilize the new `TextField` component across various widgets. > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: a1a552cb0bfdc9754341de5db0a6d8b142479083 > Cypress dashboard. > Tags: `@tag.Anvil` > Spec: >
Fri, 22 Nov 2024 10:01:23 UTC --- .../{TextInput => Calendar}/index.ts | 0 .../src/components/Calendar/src/Calendar.tsx | 39 +++++ .../components/Calendar/src/CalendarCell.tsx | 23 +++ .../Calendar/src/CalendarHeaderCell.tsx | 22 +++ .../Calendar/src/CalendarHeading.tsx | 21 +++ .../src/components/Calendar/src/index.ts | 4 + .../components/Calendar/src/styles.module.css | 66 +++++++++ .../Calendar/stories/Calendar.stories.tsx | 52 +++++++ .../src/components/Datepicker/index.ts | 1 + .../components/Datepicker/src/Datepicker.tsx | 112 +++++++++++++++ .../Datepicker/src/DatepickerTrigger.tsx | 49 +++++++ .../src/components/Datepicker/src/index.ts | 3 + .../Datepicker/src/styles.module.css | 19 +++ .../src/components/Datepicker/src/types.ts | 21 +++ .../Datepicker/stories/Datepicker.stories.tsx | 133 ++++++++++++++++++ .../components/Input/src/styles.module.css | 15 +- .../src/components/Popover/src/Popover.tsx | 5 +- .../widgets/src/components/Text/src/types.ts | 2 +- .../widgets/src/components/TextField/index.ts | 1 + .../src/TextField.tsx} | 4 +- .../src/components/TextField/src/index.ts | 2 + .../{TextInput => TextField}/src/types.ts | 2 +- .../stories/TextField.stories.tsx} | 32 ++--- .../src/components/TextInput/src/index.ts | 2 - .../widgets/src/components/TimeField/index.ts | 1 + .../components/TimeField/src/TimeField.tsx | 73 ++++++++++ .../src/components/TimeField/src/index.ts | 2 + .../src/components/TimeField/src/types.ts | 14 ++ .../TimeField/stories/TimeField.stories.tsx | 52 +++++++ .../design-system/widgets/src/index.ts | 5 +- .../widgets/src/testing/ComplexForm.tsx | 6 +- .../storybook/.storybook/preview-head.html | 8 +- .../component/index.tsx | 4 +- .../ui/wds/WDSInputWidget/component/index.tsx | 4 +- .../WDSPhoneInputWidget/component/index.tsx | 4 +- .../component/TableHeader/PageNumberInput.tsx | 4 +- .../component/TableHeader/Search.tsx | 4 +- 37 files changed, 763 insertions(+), 48 deletions(-) rename app/client/packages/design-system/widgets/src/components/{TextInput => Calendar}/index.ts (100%) create mode 100644 app/client/packages/design-system/widgets/src/components/Calendar/src/Calendar.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarCell.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeaderCell.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/Calendar/src/index.ts create mode 100644 app/client/packages/design-system/widgets/src/components/Calendar/src/styles.module.css create mode 100644 app/client/packages/design-system/widgets/src/components/Calendar/stories/Calendar.stories.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/Datepicker/index.ts create mode 100644 app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/Datepicker/src/DatepickerTrigger.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/Datepicker/src/index.ts create mode 100644 app/client/packages/design-system/widgets/src/components/Datepicker/src/styles.module.css create mode 100644 app/client/packages/design-system/widgets/src/components/Datepicker/src/types.ts create mode 100644 app/client/packages/design-system/widgets/src/components/Datepicker/stories/Datepicker.stories.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/TextField/index.ts rename app/client/packages/design-system/widgets/src/components/{TextInput/src/TextInput.tsx => TextField/src/TextField.tsx} (92%) create mode 100644 app/client/packages/design-system/widgets/src/components/TextField/src/index.ts rename app/client/packages/design-system/widgets/src/components/{TextInput => TextField}/src/types.ts (82%) rename app/client/packages/design-system/widgets/src/components/{TextInput/stories/TextInput.stories.tsx => TextField/stories/TextField.stories.tsx} (76%) delete mode 100644 app/client/packages/design-system/widgets/src/components/TextInput/src/index.ts create mode 100644 app/client/packages/design-system/widgets/src/components/TimeField/index.ts create mode 100644 app/client/packages/design-system/widgets/src/components/TimeField/src/TimeField.tsx create mode 100644 app/client/packages/design-system/widgets/src/components/TimeField/src/index.ts create mode 100644 app/client/packages/design-system/widgets/src/components/TimeField/src/types.ts create mode 100644 app/client/packages/design-system/widgets/src/components/TimeField/stories/TimeField.stories.tsx diff --git a/app/client/packages/design-system/widgets/src/components/TextInput/index.ts b/app/client/packages/design-system/widgets/src/components/Calendar/index.ts similarity index 100% rename from app/client/packages/design-system/widgets/src/components/TextInput/index.ts rename to app/client/packages/design-system/widgets/src/components/Calendar/index.ts diff --git a/app/client/packages/design-system/widgets/src/components/Calendar/src/Calendar.tsx b/app/client/packages/design-system/widgets/src/components/Calendar/src/Calendar.tsx new file mode 100644 index 0000000000..21b5afcb8a --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Calendar/src/Calendar.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import type { + DateValue, + CalendarProps as HeadlessCalendarProps, +} from "react-aria-components"; +import { + CalendarGrid as HeadlessCalendarGrid, + CalendarGridBody as HeadlessCalendarGridBody, + CalendarGridHeader as HeadlessCalendarGridHeader, + Calendar as HeadlessCalendar, +} from "react-aria-components"; +import { Flex, IconButton } from "@appsmith/wds"; + +import styles from "./styles.module.css"; +import { CalendarCell } from "./CalendarCell"; +import { CalendarHeading } from "./CalendarHeading"; +import { CalendarHeaderCell } from "./CalendarHeaderCell"; + +type CalendarProps = HeadlessCalendarProps; + +export const Calendar = (props: CalendarProps) => { + return ( + + + + + + + + + {(day) => {day}} + + + {(date) => } + + + + ); +}; diff --git a/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarCell.tsx b/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarCell.tsx new file mode 100644 index 0000000000..2d885d4083 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarCell.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Text } from "@appsmith/wds"; +import { + CalendarCell as HeadlessCalendarCell, + type CalendarCellProps as HeadlessCalendarCellProps, +} from "react-aria-components"; + +import styles from "./styles.module.css"; + +export type CalendarCellProps = HeadlessCalendarCellProps & + React.RefAttributes; + +function CalendarCell(props: CalendarCellProps) { + const { date } = props; + + return ( + + {date.day} + + ); +} + +export { CalendarCell }; diff --git a/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeaderCell.tsx b/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeaderCell.tsx new file mode 100644 index 0000000000..5f8b20ebc1 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeaderCell.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { Text } from "@appsmith/wds"; +import { CalendarHeaderCell as HeadlessCalendarHeaderCell } from "react-aria-components"; + +import { type CalendarHeaderCellProps as HeadlessCalendarHeaderCellProps } from "react-aria-components"; + +export type CalendarHeaderCellProps = HeadlessCalendarHeaderCellProps & + React.RefAttributes; + +function CalendarHeaderCell(props: CalendarHeaderCellProps) { + const { children } = props; + + return ( + + + {children} + + + ); +} + +export { CalendarHeaderCell }; diff --git a/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx b/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx new file mode 100644 index 0000000000..ed931b848d --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Calendar/src/CalendarHeading.tsx @@ -0,0 +1,21 @@ +import { Text, type TextProps } from "@appsmith/wds"; +import React, { forwardRef, type ForwardedRef } from "react"; +import { HeadingContext, useContextProps } from "react-aria-components"; + +function CalendarHeading( + props: TextProps, + ref: ForwardedRef, +) { + [props, ref] = useContextProps(props, ref, HeadingContext); + const { children, ...domProps } = props; + + return ( + + {children} + + ); +} + +const _CalendarHeading = forwardRef(CalendarHeading); + +export { _CalendarHeading as CalendarHeading }; diff --git a/app/client/packages/design-system/widgets/src/components/Calendar/src/index.ts b/app/client/packages/design-system/widgets/src/components/Calendar/src/index.ts new file mode 100644 index 0000000000..2a316877fd --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Calendar/src/index.ts @@ -0,0 +1,4 @@ +export { Calendar } from "./Calendar"; +export { CalendarCell } from "./CalendarCell"; +export { CalendarHeading } from "./CalendarHeading"; +export { CalendarHeaderCell } from "./CalendarHeaderCell"; diff --git a/app/client/packages/design-system/widgets/src/components/Calendar/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/Calendar/src/styles.module.css new file mode 100644 index 0000000000..2a51ca6840 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Calendar/src/styles.module.css @@ -0,0 +1,66 @@ +.calendar { + padding: var(--outer-spacing-3); +} + +.calendar table { + display: flex; + flex-direction: column; + margin: 0; +} + +.calendar thead tr { + display: flex; + justify-content: space-around; + padding-block-start: var(--inner-spacing-1); +} + +.calendar tbody tr { + display: flex; + justify-content: space-between; +} + +.calendar thead th { + display: flex; + align-items: center; + justify-content: center; + inline-size: var(--sizing-9); + block-size: var(--sizing-9); +} + +.calendar tbody td { + padding: var(--inner-spacing-1); +} + +.calendar tbody [role="button"] { + display: flex; + align-items: center; + justify-content: center; + inline-size: var(--sizing-9); + block-size: var(--sizing-9); + border-radius: var(--border-radius-elevation-3); + border: var(--border-width-2) solid transparent; + text-align: center; +} + +.calendar tbody [role="button"][data-disabled] { + opacity: var(--opacity-disabled); +} + +.calendar tbody [role="button"][data-hovered] { + background-color: var(--color-bg-accent-subtle-hover); + cursor: pointer; +} + +.calendar tbody [role="button"][data-pressed] { + background-color: var(--color-bg-accent-subtle-active); +} + +.calendar tbody [role="button"][data-selected] { + background-color: var(--color-bg-accent); + color: var(--color-fg-on-accent); +} + +.calendar tbody [role="button"][data-focus-visible] { + outline: var(--border-width-2) solid var(--color-bd-accent); + outline-offset: var(--border-width-2); +} diff --git a/app/client/packages/design-system/widgets/src/components/Calendar/stories/Calendar.stories.tsx b/app/client/packages/design-system/widgets/src/components/Calendar/stories/Calendar.stories.tsx new file mode 100644 index 0000000000..0916b4e41d --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Calendar/stories/Calendar.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { Calendar } from "../src"; +import { today, getLocalTimeZone } from "@internationalized/date"; + +const meta: Meta = { + component: Calendar, + title: "WDS/Widgets/Calendar", + parameters: { + docs: { + description: { + component: "A calendar component for date selection and display.", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + defaultValue: today(getLocalTimeZone()), + }, +}; + +export const WithMinDate: Story = { + args: { + defaultValue: today(getLocalTimeZone()), + minValue: today(getLocalTimeZone()), + }, +}; + +export const WithMaxDate: Story = { + args: { + defaultValue: today(getLocalTimeZone()), + maxValue: today(getLocalTimeZone()).add({ days: 10 }), + }, +}; + +export const Disabled: Story = { + args: { + defaultValue: today(getLocalTimeZone()), + isDisabled: true, + }, +}; + +export const ReadOnly: Story = { + args: { + defaultValue: today(getLocalTimeZone()), + isReadOnly: true, + }, +}; diff --git a/app/client/packages/design-system/widgets/src/components/Datepicker/index.ts b/app/client/packages/design-system/widgets/src/components/Datepicker/index.ts new file mode 100644 index 0000000000..3bd16e178a --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Datepicker/index.ts @@ -0,0 +1 @@ +export * from "./src"; diff --git a/app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx b/app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx new file mode 100644 index 0000000000..3d98fb0b31 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Datepicker/src/Datepicker.tsx @@ -0,0 +1,112 @@ +import { + FieldError, + FieldLabel, + Popover, + Calendar, + inputFieldStyles, + TimeField, +} from "@appsmith/wds"; +import clsx from "clsx"; +import React from "react"; +import { + Dialog, + DatePicker as HeadlessDatePicker, + type TimeValue, + type DateValue, +} from "react-aria-components"; + +import type { DatePickerProps } from "./types"; +import datePickerStyles from "./styles.module.css"; +import { DatepickerTrigger } from "./DatepickerTrigger"; + +export const DatePicker = (props: DatePickerProps) => { + const { + className, + contextualHelp, + errorMessage, + isDisabled, + isLoading, + isRequired, + label, + placeholderValue, + popoverClassName, + size = "medium", + ...rest + } = props; + + const placeholder: DateValue | null | undefined = placeholderValue; + const timePlaceholder = ( + placeholder && "hour" in placeholder ? placeholder : null + ) as TimeValue; + const timeMinValue = ( + props.minValue && "hour" in props.minValue ? props.minValue : null + ) as TimeValue; + const timeMaxValue = ( + props.maxValue && "hour" in props.maxValue ? props.maxValue : null + ) as TimeValue; + + return ( + + {({ state }) => { + const root = document.body.querySelector( + "[data-theme-provider]", + ) as HTMLButtonElement; + const timeGranularity = + state.granularity === "hour" || + state.granularity === "minute" || + state.granularity === "second" + ? state.granularity + : null; + const showTimeField = !!timeGranularity; + + return ( + <> + + {label} + + + {errorMessage} + + + + {showTimeField && ( +
+ +
+ )} +
+
+ + ); + }} +
+ ); +}; diff --git a/app/client/packages/design-system/widgets/src/components/Datepicker/src/DatepickerTrigger.tsx b/app/client/packages/design-system/widgets/src/components/Datepicker/src/DatepickerTrigger.tsx new file mode 100644 index 0000000000..32e5fea91b --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Datepicker/src/DatepickerTrigger.tsx @@ -0,0 +1,49 @@ +import clsx from "clsx"; +import React, { useMemo } from "react"; +import type { SIZES } from "@appsmith/wds"; +import { getTypographyClassName } from "@appsmith/wds-theming"; +import { textInputStyles, Spinner, IconButton } from "@appsmith/wds"; +import { DateInput, DateSegment, Group } from "react-aria-components"; + +import dateInputStyles from "./styles.module.css"; + +interface DatepickerTriggerProps { + isLoading?: boolean; + size?: Omit; + isDisabled?: boolean; +} + +export const DatepickerTrigger = (props: DatepickerTriggerProps) => { + const { isDisabled, isLoading, size } = props; + + const suffix = useMemo(() => { + if (Boolean(isLoading)) return ; + + return ( + + ); + }, [isLoading, size, isDisabled]); + + return ( + + + {(segment) => } + + {suffix} + + ); +}; diff --git a/app/client/packages/design-system/widgets/src/components/Datepicker/src/index.ts b/app/client/packages/design-system/widgets/src/components/Datepicker/src/index.ts new file mode 100644 index 0000000000..262edb6cb0 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Datepicker/src/index.ts @@ -0,0 +1,3 @@ +export { DatePicker } from "./Datepicker"; +export * from "./types"; +export { default as dateTimeInputStyles } from "./styles.module.css"; diff --git a/app/client/packages/design-system/widgets/src/components/Datepicker/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/Datepicker/src/styles.module.css new file mode 100644 index 0000000000..2e8d5016ea --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Datepicker/src/styles.module.css @@ -0,0 +1,19 @@ +.input:is([data-date-input]) { + display: flex; + gap: calc(var(--inner-spacing-1) / 2); + align-items: center; +} + +.input:is([data-date-input]) [data-focused="true"] { + background-color: var(--color-bg-accent); + color: var(--color-fg-on-accent); + box-shadow: 0 0 0 1px var(--color-bd-focus); +} + +.popover { + overflow: auto; +} + +.dialog .timeField { + padding: var(--outer-spacing-3); +} diff --git a/app/client/packages/design-system/widgets/src/components/Datepicker/src/types.ts b/app/client/packages/design-system/widgets/src/components/Datepicker/src/types.ts new file mode 100644 index 0000000000..f2a7e01671 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Datepicker/src/types.ts @@ -0,0 +1,21 @@ +import type { + DateValue, + DatePickerProps as SpectrumDatePickerProps, +} from "react-aria-components"; +import type { SIZES, FieldProps } from "@appsmith/wds"; + +export interface DatePickerProps + extends Omit, "slot" | "placeholder">, + FieldProps { + /** size of the select + * + * @default medium + */ + size?: Omit; + /** + * className for the popover + */ + popoverClassName?: string; +} + +export type { DateValue }; diff --git a/app/client/packages/design-system/widgets/src/components/Datepicker/stories/Datepicker.stories.tsx b/app/client/packages/design-system/widgets/src/components/Datepicker/stories/Datepicker.stories.tsx new file mode 100644 index 0000000000..bfb0428959 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Datepicker/stories/Datepicker.stories.tsx @@ -0,0 +1,133 @@ +import React from "react"; +import { objectKeys } from "@appsmith/utils"; +import { Button, Flex, SIZES } from "@appsmith/wds"; +import { parseDate } from "@internationalized/date"; +import type { Meta, StoryObj } from "@storybook/react"; + +import { DatePicker } from "../src"; +/** + * A date picker allows a user to select a date. + */ +const meta: Meta = { + component: DatePicker, + title: "WDS/Widgets/DatePicker", +}; + +export default meta; +type Story = StoryObj; + +export const Main: Story = { + args: {}, + render: (args) => ( + + + + ), +}; + +export const WithDefaultValue: Story = { + args: { + label: "Default Value", + value: parseDate("2023-06-15"), + }, + render: (args) => ( + + + + ), +}; + +/** + * The component supports two sizes `small` and `medium`. Default size is `medium`. + */ +export const Sizes: Story = { + render: () => ( + + {objectKeys(SIZES) + .filter((size) => !["xSmall", "large"].includes(size)) + .map((size) => ( + + ))} + + ), +}; + +export const Loading: Story = { + args: { + isLoading: true, + }, +}; + +export const Disabled: Story = { + args: { + isDisabled: true, + }, +}; + +export const Validation: Story = { + render: () => ( +
{ + e.preventDefault(); + alert("Form submitted"); + }} + > + + + + +
+ ), +}; + +export const ContextualHelp: Story = { + args: { + label: "Date", + contextualHelp: "Click to open the date picker and select a date", + }, +}; + +export const MaxDate: Story = { + args: { + label: "Date", + maxValue: parseDate("2024-06-15"), + }, +}; + +export const MinDate: Story = { + args: { + label: "Date", + minValue: parseDate("2024-06-15"), + }, +}; + +export const Granularity: Story = { + render: () => ( + + + + + + + ), +}; diff --git a/app/client/packages/design-system/widgets/src/components/Input/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/Input/src/styles.module.css index 95c1532aed..c5fb78b86e 100644 --- a/app/client/packages/design-system/widgets/src/components/Input/src/styles.module.css +++ b/app/client/packages/design-system/widgets/src/components/Input/src/styles.module.css @@ -128,7 +128,15 @@ * ---------------------------------------------------------------------------- */ .inputGroup[data-hovered] - .input:not(:is([data-focused], [data-readonly], [data-disabled])) { + .input:not( + :is( + [data-focused], + [data-readonly], + [data-disabled], + [data-focus-within], + :has(~ input[data-disabled="true"]) + ) + ) { background-color: var(--color-bg-neutral-subtle-hover); box-shadow: inset 0 0 0 1px var(--color-bd-on-neutral-subtle-hover); } @@ -175,8 +183,7 @@ * ---------------------------------------------------------------------------- */ .input[data-disabled], -.input[data-disabled] :is(input, textarea), -.input[data-disabled] label { +.input:has(~ input[data-disabled]) { cursor: not-allowed; box-shadow: none; } @@ -202,7 +209,7 @@ * FOCUSSED * ---------------------------------------------------------------------------- */ -.input[data-focused]:not([data-readonly]) { +.input:is([data-focused], [data-focus-within]):not([data-readonly]) { background-color: transparent; box-shadow: 0 0 0 2px var(--color-bd-focus); } diff --git a/app/client/packages/design-system/widgets/src/components/Popover/src/Popover.tsx b/app/client/packages/design-system/widgets/src/components/Popover/src/Popover.tsx index 2eb2a15e21..fcacd88b6e 100644 --- a/app/client/packages/design-system/widgets/src/components/Popover/src/Popover.tsx +++ b/app/client/packages/design-system/widgets/src/components/Popover/src/Popover.tsx @@ -1,3 +1,4 @@ +import clsx from "clsx"; import React from "react"; import type { PopoverProps } from "react-aria-components"; import { Popover as HeadlessPopover } from "react-aria-components"; @@ -5,10 +6,10 @@ import { Popover as HeadlessPopover } from "react-aria-components"; import styles from "./styles.module.css"; export const Popover = (props: PopoverProps) => { - const { children, ...rest } = props; + const { children, className, ...rest } = props; return ( - + {children} ); diff --git a/app/client/packages/design-system/widgets/src/components/Text/src/types.ts b/app/client/packages/design-system/widgets/src/components/Text/src/types.ts index 66a4a4c5d5..fb3394b358 100644 --- a/app/client/packages/design-system/widgets/src/components/Text/src/types.ts +++ b/app/client/packages/design-system/widgets/src/components/Text/src/types.ts @@ -35,7 +35,7 @@ export interface TextProps { /** Sets the CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. Only use as a **last resort**. Use style props instead. */ className?: string; /** The children of the component. */ - children: ReactNode; + children?: ReactNode; /** title attribute for the component */ title?: string; /** Sets the HTML [id](https://developer.mozilla.org/en-US/docs/Web/API/Element/id) for the element. */ diff --git a/app/client/packages/design-system/widgets/src/components/TextField/index.ts b/app/client/packages/design-system/widgets/src/components/TextField/index.ts new file mode 100644 index 0000000000..3bd16e178a --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/TextField/index.ts @@ -0,0 +1 @@ +export * from "./src"; diff --git a/app/client/packages/design-system/widgets/src/components/TextInput/src/TextInput.tsx b/app/client/packages/design-system/widgets/src/components/TextField/src/TextField.tsx similarity index 92% rename from app/client/packages/design-system/widgets/src/components/TextInput/src/TextInput.tsx rename to app/client/packages/design-system/widgets/src/components/TextField/src/TextField.tsx index b8ce649486..fd696a67da 100644 --- a/app/client/packages/design-system/widgets/src/components/TextInput/src/TextInput.tsx +++ b/app/client/packages/design-system/widgets/src/components/TextField/src/TextField.tsx @@ -3,9 +3,9 @@ import React from "react"; import { FieldError, FieldLabel, Input, inputFieldStyles } from "@appsmith/wds"; import { TextField as HeadlessTextField } from "react-aria-components"; -import type { TextInputProps } from "./types"; +import type { TextFieldProps } from "./types"; -export function TextInput(props: TextInputProps) { +export function TextField(props: TextFieldProps) { const { contextualHelp, errorMessage, diff --git a/app/client/packages/design-system/widgets/src/components/TextField/src/index.ts b/app/client/packages/design-system/widgets/src/components/TextField/src/index.ts new file mode 100644 index 0000000000..6c8b7842ee --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/TextField/src/index.ts @@ -0,0 +1,2 @@ +export * from "./TextField"; +export type { TextFieldProps } from "./types"; diff --git a/app/client/packages/design-system/widgets/src/components/TextInput/src/types.ts b/app/client/packages/design-system/widgets/src/components/TextField/src/types.ts similarity index 82% rename from app/client/packages/design-system/widgets/src/components/TextInput/src/types.ts rename to app/client/packages/design-system/widgets/src/components/TextField/src/types.ts index 313773233b..0504e9f1f5 100644 --- a/app/client/packages/design-system/widgets/src/components/TextInput/src/types.ts +++ b/app/client/packages/design-system/widgets/src/components/TextField/src/types.ts @@ -2,7 +2,7 @@ import type { ReactNode } from "react"; import type { FieldProps, SIZES } from "@appsmith/wds"; import type { TextFieldProps as AriaTextFieldProps } from "react-aria-components"; -export interface TextInputProps extends AriaTextFieldProps, FieldProps { +export interface TextFieldProps extends AriaTextFieldProps, FieldProps { placeholder?: string; suffix?: ReactNode; prefix?: ReactNode; diff --git a/app/client/packages/design-system/widgets/src/components/TextInput/stories/TextInput.stories.tsx b/app/client/packages/design-system/widgets/src/components/TextField/stories/TextField.stories.tsx similarity index 76% rename from app/client/packages/design-system/widgets/src/components/TextInput/stories/TextInput.stories.tsx rename to app/client/packages/design-system/widgets/src/components/TextField/stories/TextField.stories.tsx index 53e64a8725..8e44d7ce54 100644 --- a/app/client/packages/design-system/widgets/src/components/TextInput/stories/TextInput.stories.tsx +++ b/app/client/packages/design-system/widgets/src/components/TextField/stories/TextField.stories.tsx @@ -1,11 +1,11 @@ import React from "react"; import { Form } from "react-aria-components"; import type { Meta, StoryObj } from "@storybook/react"; -import { Flex, Icon, TextInput, Button } from "@appsmith/wds"; +import { Flex, Icon, TextField, Button } from "@appsmith/wds"; -const meta: Meta = { - title: "WDS/Widgets/TextInput", - component: TextInput, +const meta: Meta = { + title: "WDS/Widgets/TextField", + component: TextField, tags: ["autodocs"], args: { placeholder: "Write something...", @@ -13,7 +13,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Main: Story = { args: { @@ -36,12 +36,11 @@ export const WithContextualHelp: Story = { }; export const WithPrefixAndSuffix: Story = { - render: (args) => ( + render: () => ( - } /> - } /> - } /> + } /> + } suffix={} /> @@ -79,16 +78,14 @@ export const Readonly: Story = { }; export const Size: Story = { - render: (args) => ( + render: () => ( - } size="small" /> - } size="medium" @@ -98,11 +95,10 @@ export const Size: Story = { }; export const Validation: Story = { - render: (args) => ( + render: () => (
e.preventDefault()}> - (props: TimeFieldProps) { + const { + contextualHelp, + errorMessage, + isDisabled, + isInvalid, + isReadOnly, + isRequired, + label, + prefix, + size = "medium", + suffix, + value, + ...rest + } = props; + + return ( + + + {label} + + + + {(segment) => } + + {Boolean(prefix) && {prefix}} + {Boolean(suffix) && {suffix}} + + {errorMessage} + + ); +} diff --git a/app/client/packages/design-system/widgets/src/components/TimeField/src/index.ts b/app/client/packages/design-system/widgets/src/components/TimeField/src/index.ts new file mode 100644 index 0000000000..aca0b48cfd --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/TimeField/src/index.ts @@ -0,0 +1,2 @@ +export { TimeField } from "./TimeField"; +export type { TimeFieldProps } from "./types"; diff --git a/app/client/packages/design-system/widgets/src/components/TimeField/src/types.ts b/app/client/packages/design-system/widgets/src/components/TimeField/src/types.ts new file mode 100644 index 0000000000..a524a9b9d9 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/TimeField/src/types.ts @@ -0,0 +1,14 @@ +import type { ReactNode } from "react"; +import type { FieldProps, SIZES } from "@appsmith/wds"; +import type { + TimeFieldProps as AriaTimeFieldProps, + TimeValue, +} from "react-aria-components"; + +export interface TimeFieldProps + extends AriaTimeFieldProps, + FieldProps { + suffix?: ReactNode; + prefix?: ReactNode; + size?: Omit; +} diff --git a/app/client/packages/design-system/widgets/src/components/TimeField/stories/TimeField.stories.tsx b/app/client/packages/design-system/widgets/src/components/TimeField/stories/TimeField.stories.tsx new file mode 100644 index 0000000000..a498cd5826 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/TimeField/stories/TimeField.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { TimeField } from "../src"; + +import { Time } from "@internationalized/date"; + +const meta: Meta = { + title: "WDS/Widgets/TimeField", + component: TimeField, + parameters: { + docs: { + description: { + component: + "A time input component that allows users to enter and select time values.", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithLabel: Story = { + args: { + value: new Time(14, 15), + label: "With Label", + }, +}; + +export const Disabled: Story = { + args: { + label: "Disabled", + value: new Time(15, 30), + isDisabled: true, + }, +}; + +export const WithError: Story = { + args: { + isInvalid: true, + value: new Time(9, 45), + errorMessage: "Please enter a valid time", + }, +}; + +export const Required: Story = { + args: { + label: "Required", + isRequired: true, + }, +}; diff --git a/app/client/packages/design-system/widgets/src/index.ts b/app/client/packages/design-system/widgets/src/index.ts index b0236e3a62..e09e455f79 100644 --- a/app/client/packages/design-system/widgets/src/index.ts +++ b/app/client/packages/design-system/widgets/src/index.ts @@ -21,7 +21,7 @@ export * from "./components/ContextualHelp"; export * from "./components/Link"; export * from "./components/Popover"; export * from "./components/FieldError"; -export * from "./components/TextInput"; +export * from "./components/TextField"; export * from "./components/FieldLabel"; export * from "./components/Input"; export * from "./components/Field"; @@ -32,6 +32,9 @@ export * from "./components/MenuItem"; export * from "./components/Markdown"; export * from "./components/Sidebar"; export * from "./components/Sheet"; +export * from "./components/Calendar"; +export * from "./components/Datepicker"; +export * from "./components/TimeField"; export * from "./utils"; export * from "./hooks"; diff --git a/app/client/packages/design-system/widgets/src/testing/ComplexForm.tsx b/app/client/packages/design-system/widgets/src/testing/ComplexForm.tsx index 1b43400a68..66ac0dfd7e 100644 --- a/app/client/packages/design-system/widgets/src/testing/ComplexForm.tsx +++ b/app/client/packages/design-system/widgets/src/testing/ComplexForm.tsx @@ -16,7 +16,7 @@ import { ModalBody, ModalFooter, ModalContent, - TextInput, + TextField, ComboBox, Radio, ListBoxItem, @@ -139,7 +139,7 @@ export const ComplexForm = () => {