From 16ef873b9d98924a06f29fa1784ee97efd89e5f3 Mon Sep 17 00:00:00 2001 From: Sergo Date: Thu, 28 Mar 2024 15:22:08 +0100 Subject: [PATCH] FEAT: Show balance & Allow to choose max amount --- .../widgets/token-upgrade-amount.stories.tsx | 119 ++++++++++++++++ .../widgets/token-upgrade.stories.tsx | 2 +- packages/ui/src/widgets/token-upgrade.tsx | 2 +- .../ui/src/widgets/token-upgrade/amount.tsx | 128 ++++++++++++------ 4 files changed, 205 insertions(+), 46 deletions(-) create mode 100644 packages/ui/src/__stories__/widgets/token-upgrade-amount.stories.tsx diff --git a/packages/ui/src/__stories__/widgets/token-upgrade-amount.stories.tsx b/packages/ui/src/__stories__/widgets/token-upgrade-amount.stories.tsx new file mode 100644 index 0000000..60a2c50 --- /dev/null +++ b/packages/ui/src/__stories__/widgets/token-upgrade-amount.stories.tsx @@ -0,0 +1,119 @@ +import * as Form from "@radix-ui/react-form" +import Amount from "../../widgets/token-upgrade/amount" +import type { StoryFn, StoryObj } from "@storybook/react" +import { expect, fn, userEvent, within } from "@storybook/test" + +const meta = { + title: "UI/Token Upgrade/Amount", + component: Amount, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: "padded", + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ["autodocs"], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + args: { + onAmountChange: fn(), + onAmountMaxChange: fn(), + }, + argTypes: { + address: { control: "text" }, + balance: { control: "text" }, + label: { control: "text" }, + name: { control: "text" }, + step: { control: "number" }, + symbol: { control: "text" }, + }, + decorators: [ + (Story: StoryFn) => { + return ( + + + + + + ) + }, + ], +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + address: undefined, + balance: undefined, + disabled: undefined, + placeholder: undefined, + step: undefined, + symbol: undefined, + }, + async play({ canvasElement, step }: any) { + const ctx = within(canvasElement) + + await step("should be rendered", async () => { + const el = ctx.getByRole("spinbutton", { description: "Amount" }) + await expect(el).not.toBeDisabled() + }) + }, +} + +export const Disabled: Story = { + args: { + ...Default.args, + disabled: true, + }, + async play({ canvasElement, step }: any) { + const ctx = within(canvasElement) + + await step("should be disabled", async () => { + const el = ctx.getByRole("spinbutton", { description: "Amount" }) + await expect(el).toBeDisabled() + }) + }, +} + +export const WithAddress: Story = { + args: { + ...Default.args, + address: "HoKw8CavcPjnd4QpFkvMyetz9bpQ9AqUJRjFqbjjnjqY", + }, + async play({ canvasElement, step }: any) { + const ctx = within(canvasElement) + + await step("should show symbol", async () => { + const el = ctx.getByText("Ho..Y") + await expect(el).toBeVisible() + }) + }, +} + +export const WithSymbol: Story = { + args: { + ...Default.args, + address: "HoKw8CavcPjnd4QpFkvMyetz9bpQ9AqUJRjFqbjjnjqY", + symbol: "TKN", + }, +} + +export const WithBalance: Story = { + args: { + ...Default.args, + address: "HoKw8CavcPjnd4QpFkvMyetz9bpQ9AqUJRjFqbjjnjqY", + balance: "10", + }, + async play({ canvasElement, step }: any) { + const ctx = within(canvasElement) + + await step("should allow to change amount", async () => { + const el = ctx.getByRole("button") + await expect(el).toBeVisible() + await userEvent.click(el) + await expect( + ctx.getByRole("spinbutton", { description: "Amount" }), + ).toHaveValue(10) + }) + }, +} diff --git a/packages/ui/src/__stories__/widgets/token-upgrade.stories.tsx b/packages/ui/src/__stories__/widgets/token-upgrade.stories.tsx index 8f0f58e..dd6c8b8 100644 --- a/packages/ui/src/__stories__/widgets/token-upgrade.stories.tsx +++ b/packages/ui/src/__stories__/widgets/token-upgrade.stories.tsx @@ -77,7 +77,7 @@ export const WithTokenAddress: StoryObj< ComponentPropsWithTestId > = { args: { - tokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + tokenAddress: "HoKw8CavcPjnd4QpFkvMyetz9bpQ9AqUJRjFqbjjnjqY", }, async play({ canvasElement, step }: any) { const ctx = within(canvasElement) diff --git a/packages/ui/src/widgets/token-upgrade.tsx b/packages/ui/src/widgets/token-upgrade.tsx index bef14f7..9677bc4 100644 --- a/packages/ui/src/widgets/token-upgrade.tsx +++ b/packages/ui/src/widgets/token-upgrade.tsx @@ -109,7 +109,7 @@ export function TokenUpgradeBase({ , React.ComponentPropsWithoutRef<"input"> { address?: string - balance: string + balance?: string label?: string name?: string - onAmountChange?: ({ amount }: { amount: number }) => void + onAmountChange?: (a: { amount: number }) => void + onAmountMaxChange?: (a: { amount: number }) => void symbol?: string } export default function Amount({ address, - balance, + balance = "0", disabled, label = "Amount", name = "amount", onAmountChange, + onAmountMaxChange, min = 0, - placeholder = "0.0", + placeholder = "0", step = 1, symbol, ...props }: AmountProps) { let variants = {} + const hasBalance = balance && Number(balance) > 0 + const [value, setValue] = useState() + + const onValueChange = useCallback( + (value: string) => { + const numValue = Number(value) + const isValid = numValue >= 0 + if (isValid) { + setValue(value) + onAmountChange?.({ amount: numValue }) + } + }, + [onAmountChange], + ) + + const onValueMaxChange = useCallback( + (value: number) => { + setValue(String(value)) + onAmountMaxChange?.({ amount: value }) + }, + [onAmountMaxChange], + ) + if (disabled) variants = { variant: "disabled" } + if (hasBalance) variants = { variant: "active" } const displaySymbol = useMemo(() => { let s = symbol @@ -63,45 +91,57 @@ export default function Amount({ > {label} -
- {disabled ? ( -
- -
- ) : null} - { - const value = Number(e.target.value) - const isValid = value >= 0 - if (isValid) { - if (onAmountChange) onAmountChange({ amount: value }) - } - }} - > - + {hasBalance && ( + + )} +
+ - - {displaySymbol ? ( -
- {displaySymbol} -
- ) : null} + onChange={(e) => { + onValueChange(e.target.value) + }} + > + + + {displaySymbol ? ( +
+ {displaySymbol} +
+ ) : null} +
+ {Number(balance) > 0 ? ( +
+ Balance: {balance} {address} +
+ ) : null} ) }