chore: add datepicker component (#37563)

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"

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/11970103158>
> Commit: a1a552cb0bfdc9754341de5db0a6d8b142479083
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11970103158&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Anvil`
> Spec:
> <hr>Fri, 22 Nov 2024 10:01:23 UTC
<!-- end of auto-generated comment: Cypress test results  -->
This commit is contained in:
Pawan Kumar 2024-11-22 15:50:43 +05:30 committed by GitHub
parent afa2324432
commit d87f7ccd62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 763 additions and 48 deletions

View File

@ -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<T extends DateValue> = HeadlessCalendarProps<T>;
export const Calendar = <T extends DateValue>(props: CalendarProps<T>) => {
return (
<HeadlessCalendar {...props} className={styles.calendar}>
<Flex alignItems="center" justifyContent="space-between" width="100%">
<IconButton icon="chevron-left" slot="previous" variant="ghost" />
<CalendarHeading size="subtitle" />
<IconButton icon="chevron-right" slot="next" variant="ghost" />
</Flex>
<HeadlessCalendarGrid>
<HeadlessCalendarGridHeader>
{(day) => <CalendarHeaderCell>{day}</CalendarHeaderCell>}
</HeadlessCalendarGridHeader>
<HeadlessCalendarGridBody>
{(date) => <CalendarCell date={date} />}
</HeadlessCalendarGridBody>
</HeadlessCalendarGrid>
</HeadlessCalendar>
);
};

View File

@ -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<HTMLTableCellElement>;
function CalendarCell(props: CalendarCellProps) {
const { date } = props;
return (
<HeadlessCalendarCell {...props} className={styles["calendar-cell"]}>
<Text>{date.day}</Text>
</HeadlessCalendarCell>
);
}
export { CalendarCell };

View File

@ -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<HTMLTableCellElement>;
function CalendarHeaderCell(props: CalendarHeaderCellProps) {
const { children } = props;
return (
<HeadlessCalendarHeaderCell {...props}>
<Text color="neutral" fontWeight={700} textAlign="center">
{children}
</Text>
</HeadlessCalendarHeaderCell>
);
}
export { CalendarHeaderCell };

View File

@ -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<HTMLHeadingElement>,
) {
[props, ref] = useContextProps(props, ref, HeadingContext);
const { children, ...domProps } = props;
return (
<Text {...domProps} color="neutral" ref={ref}>
{children}
</Text>
);
}
const _CalendarHeading = forwardRef(CalendarHeading);
export { _CalendarHeading as CalendarHeading };

View File

@ -0,0 +1,4 @@
export { Calendar } from "./Calendar";
export { CalendarCell } from "./CalendarCell";
export { CalendarHeading } from "./CalendarHeading";
export { CalendarHeaderCell } from "./CalendarHeaderCell";

View File

@ -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);
}

View File

@ -0,0 +1,52 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Calendar } from "../src";
import { today, getLocalTimeZone } from "@internationalized/date";
const meta: Meta<typeof Calendar> = {
component: Calendar,
title: "WDS/Widgets/Calendar",
parameters: {
docs: {
description: {
component: "A calendar component for date selection and display.",
},
},
},
};
export default meta;
type Story = StoryObj<typeof Calendar>;
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,
},
};

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -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 = <T extends DateValue>(props: DatePickerProps<T>) => {
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 (
<HeadlessDatePicker
aria-label={Boolean(label) ? undefined : "DatePicker"}
className={clsx(inputFieldStyles.field, className)}
data-size={size}
isDisabled={isDisabled}
isRequired={isRequired}
{...rest}
>
{({ 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 (
<>
<FieldLabel
contextualHelp={contextualHelp}
isDisabled={isDisabled}
isRequired={isRequired}
>
{label}
</FieldLabel>
<DatepickerTrigger
isDisabled={isDisabled}
isLoading={isLoading}
size={size}
/>
<FieldError>{errorMessage}</FieldError>
<Popover
UNSTABLE_portalContainer={root}
className={clsx(datePickerStyles.popover, popoverClassName)}
>
<Dialog className={datePickerStyles.dialog}>
<Calendar />
{showTimeField && (
<div className={datePickerStyles.timeField}>
<TimeField
granularity={timeGranularity}
hideTimeZone={props.hideTimeZone}
hourCycle={props.hourCycle}
label="Time"
maxValue={timeMaxValue}
minValue={timeMinValue}
onChange={state.setTimeValue}
placeholderValue={timePlaceholder}
value={state.timeValue}
/>
</div>
)}
</Dialog>
</Popover>
</>
);
}}
</HeadlessDatePicker>
);
};

View File

@ -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<keyof typeof SIZES, "xSmall" | "large">;
isDisabled?: boolean;
}
export const DatepickerTrigger = (props: DatepickerTriggerProps) => {
const { isDisabled, isLoading, size } = props;
const suffix = useMemo(() => {
if (Boolean(isLoading)) return <Spinner />;
return (
<IconButton
color={Boolean(isLoading) ? "neutral" : "accent"}
icon="calendar-month"
isDisabled={isDisabled}
isLoading={isLoading}
size={size === "medium" ? "small" : "xSmall"}
variant={Boolean(isLoading) ? "ghost" : "filled"}
/>
);
}, [isLoading, size, isDisabled]);
return (
<Group className={textInputStyles.inputGroup}>
<DateInput
className={clsx(
textInputStyles.input,
dateInputStyles.input,
getTypographyClassName("body"),
)}
data-date-input
>
{(segment) => <DateSegment segment={segment} />}
</DateInput>
<span data-input-suffix>{suffix}</span>
</Group>
);
};

View File

@ -0,0 +1,3 @@
export { DatePicker } from "./Datepicker";
export * from "./types";
export { default as dateTimeInputStyles } from "./styles.module.css";

View File

@ -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);
}

View File

@ -0,0 +1,21 @@
import type {
DateValue,
DatePickerProps as SpectrumDatePickerProps,
} from "react-aria-components";
import type { SIZES, FieldProps } from "@appsmith/wds";
export interface DatePickerProps<T extends DateValue>
extends Omit<SpectrumDatePickerProps<T>, "slot" | "placeholder">,
FieldProps {
/** size of the select
*
* @default medium
*/
size?: Omit<keyof typeof SIZES, "xSmall" | "large">;
/**
* className for the popover
*/
popoverClassName?: string;
}
export type { DateValue };

View File

@ -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<typeof DatePicker> = {
component: DatePicker,
title: "WDS/Widgets/DatePicker",
};
export default meta;
type Story = StoryObj<typeof DatePicker>;
export const Main: Story = {
args: {},
render: (args) => (
<Flex width="sizing-60">
<DatePicker {...args} popoverClassName="sb-unstyled" />
</Flex>
),
};
export const WithDefaultValue: Story = {
args: {
label: "Default Value",
value: parseDate("2023-06-15"),
},
render: (args) => (
<Flex width="sizing-60">
<DatePicker {...args} popoverClassName="sb-unstyled" />
</Flex>
),
};
/**
* The component supports two sizes `small` and `medium`. Default size is `medium`.
*/
export const Sizes: Story = {
render: () => (
<Flex direction="column" gap="spacing-4" width="sizing-60">
{objectKeys(SIZES)
.filter((size) => !["xSmall", "large"].includes(size))
.map((size) => (
<DatePicker key={size} popoverClassName="sb-unstyled" size={size} />
))}
</Flex>
),
};
export const Loading: Story = {
args: {
isLoading: true,
},
};
export const Disabled: Story = {
args: {
isDisabled: true,
},
};
export const Validation: Story = {
render: () => (
<form
onSubmit={(e) => {
e.preventDefault();
alert("Form submitted");
}}
>
<Flex direction="column" gap="spacing-5" width="sizing-60">
<DatePicker
isRequired
label="Validation"
popoverClassName="sb-unstyled"
/>
<Button type="submit">Submit</Button>
</Flex>
</form>
),
};
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: () => (
<Flex direction="column" gap="spacing-5" width="sizing-100">
<DatePicker
granularity="day"
label="Day"
popoverClassName="sb-unstyled"
/>
<DatePicker
granularity="hour"
label="Hour"
popoverClassName="sb-unstyled"
/>
<DatePicker
granularity="minute"
label="Minute"
popoverClassName="sb-unstyled"
/>
<DatePicker
granularity="second"
label="Second"
popoverClassName="sb-unstyled"
/>
</Flex>
),
};

View File

@ -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);
}

View File

@ -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 (
<HeadlessPopover {...rest} className={styles.popover}>
<HeadlessPopover {...rest} className={clsx(styles.popover, className)}>
{children}
</HeadlessPopover>
);

View File

@ -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. */

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -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,

View File

@ -0,0 +1,2 @@
export * from "./TextField";
export type { TextFieldProps } from "./types";

View File

@ -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;

View File

@ -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<typeof TextInput> = {
title: "WDS/Widgets/TextInput",
component: TextInput,
const meta: Meta<typeof TextField> = {
title: "WDS/Widgets/TextField",
component: TextField,
tags: ["autodocs"],
args: {
placeholder: "Write something...",
@ -13,7 +13,7 @@ const meta: Meta<typeof TextInput> = {
};
export default meta;
type Story = StoryObj<typeof TextInput>;
type Story = StoryObj<typeof TextField>;
export const Main: Story = {
args: {
@ -36,12 +36,11 @@ export const WithContextualHelp: Story = {
};
export const WithPrefixAndSuffix: Story = {
render: (args) => (
render: () => (
<Flex direction="column" gap="spacing-4">
<TextInput {...args} suffix={<Icon name="user" size="medium" />} />
<TextInput {...args} prefix={<Icon name="user" size="medium" />} />
<TextInput
{...args}
<TextField suffix={<Icon name="user" size="medium" />} />
<TextField prefix={<Icon name="user" size="medium" />} />
<TextField
prefix={<Icon name="user" size="medium" />}
suffix={<Icon name="user" size="medium" />}
/>
@ -79,16 +78,14 @@ export const Readonly: Story = {
};
export const Size: Story = {
render: (args) => (
render: () => (
<Flex direction="column" gap="spacing-4">
<TextInput
{...args}
<TextField
label="Small"
prefix={<Icon name="user" size="medium" />}
size="small"
/>
<TextInput
{...args}
<TextField
label="Medium"
prefix={<Icon name="user" size="medium" />}
size="medium"
@ -98,11 +95,10 @@ export const Size: Story = {
};
export const Validation: Story = {
render: (args) => (
render: () => (
<Form onSubmit={(e) => e.preventDefault()}>
<Flex direction="column" gap="spacing-3" width="sizing-60">
<TextInput
{...args}
<TextField
errorMessage="Please enter a valid email address"
isRequired
label="Email"

View File

@ -1,2 +0,0 @@
export * from "./TextInput";
export type { TextInputProps } from "./types";

View File

@ -0,0 +1 @@
export * from "./src";

View File

@ -0,0 +1,73 @@
import clsx from "clsx";
import React from "react";
import {
FieldError,
FieldLabel,
inputFieldStyles,
textInputStyles,
dateTimeInputStyles,
} from "@appsmith/wds";
import {
DateInput,
DateSegment,
Group,
TimeField as HeadlessTimeField,
type TimeValue,
} from "react-aria-components";
import { getTypographyClassName } from "@appsmith/wds-theming";
import type { TimeFieldProps } from "./types";
export function TimeField<T extends TimeValue>(props: TimeFieldProps<T>) {
const {
contextualHelp,
errorMessage,
isDisabled,
isInvalid,
isReadOnly,
isRequired,
label,
prefix,
size = "medium",
suffix,
value,
...rest
} = props;
return (
<HeadlessTimeField
{...rest}
className={clsx(inputFieldStyles.field)}
data-field=""
isDisabled={isDisabled}
isInvalid={isInvalid}
isReadOnly={isReadOnly}
isRequired={isRequired}
value={value}
>
<FieldLabel
contextualHelp={contextualHelp}
isDisabled={isDisabled}
isRequired={isRequired}
>
{label}
</FieldLabel>
<Group className={textInputStyles.inputGroup}>
<DateInput
className={clsx(
textInputStyles.input,
dateTimeInputStyles.input,
getTypographyClassName("body"),
)}
data-date-input
data-size={Boolean(size) ? size : undefined}
>
{(segment) => <DateSegment segment={segment} />}
</DateInput>
{Boolean(prefix) && <span data-input-prefix>{prefix}</span>}
{Boolean(suffix) && <span data-input-suffix>{suffix}</span>}
</Group>
<FieldError>{errorMessage}</FieldError>
</HeadlessTimeField>
);
}

View File

@ -0,0 +1,2 @@
export { TimeField } from "./TimeField";
export type { TimeFieldProps } from "./types";

View File

@ -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<T extends TimeValue>
extends AriaTimeFieldProps<T>,
FieldProps {
suffix?: ReactNode;
prefix?: ReactNode;
size?: Omit<keyof typeof SIZES, "xSmall">;
}

View File

@ -0,0 +1,52 @@
import type { Meta, StoryObj } from "@storybook/react";
import { TimeField } from "../src";
import { Time } from "@internationalized/date";
const meta: Meta<typeof TimeField> = {
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<typeof TimeField>;
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,
},
};

View File

@ -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";

View File

@ -16,7 +16,7 @@ import {
ModalBody,
ModalFooter,
ModalContent,
TextInput,
TextField,
ComboBox,
Radio,
ListBoxItem,
@ -139,7 +139,7 @@ export const ComplexForm = () => {
<TextArea label="Your comment" />
</Flex>
<Flex gap="spacing-2">
<TextInput />
<TextField />
<ComboBox>
{[
{
@ -167,7 +167,7 @@ export const ComplexForm = () => {
<Button>Ok</Button>
</Flex>
<Flex gap="spacing-2">
<TextInput size="small" />
<TextField size="small" />
<ComboBox
items={[
{

View File

@ -11,17 +11,17 @@
font-family: var(--font-family) !important;
}
th, td,
th:not(.sb-unstyled th), td:not(.sb-unstyled td),
.css-s230ta {
border-color: var(--color-bd) !important;
}
td,
td:not(.sb-unstyled td),
.css-s230ta {
background-color: var(--color-bg-elevation-1) !important;
}
code:not(:has(span)),
code:not(:has(span)):not(.sb-unstyled code),
.css-o1d7ko {
background-color: var(--color-bg-accent-subtle) !important;
color: var(--color-fg-on-accent-subtle) !important;
@ -29,7 +29,7 @@
}
/** Note: adding this so that all links are styled the same way excepts the one that are in the story */
a:not(.docs-story a) {
a:not(.docs-story a):not(.sb-unstyled a) {
color: var(--color-fg-accent) !important;
}

View File

@ -1,5 +1,5 @@
import React from "react";
import { Text, TextInput } from "@appsmith/wds";
import { Text, TextField } from "@appsmith/wds";
import type { CurrencyInputComponentProps } from "./types";
import { CurrencyTypeOptions } from "constants/Currency";
import { useDebouncedValue } from "@mantine/hooks";
@ -24,7 +24,7 @@ export function CurrencyInputComponent(props: CurrencyInputComponentProps) {
const [errorMessage] = useDebouncedValue(props.errorMessage, DEBOUNCE_TIME);
return (
<TextInput
<TextField
autoComplete={props.autoComplete}
autoFocus={props.autoFocus}
contextualHelp={props.tooltip}

View File

@ -1,6 +1,6 @@
import React from "react";
import { isNil } from "lodash";
import { TextInput } from "@appsmith/wds";
import { TextField } from "@appsmith/wds";
import { Icon, TextArea } from "@appsmith/wds";
import { useDebouncedValue } from "@mantine/hooks";
import { INPUT_TYPES } from "modules/ui-builder/ui/wds/WDSBaseInputWidget";
@ -46,7 +46,7 @@ function InputComponent(props: InputComponentProps) {
})();
const ElementType: React.ElementType =
props.inputType === INPUT_TYPES.MULTI_LINE_TEXT ? TextArea : TextInput;
props.inputType === INPUT_TYPES.MULTI_LINE_TEXT ? TextArea : TextField;
const autoComplete = (() => {
if (

View File

@ -1,6 +1,6 @@
import React from "react";
import { ISDCodeOptions } from "constants/ISDCodes_v2";
import { Text, TextInput } from "@appsmith/wds";
import { Text, TextField } from "@appsmith/wds";
import type { PhoneInputComponentProps } from "./types";
@ -16,7 +16,7 @@ export function PhoneInputComponent(props: PhoneInputComponentProps) {
);
return (
<TextInput
<TextField
autoComplete={props.autoComplete}
autoFocus={props.autoFocus}
contextualHelp={props.tooltip}

View File

@ -1,5 +1,5 @@
import { Keys } from "@blueprintjs/core";
import { TextInput } from "@appsmith/wds";
import { TextField } from "@appsmith/wds";
import React, { useCallback, useEffect, useState } from "react";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
@ -46,7 +46,7 @@ function PageNumberInputComponent(props: {
);
return (
<TextInput
<TextField
className="t--table-widget-page-input"
excludeFromTabOrder={props.excludeFromTabOrder}
isDisabled={props.disabled}

View File

@ -1,5 +1,5 @@
import React from "react";
import { TextInput } from "@appsmith/wds";
import { TextField } from "@appsmith/wds";
export interface SearchProps {
isVisibleSearch?: boolean;
@ -12,7 +12,7 @@ export const Search = (props: SearchProps) => {
const { excludeFromTabOrder, isVisibleSearch, onSearch, searchKey } = props;
return isVisibleSearch ? (
<TextInput
<TextField
excludeFromTabOrder={excludeFromTabOrder}
onChange={onSearch}
placeholder="Search..."