Skip to content

Commit

Permalink
feat: add date and time types to input (#449)
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysztofzuraw authored May 30, 2023
1 parent 342a563 commit c19b0f6
Show file tree
Hide file tree
Showing 27 changed files with 187 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ module.exports = {
"external",
"builtin",
"internal",
"sibling",
"parent",
"sibling",
"index",
],
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"@storybook/react-vite": "^7.0.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@types/react": "^17.0.50",
"@types/react-dom": "^17.0.17",
"@types/testing-library__jest-dom": "^5.14.6",
Expand Down
42 changes: 42 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
import { RemoveIcon, SearchIcon } from "../Icons";
import { Button } from "./Button";

const meta: Meta<typeof Button> = {
title: "Components / Button",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { ButtonHTMLAttributes, forwardRef, ReactNode } from "react";

import { classNames } from "~/utils";

import { isFixedWidth } from "./utils";
import { Box, PropsWithBox } from "../Box";
import { isFixedWidth } from "./utils";
import { button, ButtonVariants } from "./Button.css";

export type ButtonProps = PropsWithBox<
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/utils.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SearchIcon } from "../Icons";
import { ButtonProps } from "./Button";
import { isFixedWidth } from "./utils";
import { SearchIcon } from "../Icons";

describe("isFixedWidth", () => {
it("Returns true when only icon is provided with no children", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
} from "@radix-ui/react-checkbox";
import { ReactNode, forwardRef } from "react";
import { classNames } from "~/utils";
import { Box } from "../Box";
import { CheckedIcon } from "./CheckedIcon";
import { IndeterminateIcon } from "./IndeterminateIcon";
import { Box } from "../Box";
import { commonCheckbox, defaultCheckbox, errorCheckbox } from "./Checkbox.css";

export type CheckboxProps = RadixCheckboxProps & {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Chip/Chip.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Meta, StoryObj } from "@storybook/react";

import { Chip } from "./Chip";
import { Box } from "../Box";
import { Text } from "../Text";
import { Chip } from "./Chip";

const meta: Meta<typeof Chip> = {
title: "Components / Chip",
Expand Down
8 changes: 4 additions & 4 deletions src/components/Combobox/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { forwardRef, InputHTMLAttributes, ReactNode, useRef } from "react";

import { classNames } from "~/utils";

import { ComboboxWrapper } from "./ComboboxWrapper";
import { Box, List, PropsWithBox, Text } from "..";
import { helperTextRecipe, inputRecipe, InputVariants } from "../BaseInput";
import { listItemStyle, listStyle, listWrapperRecipe } from "../BaseSelect";
import {
ChangeHandler,
InputValue,
Option,
useComboboxEvents,
} from "./useComboboxEvents";
import { Box, List, PropsWithBox, Text } from "..";
import { helperTextRecipe, inputRecipe, InputVariants } from "../BaseInput";
import { listItemStyle, listStyle, listWrapperRecipe } from "../BaseSelect";
import { ComboboxWrapper } from "./ComboboxWrapper";

export type ComboboxProps = PropsWithBox<
Omit<
Expand Down
2 changes: 1 addition & 1 deletion src/components/Combobox/ComboboxWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { ReactNode } from "react";
import { classNames } from "~/utils";

import { sprinkles } from "~/theme";
import { Option } from "./useComboboxEvents";
import { LabelVariants, labelRecipe, spanRecipe } from "../BaseInput";
import { toggleIconStyle } from "../BaseSelect";
import { Box } from "../Box";
import { ArrowDownIcon } from "../Icons";
import { Option } from "./useComboboxEvents";

type ComboboxWrapperProps = LabelVariants & {
id?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/components/Expression/AutosizeInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {
useEffect,
useRef,
} from "react";
import { useTextMetrics } from "./useTextMetrics";
import { Box } from "../../Box";
import {
input as inputStyles,
inputContainer as inputContainerStyles,
} from "../Expression.css";
import { useTextMetrics } from "./useTextMetrics";

export interface CalculationChange {
left: number;
Expand Down
2 changes: 1 addition & 1 deletion src/components/Expression/OperandAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import {
} from "react";
import * as Portal from "@radix-ui/react-portal";
import { useOutsideClick } from "~/utils/useClickOutside";
import { Box } from "../Box";
import {
AutosizeInput,
CalculationChange,
InputContainer,
} from "./AutosizeInput";
import { DropdownContent } from "./Dropdown";
import { Box } from "../Box";
import {
dropdownItem as dropdownItemStyles,
autocompleteContainer as autocompleteContainerStyles,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Expression/OperandNumber.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InputHTMLAttributes, ReactNode } from "react";
import { AutosizeInput, InputContainer } from "./AutosizeInput";
import { Box } from "../Box";
import { AutosizeInput, InputContainer } from "./AutosizeInput";
import { numberInputSign as numberInputSignStyles } from "./Expression.css";

interface SignProps {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Expression/OperandRange.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { InputHTMLAttributes, ChangeEvent } from "react";
import { Box } from "../Box";
import { AutosizeInput, InputContainer } from "./AutosizeInput";
import { Sign } from "./OperandNumber";
import { Box } from "../Box";
import { rangeItem as rangeItemStyles } from "./Expression.css";

type OperandRangeProps = Omit<
Expand Down
75 changes: 75 additions & 0 deletions src/components/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@ export const Number: Story = {
value: "0",
label: null,
},
parameters: {
docs: {
source: {
code: `
const [value, setValue] = useState("0");
<Input
type="number"
value={value}
onChange={(e) => setValue(e.target.value)}
endAdornment={<Text variant="caption">USD</Text>}
/>`,
},
},
},
};

export const NumberWithLabel: Story = {
Expand All @@ -154,4 +169,64 @@ export const NumberWithLabel: Story = {
label: "Price",
value: 0.9,
},
parameters: {
docs: {
source: {
code: `
const [value, setValue] = useState("0.9");
<Input
label="Price"
type="number"
value={value}
onChange={(e) => setValue(e.target.value)}
endAdornment={<Text variant="caption">USD</Text>}
/>`,
},
},
},
};

export const Date: Story = {
...InputTemplate,
args: {
type: "date",
value: "",
},
parameters: {
docs: {
source: {
code: `
const [value, setValue] = useState("");
<Input
type="date"
value={value}
onChange={(e) => setValue(e.target.value)}
/>`,
},
},
},
};

export const Time: Story = {
...InputTemplate,
args: {
type: "time",
value: "",
},
parameters: {
docs: {
source: {
code: `
const [value, setValue] = useState("");
<Input
type="time"
value={value}
onChange={(e) => setValue(e.target.value)}
/>`,
},
},
},
};
10 changes: 5 additions & 5 deletions src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { FocusEvent, InputHTMLAttributes, ReactNode, forwardRef } from "react";

import { classNames } from "~/utils";

import { Box, PropsWithBox, Text } from "..";
import { InputVariants, helperTextRecipe, inputRecipe } from "../BaseInput";

import { InputWrapper, useStateEvents } from "./InputWrapper";
import { checkIfValidNumberInput } from "./helpers";
import { InputVariants, helperTextRecipe, inputRecipe } from "../BaseInput";
import { Box, PropsWithBox } from "../Box";
import { Text } from "../Text";

export type InputProps = PropsWithBox<
Omit<
Expand All @@ -15,7 +15,7 @@ export type InputProps = PropsWithBox<
> & {
label?: ReactNode;
error?: boolean;
type?: "text" | "number" | "url" | "email" | "password";
type?: "text" | "number" | "url" | "email" | "password" | "date" | "time";
helperText?: ReactNode;
endAdornment?: ReactNode;
}
Expand Down Expand Up @@ -52,7 +52,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
value: inputValue,
active,
typed,
} = useStateEvents(value, onChange);
} = useStateEvents(value, type, onChange);

return (
<Box
Expand Down
23 changes: 23 additions & 0 deletions src/components/Input/InputWrapper.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { renderHook } from "@testing-library/react-hooks";

import { useStateEvents } from "./InputWrapper";

describe("useStateEvents", () => {
it.each([
{ type: "text", value: "Hello", expected: true },
{ type: "number", value: "123", expected: true },
{ type: "text", value: undefined, expected: false },
{ type: "number", value: undefined, expected: false },
{ type: "date", value: "2021-01-01", expected: true },
{ type: "time", value: "12:00", expected: true },
{ type: "date", value: undefined, expected: true },
{ type: "time", value: undefined, expected: true },
] as const)(
`should return typed=$expected if type is $type with value $value`,
({ type, value, expected }) => {
const { result } = renderHook(() => useStateEvents(value, type));

expect(result.current.typed).toBe(expected);
}
);
});
10 changes: 8 additions & 2 deletions src/components/Input/InputWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@
Do not expose this file, it's for internal purposes only.
*/
import { ReactNode, useState } from "react";
import { Box } from "~/components/Box";

import { Box } from "~/components";
import { classNames } from "~/utils";
import { LabelVariants, labelRecipe, spanRecipe } from "../BaseInput";

import { InputProps } from "./Input";
import { checkIfDateTimeInput } from "./helpers";

type InputValue = string | number | readonly string[] | undefined;
type ChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => void;

export const useStateEvents = (
value: InputValue,
type: InputProps["type"],
changeHandler?: ChangeHandler
) => {
const [active, setActive] = useState(false);
const typed = Boolean(value || active);
// do not scale label down if input is date or time
const typed = checkIfDateTimeInput(type) ? true : Boolean(value || active);

const onFocus = () => setActive(true);
const onBlur = () => setActive(false);
Expand Down
5 changes: 5 additions & 0 deletions src/components/Input/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { KeyboardEvent } from "react";

import { InputProps } from "./Input";

// Check if input type number is valid as input type number doesn't currently work in browsers like Safari and Firefox
export const checkIfValidNumberInput = (event: KeyboardEvent<HTMLElement>) => {
const allowedCharacter =
/^[\d.,]*$|(Backspace|Tab|Delete|ArrowLeft|ArrowRight|ArrowDown|ArrowUp)/;

return !event.key.match(allowedCharacter) && event.preventDefault();
};

export const checkIfDateTimeInput = (type: InputProps["type"]) =>
["date", "time"].includes(type ?? "");
Loading

2 comments on commit c19b0f6

@vercel
Copy link

@vercel vercel bot commented on c19b0f6 May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on c19b0f6 May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

macaw-ui – ./legacy

macaw-ui-saleorcommerce.vercel.app
macaw-ui.vercel.app
macaw-ui-git-canary-saleorcommerce.vercel.app

Please sign in to comment.