From 75a3254656ac773ba5b2087b5763830e79c9c60e Mon Sep 17 00:00:00 2001 From: Ha Gia Phat Date: Mon, 30 Oct 2023 09:48:35 +0700 Subject: [PATCH] fix(number-field): remove zagjs and use react-aria (#113) --- compiler/scaffold.config.js | 8 +- package.json | 7 +- packages/react/package.json | 33 +- .../react/scaffolds/number-field/index.ts | 1 + .../number-field.css.ts} | 4 +- .../scaffolds/number-field/number-field.tsx | 107 ++++++ .../number-field/number-field.types.tsx | 39 ++ .../react/scaffolds/number-input/index.ts | 1 - .../scaffolds/number-input/number-input.tsx | 116 ------ .../number-input/number-input.types.tsx | 65 ---- ...ut.stories.tsx => NumberField.stories.tsx} | 22 +- packages/react/tsconfig.json | 2 +- packages/vue/package.json | 1 - src/ui/add-liquidity/add-liquidity.lite.tsx | 20 +- .../asset-withdraw-tokens.lite.tsx | 8 +- src/ui/bonding-more/bonding-more.lite.tsx | 19 +- src/ui/bonding-more/bonding-more.types.tsx | 4 +- src/ui/liquid-staking/liquid-staking.lite.tsx | 12 +- .../liquid-staking/liquid-staking.types.tsx | 2 +- .../nft-make-offer/nft-make-offer.types.tsx | 6 +- .../nft-minimum-offer.types.tsx | 7 +- src/ui/nft-mint/nft-mint.lite.tsx | 18 +- src/ui/nft-mint/nft-mint.types.tsx | 2 +- .../overview-transfer.lite.tsx | 16 +- .../overview-transfer.types.tsx | 7 +- src/ui/swap-price/swap-price.lite.tsx | 2 +- src/ui/swap-price/swap-price.types.tsx | 6 +- src/ui/swap-token/swap-token.lite.tsx | 31 +- src/ui/swap-token/swap-token.types.tsx | 5 +- src/ui/token-input/token-input.lite.tsx | 28 +- src/ui/token-input/token-input.types.tsx | 8 +- .../token-number-field.lite.tsx | 16 +- .../token-number-field.types.tsx | 8 +- src/ui/transfer-item/transfer-item.lite.tsx | 38 +- src/ui/transfer-item/transfer-item.types.tsx | 8 +- yarn.lock | 360 ++++++++++++------ 36 files changed, 573 insertions(+), 464 deletions(-) create mode 100644 packages/react/scaffolds/number-field/index.ts rename packages/react/scaffolds/{number-input/number-input.css.ts => number-field/number-field.css.ts} (81%) create mode 100644 packages/react/scaffolds/number-field/number-field.tsx create mode 100644 packages/react/scaffolds/number-field/number-field.types.tsx delete mode 100644 packages/react/scaffolds/number-input/index.ts delete mode 100644 packages/react/scaffolds/number-input/number-input.tsx delete mode 100644 packages/react/scaffolds/number-input/number-input.types.tsx rename packages/react/stories/{NumberInput.stories.tsx => NumberField.stories.tsx} (52%) diff --git a/compiler/scaffold.config.js b/compiler/scaffold.config.js index 4b5ddc00..a41ee42c 100644 --- a/compiler/scaffold.config.js +++ b/compiler/scaffold.config.js @@ -75,13 +75,13 @@ module.exports = { path: "../change-chain-combobox", }, }, - "number-input": { + "number-field": { jsxMap: { - ScaffoldNumberInput: "NumberInput", + ScaffoldNumberField: "NumberField", }, import: { - imports: { NumberInput: "default" }, - path: "../number-input", + imports: { NumberField: "default" }, + path: "../number-field", }, }, }; diff --git a/package.json b/package.json index 5836a434..83bbdcd3 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,10 @@ "@formkit/auto-animate": "1.0.0-beta.6", "@parcel/packager-ts": "^2.10.0", "@parcel/watcher": "^2.3.0", + "@react-aria/i18n": "^3.8.4", + "@react-aria/numberfield": "^3.9.1", + "@react-aria/utils": "^3.21.1", + "@react-stately/numberfield": "^3.6.2", "@swc/helpers": "^0.5.0", "@types/animejs": "^3.1.7", "@types/node": "^20.5.8", @@ -52,9 +56,6 @@ "@vanilla-extract/dynamic": "^2.0.3", "@vitejs/plugin-vue": "^4.1.0", "@vue/babel-preset-app": "^5.0.8", - "@zag-js/number-input": "^0.26.0", - "@zag-js/react": "^0.26.0", - "@zag-js/vue": "^0.26.0", "autoprefixer": "^10.4.14", "command-line-args": "^5.2.1", "copy-to-clipboard": "^3.3.3", diff --git a/packages/react/package.json b/packages/react/package.json index fe943731..25074bfa 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -15,8 +15,33 @@ "import": "./dist/index.d.ts", "default": "./dist/index.d.ts" }, + "development": { + "worker": { + "module": "./dist/interchain-ui-kit-react.development.worker.esm.js", + "import": "./dist/interchain-ui-kit-react.development.worker.cjs.mjs", + "default": "./dist/interchain-ui-kit-react.development.worker.cjs.js" + }, + "browser": { + "module": "./dist/interchain-ui-kit-react.browser.development.esm.js", + "import": "./dist/interchain-ui-kit-react.browser.development.cjs.mjs", + "default": "./dist/interchain-ui-kit-react.browser.development.cjs.js" + }, + "module": "./dist/interchain-ui-kit-react.development.esm.js", + "import": "./dist/interchain-ui-kit-react.development.cjs.mjs", + "default": "./dist/interchain-ui-kit-react.development.cjs.js" + }, + "worker": { + "module": "./dist/interchain-ui-kit-react.worker.esm.js", + "import": "./dist/interchain-ui-kit-react.worker.cjs.mjs", + "default": "./dist/interchain-ui-kit-react.worker.cjs.js" + }, + "browser": { + "module": "./dist/interchain-ui-kit-react.browser.esm.js", + "import": "./dist/interchain-ui-kit-react.browser.cjs.mjs", + "default": "./dist/interchain-ui-kit-react.browser.cjs.js" + }, "module": "./dist/interchain-ui-kit-react.esm.js", - "import": "./dist/interchain-ui-kit-react.esm.js", + "import": "./dist/interchain-ui-kit-react.cjs.mjs", "default": "./dist/interchain-ui-kit-react.cjs.js" }, "./styles": "./dist/interchain-ui-kit-react.cjs.css", @@ -46,12 +71,14 @@ "@floating-ui/dom": "^1.5.2", "@floating-ui/react": "^0.26.0", "@formkit/auto-animate": "1.0.0-beta.6", + "@react-aria/i18n": "^3.8.4", + "@react-aria/numberfield": "^3.9.1", + "@react-aria/utils": "^3.21.1", + "@react-stately/numberfield": "^3.6.2", "@vanilla-extract/css": "^1.12.0", "@vanilla-extract/css-utils": "^0.1.3", "@vanilla-extract/dynamic": "^2.0.3", "@vanilla-extract/recipes": "^0.4.0", - "@zag-js/number-input": "^0.26.0", - "@zag-js/react": "^0.26.0", "animejs": "^3.2.1", "bignumber.js": "^9.1.1", "clsx": "^1.2.1", diff --git a/packages/react/scaffolds/number-field/index.ts b/packages/react/scaffolds/number-field/index.ts new file mode 100644 index 00000000..95ea7a95 --- /dev/null +++ b/packages/react/scaffolds/number-field/index.ts @@ -0,0 +1 @@ +export { default } from "./number-field"; diff --git a/packages/react/scaffolds/number-input/number-input.css.ts b/packages/react/scaffolds/number-field/number-field.css.ts similarity index 81% rename from packages/react/scaffolds/number-input/number-input.css.ts rename to packages/react/scaffolds/number-field/number-field.css.ts index 402d8180..baf81981 100644 --- a/packages/react/scaffolds/number-input/number-input.css.ts +++ b/packages/react/scaffolds/number-field/number-field.css.ts @@ -11,12 +11,12 @@ export const borderless = style({ }, }); -export const withStartAddon = style({ +export const withDecrementButton = style({ borderTopLeftRadius: 0, borderBottomLeftRadius: 0, }); -export const withEndAddon = style({ +export const withIncrementButton = style({ borderTopRightRadius: 0, borderBottomRightRadius: 0, }); diff --git a/packages/react/scaffolds/number-field/number-field.tsx b/packages/react/scaffolds/number-field/number-field.tsx new file mode 100644 index 00000000..064cea7d --- /dev/null +++ b/packages/react/scaffolds/number-field/number-field.tsx @@ -0,0 +1,107 @@ +import React, { useState, useId, forwardRef } from "react"; +import { useNumberFieldState } from "@react-stately/numberfield"; +import { useNumberField } from "@react-aria/numberfield"; +import { useLocale } from "@react-aria/i18n"; +import { mergeRefs } from "@react-aria/utils"; + +import clx from "clsx"; +import { + inputStyles, + inputSizes, + inputIntent, + inputRootIntent, + rootInput, + rootInputFocused, +} from "@/ui/text-field/text-field.css"; +import FieldLabel from "@/ui/field-label"; +import Stack from "@/ui/stack"; +import Box from "@/ui/box"; +import useTheme from "../hooks/use-theme"; +import * as styles from "./number-field.css"; +import type { NumberInputProps } from "./number-field.types"; + +const NumberInput = forwardRef( + (props, forwardedRef) => { + const { + id = useId(), + label, + isDisabled, + size = "sm", + intent = "default", + } = props; + + const { theme } = useTheme(); + const { locale } = useLocale(); + const [isFocused, setIsFocused] = useState(false); + + const state = useNumberFieldState({ + ...props, + locale, + onFocusChange(focused) { + setIsFocused(focused); + }, + }); + + const inputRef = React.useRef(null); + const handleRef = mergeRefs(inputRef, forwardedRef); + + const { + labelProps, + groupProps, + inputProps, + incrementButtonProps, + decrementButtonProps, + } = useNumberField(props, state, inputRef); + + return ( + + + {label && } + +
+ {props.canDecrement && React.isValidElement(props.decrementButton) + ? React.cloneElement(props.decrementButton, decrementButtonProps) + : props?.decrementButton} + + + + {props.canIncrement && React.isValidElement(props.incrementButton) + ? React.cloneElement(props.incrementButton, incrementButtonProps) + : props?.incrementButton} +
+
+
+ ); + } +); + +export default NumberInput; diff --git a/packages/react/scaffolds/number-field/number-field.types.tsx b/packages/react/scaffolds/number-field/number-field.types.tsx new file mode 100644 index 00000000..4c160e49 --- /dev/null +++ b/packages/react/scaffolds/number-field/number-field.types.tsx @@ -0,0 +1,39 @@ +import { ReactNode } from "react"; +import type { Sprinkles } from "@/styles/rainbow-sprinkles.css"; +import type { AriaNumberFieldProps } from "@react-aria/numberfield"; + +export interface NumberInputProps { + // ==== Core logic props + defaultValue?: number; + value?: number; + minValue?: number; + maxValue?: number; + incrementButton?: ReactNode; + decrementButton?: ReactNode; + canIncrement?: boolean; + canDecrement?: boolean; + // ==== Form field props + id?: string; + isDisabled?: boolean; + isReadOnly?: boolean; + autoFocus?: boolean; + formatOptions?: AriaNumberFieldProps["formatOptions"]; + name?: string; + label?: string; + onChange?: (value: number) => void; + onBlur?: (e?: any) => void; + onFocus?: (e?: any) => void; + onFocusChange?: (isFocused: boolean) => void; + onKeyDown?: (e?: any) => void; + onKeyUp?: (e?: any) => void; + // ==== Style props + textAlign?: Sprinkles["textAlign"]; + fontSize?: Sprinkles["fontSize"]; + size?: "sm" | "md"; + placeholder?: string | undefined; + intent?: "default" | "error"; + className?: string; + inputContainer?: string; + inputClassName?: string; + borderless?: boolean; +} diff --git a/packages/react/scaffolds/number-input/index.ts b/packages/react/scaffolds/number-input/index.ts deleted file mode 100644 index ebc80972..00000000 --- a/packages/react/scaffolds/number-input/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./number-input"; diff --git a/packages/react/scaffolds/number-input/number-input.tsx b/packages/react/scaffolds/number-input/number-input.tsx deleted file mode 100644 index 7b0857cd..00000000 --- a/packages/react/scaffolds/number-input/number-input.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React, { useId, forwardRef } from "react"; -import clx from "clsx"; -import * as numberInput from "@zag-js/number-input"; -import { useMachine, normalizeProps } from "@zag-js/react"; -import { NumberInputProps } from "./number-input.types"; -import { - inputStyles, - inputSizes, - inputIntent, - inputRootIntent, - rootInput, - rootInputFocused, -} from "@/ui/text-field/text-field.css"; -import FieldLabel from "@/ui/field-label"; -import Stack from "@/ui/stack"; -import Box from "@/ui/box"; -import useTheme from "../hooks/use-theme"; -import * as styles from "./number-input.css"; - -const NumberInput = forwardRef( - (props, forwardedRef) => { - const { - id = useId(), - label, - disabled, - readOnly, - value, - min, - max, - step, - onChange, - onFocus, - size = "sm", - intent = "default", - name, - minFractionDigits = 0, - maxFractionDigits = 6, - } = props; - - const { theme } = useTheme(); - - const [state, send] = useMachine( - numberInput.machine({ - id, - disabled, - readOnly, - value, - min, - max, - step, - name, - formatOptions: { - maximumFractionDigits: maxFractionDigits, - minimumFractionDigits: minFractionDigits, - }, - onValueChange: onChange, - onFocusChange: onFocus, - }) - ); - - const api = numberInput.connect(state, send, normalizeProps); - - return ( -
- - {label && ( - - )} - -
- {props.canDecrese && React.isValidElement(props.startAddon) - ? React.cloneElement(props.startAddon, api.decrementTriggerProps) - : props?.startAddon} - - - - {props.canIncrease && React.isValidElement(props.endAddon) - ? React.cloneElement(props.endAddon, api.decrementTriggerProps) - : props?.endAddon} -
-
-
- ); - } -); - -export default NumberInput; diff --git a/packages/react/scaffolds/number-input/number-input.types.tsx b/packages/react/scaffolds/number-input/number-input.types.tsx deleted file mode 100644 index 3cae755e..00000000 --- a/packages/react/scaffolds/number-input/number-input.types.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { ReactNode } from "react"; -import type { Sprinkles } from "@/styles/rainbow-sprinkles.css"; - -type Value = { - value: string; - valueAsNumber: number; -}; - -type FocusChangeValue = { - focused: boolean; -} & Value; - -export interface NumberInputProps { - id?: string; - /** - * Whether the number input is disabled. - */ - disabled?: boolean; - /** - * Whether the number input is readonly - */ - readOnly?: boolean; - /** - * The value of the input - */ - value: string; - /** - * The minimum value of the number input - */ - min?: number; - /** - * The maximum value of the number input - */ - max?: number; - /** - * The amount to increment or decrement the value by - */ - step?: number; - /** - * Function invoked when the value changes - */ - onChange?: (details: Value) => void; - /** - * Function invoked when the number input is focused - */ - onFocus?: (details: FocusChangeValue) => void; - textAlign?: Sprinkles["textAlign"]; - fontSize?: Sprinkles["fontSize"]; - size?: "sm" | "md"; - placeholder?: string | undefined; - intent?: "default" | "error"; - className?: string; - inputContainer?: string; - inputClassName?: string; - label?: string; - borderless?: boolean; - startAddon?: ReactNode; - endAddon?: ReactNode; - name?: string; - precision?: number; - canDecrese?: boolean; - canIncrease?: boolean; - minFractionDigits?: number; - maxFractionDigits?: number; -} diff --git a/packages/react/stories/NumberInput.stories.tsx b/packages/react/stories/NumberField.stories.tsx similarity index 52% rename from packages/react/stories/NumberInput.stories.tsx rename to packages/react/stories/NumberField.stories.tsx index e8af1d85..d97faf16 100644 --- a/packages/react/stories/NumberInput.stories.tsx +++ b/packages/react/stories/NumberField.stories.tsx @@ -1,11 +1,11 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; -import NumberInput from "../src/ui/number-input"; +import NumberField from "../src/ui/number-field"; -const meta: Meta = { - component: NumberInput, - title: "NumberInput", +const meta: Meta = { + component: NumberField, + title: "NumberField", tags: ["autodocs"], argTypes: {}, }; @@ -17,19 +17,19 @@ type Story = StoryObj; export const Primary: Story = { args: {}, render: () => { - const [value, setValue] = React.useState("0"); + const [value, setValue] = React.useState(0); return ( - { - console.log("Change", details.value); - setValue(details.value); + onChange={(value) => { + console.log("Change", value); + setValue(value); }} /> ); diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 6cd2c024..c7a43923 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -3,7 +3,7 @@ "jsx": "react-jsx", "lib": ["ESNext", "DOM", "DOM.Iterable"], "target": "ESNext", - "module": "es2020", + "module": "ESNext", "moduleResolution": "node", "skipLibCheck": true, "esModuleInterop": true, diff --git a/packages/vue/package.json b/packages/vue/package.json index 506dbf34..346c0e9a 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -21,7 +21,6 @@ "@vanilla-extract/css": "^1.12.0", "@vanilla-extract/dynamic": "^2.0.3", "@vanilla-extract/recipes": "^0.4.0", - "@zag-js/vue": "^0.26.0", "animejs": "^3.2.1", "copy-to-clipboard": "^3.3.3", "immer": "^9.0.19", diff --git a/src/ui/add-liquidity/add-liquidity.lite.tsx b/src/ui/add-liquidity/add-liquidity.lite.tsx index 9273625e..989021bf 100644 --- a/src/ui/add-liquidity/add-liquidity.lite.tsx +++ b/src/ui/add-liquidity/add-liquidity.lite.tsx @@ -21,8 +21,8 @@ export default function AddLiquidity(props: AddLiquidityProps) { const state = useStore<{ progress1: number; progress2: number; - amount1: string; - amount2: string; + amount1: number; + amount2: number; btnText: string; disabled: boolean; isInsufficient: boolean; @@ -37,8 +37,8 @@ export default function AddLiquidity(props: AddLiquidityProps) { }>({ progress1: 50, progress2: 50, - amount1: "", - amount2: "", + amount1: 0, + amount2: 0, btnText: "Add liquidity", disabled: true, onChangeHandler(values) { @@ -116,10 +116,10 @@ export default function AddLiquidity(props: AddLiquidityProps) { let value1 = value; let value2 = state.amount2; if (state.progress1 === 50) { - value2 = new BigNumber(value || 0) + value2 = new BigNumber(`${value || 0}`) .multipliedBy(props?.poolAssets[0]?.priceDisplayAmount || 0) .dividedBy(props?.poolAssets[1]?.priceDisplayAmount) - .toString(); + .toNumber(); } state.onChangeHandler([ { @@ -139,10 +139,10 @@ export default function AddLiquidity(props: AddLiquidityProps) { let value2 = value; let value1 = state.amount1; if (state.progress2 === 50) { - value1 = new BigNumber(value || 0) + value1 = new BigNumber(`${value || 0}`) .multipliedBy(props?.poolAssets[1]?.priceDisplayAmount || 0) .dividedBy(props?.poolAssets[0]?.priceDisplayAmount) - .toString(); + .toNumber(); } state.onChangeHandler([ { @@ -199,7 +199,7 @@ export default function AddLiquidity(props: AddLiquidityProps) { priceDisplayAmount={props?.poolAssets[0]?.priceDisplayAmount} onProgressChange={(v) => state.handleProgress1Change(v)} onAmountChange={(value) => state.handleAmount1Change(value)} - onFocus={(e) => { + onFocus={() => { amountChangeTypeRef = "1"; }} /> @@ -216,7 +216,7 @@ export default function AddLiquidity(props: AddLiquidityProps) { priceDisplayAmount={props?.poolAssets[1]?.priceDisplayAmount} onProgressChange={(v) => state.handleProgress2Change(v)} onAmountChange={(value) => state.handleAmount2Change(value)} - onFocus={(e) => { + onFocus={() => { amountChangeTypeRef = "2"; }} /> diff --git a/src/ui/asset-withdraw-tokens/asset-withdraw-tokens.lite.tsx b/src/ui/asset-withdraw-tokens/asset-withdraw-tokens.lite.tsx index 4e854513..782cf084 100644 --- a/src/ui/asset-withdraw-tokens/asset-withdraw-tokens.lite.tsx +++ b/src/ui/asset-withdraw-tokens/asset-withdraw-tokens.lite.tsx @@ -20,7 +20,7 @@ import { standardTransitionProperties } from "../shared/shared.css"; export default function AssetWithdrawTokens(props: AssetWithdrawTokensProps) { const state = useStore<{ theme: ThemeVariant; - inputAmount: string; + inputAmount: number; toAddress: string; lgAddressVisible: boolean; smAddressVisible: boolean; @@ -31,7 +31,7 @@ export default function AssetWithdrawTokens(props: AssetWithdrawTokensProps) { transferDisabled: boolean; }>({ theme: "light", - inputAmount: "0", + inputAmount: 0, toAddress: "", lgAddressVisible: false, smAddressVisible: false, @@ -46,7 +46,7 @@ export default function AssetWithdrawTokens(props: AssetWithdrawTokensProps) { handleAmountChange(percent) { state.inputAmount = new BigNumber(props.available) .multipliedBy(percent) - .toString(); + .toNumber(); }, onAmountChange(value) { state.inputAmount = value; @@ -55,7 +55,7 @@ export default function AssetWithdrawTokens(props: AssetWithdrawTokensProps) { get transferDisabled() { return ( new BigNumber(state.inputAmount).gt(props.available) || - state.inputAmount === "" + state.inputAmount === 0 ); }, }); diff --git a/src/ui/bonding-more/bonding-more.lite.tsx b/src/ui/bonding-more/bonding-more.lite.tsx index df8aa251..0bbe8616 100644 --- a/src/ui/bonding-more/bonding-more.lite.tsx +++ b/src/ui/bonding-more/bonding-more.lite.tsx @@ -10,27 +10,24 @@ import { BondingMoreProps } from "./bonding-more.types"; useMetadata({ isAttachedToShadowDom: true, - scaffolds: ["number-input"], + scaffolds: ["number-field"], }); export default function BondingMore(props: BondingMoreProps) { const state = useStore<{ btnText: string; disabled: boolean; - handleInputChange: (string) => void; + handleInputChange: (value: number) => void; }>({ btnText: "Amount is empty", disabled: true, - handleInputChange(value: string) { - if (value === "") { - state.disabled = true; - state.btnText = "Amount is empty"; - } else if (new BigNumber(value).eq(0)) { + handleInputChange(value: number) { + if (value === 0) { state.disabled = true; state.btnText = "Amount is zero"; } else if (new BigNumber(value).gt(props.available)) { state.disabled = true; - state.btnText = "Insufficient mount"; + state.btnText = "Insufficient amount"; } else { state.disabled = false; state.btnText = "Bond"; @@ -69,11 +66,11 @@ export default function BondingMore(props: BondingMoreProps) { {/* @ts-expect-error */} - state.handleInputChange(e.value)} + onChange={(value) => state.handleInputChange(value)} />