diff --git a/packages/constants/component/name.ts b/packages/constants/component/name.ts index e32fb8505..c583f9b8f 100644 --- a/packages/constants/component/name.ts +++ b/packages/constants/component/name.ts @@ -68,6 +68,7 @@ export const ComponentName = { ChatCard: "WizChatCard", ChatForm: "WizChatForm", ChatItem: "WizChatItem", + NumberInput: "WizNumberInput", Notification: "WizNotification", NotificationList: "WizNotificationList", NotificationPanel: "WizNotificationPanel", diff --git a/packages/styles/bases/number-input.css.ts b/packages/styles/bases/number-input.css.ts new file mode 100644 index 000000000..bd7cc7c9c --- /dev/null +++ b/packages/styles/bases/number-input.css.ts @@ -0,0 +1,69 @@ +import { style } from "@vanilla-extract/css"; +import { THEME } from "@wizleap-inc/wiz-ui-constants"; + +const BORDER_WIDTH = "1px"; + +export const datePickerStyle = style({ + width: "100%", + borderRadius: THEME.spacing.xs2, + boxSizing: "border-box", + padding: `calc(${THEME.spacing.xs} - ${BORDER_WIDTH}) ${THEME.spacing.xs}`, + fontSize: THEME.fontSize.sm, + lineHeight: THEME.fontSize.xl3, +}); + +export const InputStyle = style({ + "::-webkit-outer-spin-button": { + appearance: "none", + margin: 0, + }, + "::-webkit-inner-spin-button": { + appearance: "none", + margin: 0, + }, + MozAppearance: "textfield", + minWidth: "30%", + border: "none", + outline: "none", + padding: `${THEME.spacing.xs2} ${THEME.spacing.no}`, + lineHeight: THEME.fontSize.xl, + flexGrow: 1, + fontSize: THEME.fontSize.sm, + color: THEME.color.gray["800"], + "::placeholder": { + color: THEME.color.gray["500"], + userSelect: "none", + }, + ":disabled": { + cursor: "not-allowed", + backgroundColor: THEME.color.gray["300"], + }, +}); + +export const ButtonStyle = style({ + lineHeight: 0.2, + position: "relative", + cursor: "pointer", + padding: THEME.spacing.no, + borderRadius: THEME.spacing.xs2, + border: "none", + background: "transparent", + fill: THEME.color.gray["800"], + "@media": { + "(any-hover: hover)": { + ":hover": { + backgroundColor: THEME.color.green["300"], + fill: THEME.color.green["800"], + }, + }, + }, + ":active": { + backgroundColor: THEME.color.green["800"], + fill: THEME.color.white["800"], + }, +}); + +export const ArrowIconStyle = style({ + transform: "scale(2)", + pointerEvents: "none", +}); diff --git a/packages/wiz-ui-react/src/components/base/inputs/number-input/components/index.ts b/packages/wiz-ui-react/src/components/base/inputs/number-input/components/index.ts new file mode 100644 index 000000000..5f0382f95 --- /dev/null +++ b/packages/wiz-ui-react/src/components/base/inputs/number-input/components/index.ts @@ -0,0 +1 @@ +export { WizNumberInput } from "./number-input"; diff --git a/packages/wiz-ui-react/src/components/base/inputs/number-input/components/number-input.tsx b/packages/wiz-ui-react/src/components/base/inputs/number-input/components/number-input.tsx new file mode 100644 index 000000000..92ff27c98 --- /dev/null +++ b/packages/wiz-ui-react/src/components/base/inputs/number-input/components/number-input.tsx @@ -0,0 +1,92 @@ +import { ARIA_LABELS, ComponentName } from "@wizleap-inc/wiz-ui-constants"; +import * as styles from "@wizleap-inc/wiz-ui-styles/bases/number-input.css"; +import { + fillStyle, + fontSizeStyle, + inputBorderStyle, +} from "@wizleap-inc/wiz-ui-styles/commons"; +import clsx from "clsx"; +import { ChangeEvent, useRef } from "react"; + +import { WizIArrowDropDown, WizIArrowDropUp, WizVStack } from "@/components"; +import { BaseProps } from "@/types"; + +type Props = BaseProps & { + value: number; + onChange: (e: ChangeEvent) => void; + placeholder?: string; + disabled?: boolean; + width?: string; + error?: boolean; + min?: number; + max?: number; + step?: number; + precision?: number; +}; + +const NumberInput = (props: Props) => { + const { value, onChange, ...rest } = props; + const inputRef = useRef(null); + + const handleStepUp = () => inputRef.current?.stepUp(); + const handleStepDown = () => inputRef.current?.stepDown(); + + return ( +
+ + + + + + +
+ ); +}; + +export default NumberInput; + +NumberInput.displayName = ComponentName.NumberInput; + +export const WizNumberInput = NumberInput; diff --git a/packages/wiz-ui-react/src/components/base/inputs/number-input/index.ts b/packages/wiz-ui-react/src/components/base/inputs/number-input/index.ts new file mode 100644 index 000000000..40b494c5f --- /dev/null +++ b/packages/wiz-ui-react/src/components/base/inputs/number-input/index.ts @@ -0,0 +1 @@ +export * from "./components"; diff --git a/packages/wiz-ui-react/src/components/base/inputs/number-input/stories/number-input.stories.tsx b/packages/wiz-ui-react/src/components/base/inputs/number-input/stories/number-input.stories.tsx new file mode 100644 index 000000000..a0154ff2f --- /dev/null +++ b/packages/wiz-ui-react/src/components/base/inputs/number-input/stories/number-input.stories.tsx @@ -0,0 +1,34 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { Meta, StoryObj } from "@storybook/react"; +import { userEvent, within } from "@storybook/testing-library"; + +import { WizNumberInput } from ".."; + +const meta: Meta = { + title: "Base/Input/NumberInput", + component: WizNumberInput, +}; + +export default meta; +type Story = StoryObj; + +const Template: Story = { + render: (args) => { + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const textbox = canvas.getByRole("textbox"); + userEvent.click(textbox); + textbox.blur(); + }, +}; + +export const Default: Story = { + ...Template, + args: {}, +};