Skip to content

Commit

Permalink
feat(components): date and time fields (#6036)
Browse files Browse the repository at this point in the history
* feat: date range picker

* WIP

* cleanp

* time field

* cleanup
  • Loading branch information
mikeldking authored Jan 21, 2025
1 parent 141f523 commit b31d7ca
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 20 deletions.
12 changes: 10 additions & 2 deletions app/src/GlobalStyles.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from "react";
import { css, Global } from "@emotion/react";

import { ThemeContextType, useTheme } from "./contexts";
import { useProvider } from "@arizeai/components";

import { ThemeContextType } from "./contexts";

/**
* Medium size root CSS variables
Expand Down Expand Up @@ -939,6 +941,12 @@ export const derivedCSS = (theme: ThemeContextType["theme"]) => css`
--ac-global-border-color-light: var(--ac-global-color-grey-400);
--ac-global-border-color-dark: var(--ac-global-color-grey-300);
--ac-highlight-foreground: var(--ac-global-text-color-900);
--ac-highlight-background: var(--ac-global-color-primary-300);
// Text
--ac-text-color-placeholder: var(--ac-global-color-grey-700);
// Styles for text fields etc
--ac-global-input-field-border-color: var(--ac-global-color-grey-400);
--ac-global-input-field-border-color-hover: var(--ac-global-color-grey-300);
Expand Down Expand Up @@ -1136,7 +1144,7 @@ const appGlobalStylesCSS = css`
`;

export function GlobalStyles() {
const { theme } = useTheme();
const { theme = "dark" } = useProvider();
const themeCSS = theme === "dark" ? darkThemeCSS : lightThemeCSS;
return (
<Global
Expand Down
30 changes: 14 additions & 16 deletions app/src/components/combobox/styles.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { css } from "@emotion/react";

import { fieldPopoverCSS } from "../field/styles";

export const comboBoxCSS = css`
&[data-size="M"] {
--combobox-vertical-padding: 6px;
Expand Down Expand Up @@ -51,22 +53,18 @@ export const comboBoxCSS = css`
}
`;

export const comboBoxPopoverCSS = css`
width: var(--trigger-width);
background-color: var(--ac-global-menu-background-color);
border-radius: var(--ac-global-rounding-small);
color: var(--ac-global-text-color-900);
box-shadow: 0px 4px 10px var(--px-overlay-shadow-color);
border: 1px solid var(--ac-global-menu-border-color);
max-height: inherit;
.react-aria-ListBox {
display: block;
width: unset;
max-height: inherit;
min-height: unset;
border: none;
}
`;
export const comboBoxPopoverCSS = css(
fieldPopoverCSS,
css`
.react-aria-ListBox {
display: block;
width: unset;
max-height: inherit;
min-height: unset;
border: none;
}
`
);

export const comboBoxItemCSS = css`
outline: none;
Expand Down
71 changes: 71 additions & 0 deletions app/src/components/datetime/DateField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { forwardRef, Ref } from "react";
import {
DateField as AriaDateField,
DateFieldProps as AriaDateFieldProps,
DateValue,
} from "react-aria-components";
import { css } from "@emotion/react";

import { fieldBaseCSS } from "../field/styles";

export type DateFieldProps<T extends DateValue> = AriaDateFieldProps<T>;

const dateFieldCSS = css`
--date-field-vertical-padding: 6px;
--date-field-horizontal-padding: 8px;
color: var(--ac-global-text-color-900);
.react-aria-DateInput {
display: flex;
padding: var(--date-field-vertical-padding)
var(--date-field-horizontal-padding);
border: var(--ac-global-border-size-thin) solid
var(--ac-global-input-field-border-color);
border-radius: var(--ac-global-rounding-small);
background-color: var(--ac-global-input-field-background-color);
width: fit-content;
min-width: 150px;
white-space: nowrap;
forced-color-adjust: none;
&[data-focus-within] {
outline: 1px solid var(--ac-global-color-primary);
outline-offset: -1px;
}
}
.react-aria-DateSegment {
padding: 0 2px;
font-variant-numeric: tabular-nums;
text-align: end;
color: var(--ac-global-text-color-900);
&[data-type="literal"] {
padding: 0;
}
&[data-placeholder] {
color: var(--ac-text-color-placeholder);
font-style: italic;
}
&:focus {
color: var(--ac-highlight-foreground);
background: var(--ac-highlight-background);
outline: none;
border-radius: var(--ac-global-rounding-small);
caret-color: transparent;
}
}
`;
function DateField<T extends DateValue>(
props: DateFieldProps<T>,
ref: Ref<HTMLDivElement>
) {
return (
<AriaDateField css={css(fieldBaseCSS, dateFieldCSS)} {...props} ref={ref} />
);
}

const _DateField = forwardRef(DateField);
export { _DateField as DateField };
72 changes: 72 additions & 0 deletions app/src/components/datetime/TimeField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { forwardRef, Ref } from "react";
import {
TimeField as AriaTimeField,
TimeFieldProps as AriaTimeFieldProps,
TimeValue,
} from "react-aria-components";
import { css } from "@emotion/react";

import { fieldBaseCSS } from "../field/styles";

export type TimeFieldProps<T extends TimeValue> = AriaTimeFieldProps<T>;

const timeFieldCSS = css`
--date-field-vertical-padding: 6px;
--date-field-horizontal-padding: 8px;
color: var(--ac-global-text-color-900);
.react-aria-DateInput {
display: flex;
padding: var(--date-field-vertical-padding)
var(--date-field-horizontal-padding);
border: var(--ac-global-border-size-thin) solid
var(--ac-global-input-field-border-color);
border-radius: var(--ac-global-rounding-small);
background-color: var(--ac-global-input-field-background-color);
width: fit-content;
min-width: 150px;
white-space: nowrap;
forced-color-adjust: none;
&[data-focus-within] {
outline: 1px solid var(--ac-global-color-primary);
outline-offset: -1px;
}
}
.react-aria-DateSegment {
padding: 0 2px;
font-variant-numeric: tabular-nums;
text-align: end;
color: var(--ac-global-text-color-900);
&[data-type="literal"] {
padding: 0;
}
&[data-placeholder] {
color: var(--ac-text-color-placeholder);
font-style: italic;
}
&:focus {
color: var(--ac-highlight-foreground);
background: var(--ac-highlight-background);
outline: none;
border-radius: var(--ac-global-rounding-small);
caret-color: transparent;
}
}
`;

function TimeField<T extends TimeValue>(
props: TimeFieldProps<T>,
ref: Ref<HTMLDivElement>
) {
return (
<AriaTimeField css={css(fieldBaseCSS, timeFieldCSS)} {...props} ref={ref} />
);
}

const _TimeField = forwardRef(TimeField);
export { _TimeField as TimeField };
3 changes: 3 additions & 0 deletions app/src/components/datetime/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export { DateInput, DateSegment } from "react-aria-components";
export * from "./LastNTimeRangeContext";
export * from "./LastNTimeRangePicker";
export * from "./ConnectedLastNTimeRangePicker";
export * from "./DateField";
export * from "./TimeField";
12 changes: 11 additions & 1 deletion app/src/components/field/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const fieldBaseCSS = css`
border-color: var(--ac-global-color-danger);
}
&::placeholder {
color: var(--ac-global-text-color-300);
color: var(--ac-text-color-placeholder);
font-style: italic;
}
}
Expand All @@ -59,3 +59,13 @@ export const fieldBaseCSS = css`
color: var(--ac-global-color-danger);
}
`;

export const fieldPopoverCSS = css`
width: var(--trigger-width);
background-color: var(--ac-global-menu-background-color);
border-radius: var(--ac-global-rounding-small);
color: var(--ac-global-text-color-900);
box-shadow: 0px 4px 10px var(--px-overlay-shadow-color);
border: 1px solid var(--ac-global-menu-border-color);
max-height: inherit;
`;
3 changes: 3 additions & 0 deletions app/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type {
LabelProps,
FieldErrorProps,
DialogTriggerProps,
DateValue,
TimeValue,
} from "react-aria-components";
export { classNames } from "@arizeai/components";

Expand All @@ -34,3 +36,4 @@ export * from "./content";
export * from "./textfield";
export * from "./tag";
export * from "./overlay";
export * from "./datetime";
35 changes: 35 additions & 0 deletions app/stories/DateField.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import { Meta, StoryFn } from "@storybook/react";

import {
DateField,
DateFieldProps,
DateInput,
DateSegment,
DateValue,
Label,
} from "@phoenix/components";

const meta: Meta = {
title: "DateField",
component: DateField,
parameters: {
layout: "centered",
},
};

export default meta;

const Template: StoryFn<DateFieldProps<DateValue>> = (args) => (
<DateField {...args}>
<Label>Birth date</Label>
<DateInput>{(segment) => <DateSegment segment={segment} />}</DateInput>
</DateField>
);

/**
* DateFields are used to type in dates within the UI
*/
export const Default = Template.bind({});

Default.args = {};
24 changes: 23 additions & 1 deletion app/stories/Gallery.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
Button,
ComboBox,
ComboBoxItem,
DateField,
DateInput,
DateSegment,
Disclosure,
DisclosureGroup,
DisclosurePanel,
Expand All @@ -19,6 +22,7 @@ import {
TagList,
Text,
TextField,
TimeField,
View,
} from "@phoenix/components";

Expand Down Expand Up @@ -61,7 +65,11 @@ const Template: StoryFn = () => {
gap="size-200"
alignItems={direction === "row" ? "center" : "start"}
>
<ComboBox label="Ice cream flavor" description={"pick a flavor"}>
<ComboBox
label="Ice cream flavor"
description={"pick a flavor"}
placeholder="Select a flavor"
>
<ComboBoxItem textValue="Chocolate" key={"chocolate"}>
Chocolate
</ComboBoxItem>
Expand All @@ -75,6 +83,20 @@ const Template: StoryFn = () => {
Vanilla
</ComboBoxItem>
</ComboBox>
<DateField>
<Label>Birth date</Label>
<DateInput>
{(segment) => <DateSegment segment={segment} />}
</DateInput>
<Text slot="description">your birthday</Text>
</DateField>
<TimeField>
<Label>Event Time</Label>
<DateInput>
{(segment) => <DateSegment segment={segment} />}
</DateInput>
<Text slot="description">the time of your event</Text>
</TimeField>
<Picker label="Toppings" size="compact" description={"pick a flavor"}>
<Item key="chocolate">Chocolate</Item>
<Item key="mint">Mint</Item>
Expand Down
35 changes: 35 additions & 0 deletions app/stories/TimeField.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import { Meta, StoryFn } from "@storybook/react";

import {
DateInput,
DateSegment,
Label,
TimeField,
TimeFieldProps,
TimeValue,
} from "@phoenix/components";

const meta: Meta = {
title: "TimeField",
component: TimeField,
parameters: {
layout: "centered",
},
};

export default meta;

const Template: StoryFn<TimeFieldProps<TimeValue>> = (args) => (
<TimeField {...args}>
<Label>Event time</Label>
<DateInput>{(segment) => <DateSegment segment={segment} />}</DateInput>
</TimeField>
);

/**
* DateFields are used to type in dates within the UI
*/
export const Default = Template.bind({});

Default.args = {};

0 comments on commit b31d7ca

Please sign in to comment.