diff --git a/apps/maestro/src/features/CanonicalTokenDeployment/hooks/useDeployAndRegisterRemoteCanonicalTokenMutation.ts b/apps/maestro/src/features/CanonicalTokenDeployment/hooks/useDeployAndRegisterRemoteCanonicalTokenMutation.ts index 281a453b8..b9ce6a906 100644 --- a/apps/maestro/src/features/CanonicalTokenDeployment/hooks/useDeployAndRegisterRemoteCanonicalTokenMutation.ts +++ b/apps/maestro/src/features/CanonicalTokenDeployment/hooks/useDeployAndRegisterRemoteCanonicalTokenMutation.ts @@ -116,21 +116,21 @@ export function useDeployAndRegisterRemoteCanonicalTokenMutation( return [deployTxData, ...registerTxData]; }, [input, tokenId, destinationChainNames, originalChainName]); - const totalGasFee = useMemo( - () => - Maybe.of(input?.remoteDeploymentGasFees).mapOrUndefined( - reduce((a, b) => a + b, 0n) - ), - [input?.remoteDeploymentGasFees] + const totalGasFee = Maybe.of(input?.remoteDeploymentGasFees).mapOr( + 0n, + reduce((a, b) => a + b, 0n) ); + const isMutationReady = + multicallArgs.length > 0 && + // enable if there are no remote chains or if there are remote chains and the total gas fee is greater than 0 + (!destinationChainNames.length || totalGasFee > 0n); + const prepareMulticall = usePrepareInterchainTokenFactoryMulticall({ chainId, value: totalGasFee, args: [multicallArgs], - enabled: - multicallArgs.length > 0 && - (totalGasFee === undefined || totalGasFee > 0n), + enabled: isMutationReady, }); const multicall = useInterchainTokenFactoryMulticall(prepareMulticall.config); diff --git a/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx b/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx index 069173ca2..f019c54a0 100644 --- a/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx +++ b/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx @@ -88,12 +88,14 @@ export const Step3: FC = () => { type: "deploying", txHash: tx.hash, }); - - addTransaction({ - status: "submitted", - hash: tx.hash, - chainId: sourceChain.chain_id, - }); + if (rootState.selectedChains.length > 0) { + addTransaction({ + status: "submitted", + hash: tx.hash, + chainId: sourceChain.chain_id, + txType: "INTERCHAIN_DEPLOYMENT", + }); + } }, onTransactionError(txError) { rootActions.setTxState({ @@ -105,10 +107,10 @@ export const Step3: FC = () => { }); }, [ + rootState.selectedChains.length, + state.totalGasFee, state.isEstimatingGasFees, state.hasGasFeesEstimationError, - state.remoteDeploymentGasFees, - state.evmChains, deployCanonicalTokenAsync, actions, sourceChain, diff --git a/apps/maestro/src/features/InterchainTokenDeployment/hooks/useDeployAndRegisterRemoteInterchainTokenMutation.ts b/apps/maestro/src/features/InterchainTokenDeployment/hooks/useDeployAndRegisterRemoteInterchainTokenMutation.ts index bcb8897c8..0e6597905 100644 --- a/apps/maestro/src/features/InterchainTokenDeployment/hooks/useDeployAndRegisterRemoteInterchainTokenMutation.ts +++ b/apps/maestro/src/features/InterchainTokenDeployment/hooks/useDeployAndRegisterRemoteInterchainTokenMutation.ts @@ -130,30 +130,28 @@ export function useDeployAndRegisterRemoteInterchainTokenMutation( ...commonArgs, originalChainName, destinationChain, - gasValue: input.remoteDeploymentGasFees[i] ?? 0n, + gasValue: input.remoteDeploymentGasFees?.[i] ?? 0n, }) ); return [deployTxData, ...registerTxData]; }, [input, tokenId, withDecimals, destinationChainNames, originalChainName]); - const totalGasFee = useMemo(() => { - const remoteDeploymentsGas = Maybe.of( - input?.remoteDeploymentGasFees - ).mapOrUndefined(reduce((a, b) => a + b, 0n)); + const totalGasFee = Maybe.of(input?.remoteDeploymentGasFees).mapOr( + 0n, + reduce((a, b) => a + b, 0n) + ); - // the total gas fee is the sum of the remote deployments gas fee, - // the remote transfers gas fee and the origin transfer gas fee - return remoteDeploymentsGas; - }, [input?.remoteDeploymentGasFees]); + const isMutationReady = + multicallArgs.length > 0 && + // enable if there are no remote chains or if there are remote chains and the total gas fee is greater than 0 + (!destinationChainNames.length || totalGasFee > 0n); const prepareMulticall = usePrepareInterchainTokenFactoryMulticall({ chainId, value: totalGasFee, args: [multicallArgs], - enabled: - multicallArgs.length > 0 && - (totalGasFee === undefined || totalGasFee > 0n), + enabled: isMutationReady, }); const multicall = useInterchainTokenFactoryMulticall(prepareMulticall.config); diff --git a/apps/maestro/src/features/InterchainTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx b/apps/maestro/src/features/InterchainTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx index e716d2422..f58c2eda9 100644 --- a/apps/maestro/src/features/InterchainTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx +++ b/apps/maestro/src/features/InterchainTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx @@ -93,11 +93,14 @@ export const Step3: FC = () => { txHash: tx.hash, }); - addTransaction({ - status: "submitted", - hash: tx.hash, - chainId: sourceChain.chain_id, - }); + if (rootState.selectedChains.length > 0) { + addTransaction({ + status: "submitted", + hash: tx.hash, + chainId: sourceChain.chain_id, + txType: "INTERCHAIN_DEPLOYMENT", + }); + } }, onTransactionError(txError) { rootActions.setTxState({ diff --git a/apps/maestro/src/features/RegisterRemoteTokens/RegisterRemoteTokens.tsx b/apps/maestro/src/features/RegisterRemoteTokens/RegisterRemoteTokens.tsx index d13ac10a4..a3e574eb5 100644 --- a/apps/maestro/src/features/RegisterRemoteTokens/RegisterRemoteTokens.tsx +++ b/apps/maestro/src/features/RegisterRemoteTokens/RegisterRemoteTokens.tsx @@ -27,14 +27,6 @@ export const RegisterRemoteTokens: FC = (props) => { const { address: deployerAddress } = useAccount(); const [txState, setTxState] = useTransactionState(); - useEffect( - () => { - props.onTxStateChange?.(txState); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [txState.status] - ); - const { mutateAsync: recordRemoteTokenDeployment } = trpc.interchainToken.recordRemoteTokensDeployment.useMutation(); @@ -72,7 +64,7 @@ export const RegisterRemoteTokens: FC = (props) => { }, }); - const { writeAsync: registerCanonicalTokensAsync } = + const { writeAsync: registerCanonicalTokensAsync, reset: resetCanonical } = useRegisterRemoteCanonicalTokens({ chainIds: props.chainIds, deployerAddress: deployerAddress as `0x${string}`, @@ -80,13 +72,28 @@ export const RegisterRemoteTokens: FC = (props) => { originChainId: props.originChainId ?? -1, }); - const { writeAsync: registerInterchainTokensAsync } = + const { writeAsync: registerInterchainTokensAsync, reset: resetInterchain } = useRegisterRemoteInterchainTokens({ chainIds: props.chainIds, tokenAddress: props.tokenAddress, originChainId: props.originChainId ?? -1, }); + useEffect( + () => { + props.onTxStateChange?.(txState); + + if (txState.status === "confirmed") { + // reset muations & tx state + resetCanonical(); + resetInterchain(); + setTxState({ status: "idle" }); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [txState.status] + ); + const registerTokensAsync = useMemo(() => { switch (props.deploymentKind) { case "canonical": @@ -115,6 +122,7 @@ export const RegisterRemoteTokens: FC = (props) => { status: "submitted", hash: tx.hash, chainId: props.originChainId ?? -1, + txType: "INTERCHAIN_DEPLOYMENT", }); }, onTransactionError(error) { diff --git a/apps/maestro/src/features/SearchInterchainToken/SearchInterchainToken.tsx b/apps/maestro/src/features/SearchInterchainToken/SearchInterchainToken.tsx index 56b091074..968557a42 100644 --- a/apps/maestro/src/features/SearchInterchainToken/SearchInterchainToken.tsx +++ b/apps/maestro/src/features/SearchInterchainToken/SearchInterchainToken.tsx @@ -1,11 +1,4 @@ -import { - cn, - FormControl, - InputGroup, - SpinnerIcon, - TextInput, - Tooltip, -} from "@axelarjs/ui"; +import { cn, FormControl, SpinnerIcon, TextInput, Tooltip } from "@axelarjs/ui"; import { useSessionStorageState } from "@axelarjs/utils/react"; import { useEffect, useMemo, useState, type ChangeEvent, type FC } from "react"; @@ -101,8 +94,8 @@ const SearchInterchainToken: FC = (props) => { return (
Take your token interchain
- = (props) => { onChange={(e: ChangeEvent) => setSearch(e.target.value) } - className="bg-base-200 flex-1 focus:outline-none focus:ring-0" + className="bg-base-200 join-item flex-1 focus:outline-none focus:ring-0" onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} /> - +
{isLoading && isAddress(search) ? ( ) : ( @@ -133,7 +126,7 @@ const SearchInterchainToken: FC = (props) => { className="tooltip-left md:tooltip-top" > = (props) => { /> )} - - +
+ {shouldRenderError && (
+ Number(formatEther(num)).toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 4, + }); + export function useSendInterchainTokenState(props: { tokenAddress: `0x${string}`; originTokenAddress?: `0x${string}`; @@ -77,6 +89,30 @@ export function useSendInterchainTokenState(props: { sourceChainName: props.sourceChain.chain_name, }); + const { address } = useAccount(); + + const { data: balance } = useBalance({ + address, + watch: true, + }); + + const nativeTokenSymbol = getNativeToken( + props.sourceChain.chain_name.toLowerCase() + ); + const { data: gas } = useEstimateGasFeeQuery({ + sourceChainId: props.sourceChain.chain_name, + destinationChainId: selectedToChain?.chain_name, + sourceChainTokenSymbol: nativeTokenSymbol, + }); + + const hasInsufficientGasBalance = useMemo(() => { + if (!balance || !gas) { + return false; + } + + return gas > balance.value; + }, [balance, gas]); + const { mutateAsync: tokenManagerSendTokenAsync, isLoading: isTokenManagerSending, @@ -87,6 +123,7 @@ export function useSendInterchainTokenState(props: { tokenId: props.tokenId, destinationChainName: selectedToChain?.chain_name, sourceChainName: props.sourceChain.chain_name, + gas, }); const trpcContext = trpc.useUtils(); @@ -131,6 +168,9 @@ export function useSendInterchainTokenState(props: { selectedToChain, eligibleTargetChains, tokenSymbol, + gasFee: Maybe.of(gas).mapOrUndefined(toNumericString), + nativeTokenSymbol, + hasInsufficientGasBalance, }, { setIsModalOpen, diff --git a/apps/maestro/src/features/SendInterchainToken/SendInterchainToken.tsx b/apps/maestro/src/features/SendInterchainToken/SendInterchainToken.tsx index 04bb6f4ee..e6a14ebf0 100644 --- a/apps/maestro/src/features/SendInterchainToken/SendInterchainToken.tsx +++ b/apps/maestro/src/features/SendInterchainToken/SendInterchainToken.tsx @@ -1,5 +1,12 @@ import type { EVMChainConfig } from "@axelarjs/api"; -import { Button, FormControl, Label, Modal, TextInput } from "@axelarjs/ui"; +import { + Button, + FormControl, + Label, + Modal, + TextInput, + Tooltip, +} from "@axelarjs/ui"; import { toast } from "@axelarjs/ui/toaster"; import { invariant } from "@axelarjs/utils"; import { useCallback, useEffect, useMemo, type FC } from "react"; @@ -102,6 +109,11 @@ export const SendInterchainToken: FC = (props) => { formState.errors.amountToTransfer?.message ?? "Amount is required" ); } + + if (state.hasInsufficientGasBalance) { + return `Insufficient ${state.nativeTokenSymbol} for gas fees`; + } + return ( <> Transfer {amountToTransfer || 0} {pluralized} to{" "} @@ -113,6 +125,8 @@ export const SendInterchainToken: FC = (props) => { amountToTransfer, formState.errors.amountToTransfer?.message, formState.isValid, + state.hasInsufficientGasBalance, + state.nativeTokenSymbol, state.selectedToChain?.name, state.txState?.status, ]); @@ -136,6 +150,7 @@ export const SendInterchainToken: FC = (props) => { status: "submitted", hash: txHash, chainId: props.sourceChain.chain_id, + txType: "INTERCHAIN_TRANSFER", }); }, [actions, props.sourceChain, state.txState, txHash]); @@ -259,7 +274,7 @@ export const SendInterchainToken: FC = (props) => { const bnValue = parseUnits( value as `${number}`, - Number(props.balance.decimals) + Number(props.balance.decimals) * 2 ); if (bnValue > bnBalance) { @@ -277,14 +292,29 @@ export const SendInterchainToken: FC = (props) => { onAllChainsExecuted={handleAllChainsExecuted} /> )} - + +
+ + +
diff --git a/apps/maestro/src/features/SendInterchainToken/hooks/useInterchainTokenServiceTransferMutation.ts b/apps/maestro/src/features/SendInterchainToken/hooks/useInterchainTokenServiceTransferMutation.ts index 21822add8..f98431f5b 100644 --- a/apps/maestro/src/features/SendInterchainToken/hooks/useInterchainTokenServiceTransferMutation.ts +++ b/apps/maestro/src/features/SendInterchainToken/hooks/useInterchainTokenServiceTransferMutation.ts @@ -22,14 +22,13 @@ import { import { useInterchainTokenServiceInterchainTransfer } from "~/lib/contracts/InterchainTokenService.hooks"; import { useTransactionState } from "~/lib/hooks/useTransactionState"; import { logger } from "~/lib/logger"; -import { getNativeToken } from "~/lib/utils/getNativeToken"; -import { useEstimateGasFeeQuery } from "~/services/axelarjsSDK/hooks"; export type UseSendInterchainTokenConfig = { tokenAddress: `0x${string}`; tokenId: `0x${string}`; sourceChainName: string; destinationChainName: string; + gas?: bigint; }; export type UseSendInterchainTokenInput = { @@ -49,14 +48,6 @@ export function useInterchainTokenServiceTransferMutation( const { address } = useAccount(); - const { data: gas } = useEstimateGasFeeQuery({ - sourceChainId: config.sourceChainName, - destinationChainId: config.destinationChainName, - sourceChainTokenSymbol: getNativeToken( - config.sourceChainName.toLowerCase() - ), - }); - const { writeAsync: approveInterchainTokenAsync, data: approveERC20Data } = useInterchainTokenApprove({ address: config.tokenAddress, @@ -65,7 +56,7 @@ export function useInterchainTokenServiceTransferMutation( const { writeAsync: interchainTransferAsync, data: sendTokenData } = useInterchainTokenServiceInterchainTransfer({ address: NEXT_PUBLIC_INTERCHAIN_TOKEN_SERVICE_ADDRESS, - value: BigInt(gas ?? 0) * BigInt(2), + value: BigInt(config.gas ?? 0) * BigInt(2), }); const { data: approveERC20Recepit } = useWaitForTransaction({ @@ -91,7 +82,7 @@ export function useInterchainTokenServiceTransferMutation( destinationAddress: address, amount: approvedAmountRef.current, metadata: "0x", - gasValue: gas ?? 0n, + gasValue: config.gas ?? 0n, }), }); @@ -147,7 +138,7 @@ export function useInterchainTokenServiceTransferMutation( const mutation = useMutation( async ({ amount }) => { - if (!(decimals && address && gas)) { + if (!(decimals && address && config.gas)) { return; } diff --git a/apps/maestro/src/features/Transactions/Transactions.tsx b/apps/maestro/src/features/Transactions/Transactions.tsx index 5d11c2032..59ff20b51 100644 --- a/apps/maestro/src/features/Transactions/Transactions.tsx +++ b/apps/maestro/src/features/Transactions/Transactions.tsx @@ -6,6 +6,7 @@ import Link from "next/link"; import { groupBy } from "rambda"; +import type { TxType } from "~/lib/hooks"; import { useEVMChainConfigsQuery } from "~/services/axelarscan/hooks"; import { useGetTransactionStatusOnDestinationChainsQuery, @@ -19,7 +20,7 @@ import { } from "~/ui/compounds/GMPTxStatusMonitor"; import { useTransactionsContainer } from "./Transactions.state"; -const TX_LABEL_MAP = { +const TX_LABEL_MAP: Record = { INTERCHAIN_DEPLOYMENT: "Interchain Deployment", INTERCHAIN_TRANSFER: "Interchain Transfer", } as const; @@ -58,22 +59,28 @@ function useGroupedStatuses(txHash: `0x${string}`) { const ToastElement: FC<{ txHash: `0x${string}`; chainId: number; -}> = ({ txHash, chainId }) => { + txType?: TxType; +}> = ({ txHash, chainId, txType }) => { const { elapsedBlocks, expectedConfirmations, progress } = useGMPTxProgress( txHash, chainId ); const { computed } = useEVMChainConfigsQuery(); - const { data: txType } = useGetTransactionType({ - txHash, - }); + const { data: parsedTxType } = useGetTransactionType( + { + txHash, + }, + { + // only fetch if txType is not provided + enabled: !txType, + } + ); const isLoading = !expectedConfirmations || expectedConfirmations <= 1; - const txTypeText = useMemo( - () => (txType ? TX_LABEL_MAP[txType] : "Loading..."), - [txType] + const txTypeText = Maybe.of(parsedTxType ?? txType).mapOrNull( + (txType) => TX_LABEL_MAP[txType] ); const { groupedStatusesProps, hasStatus } = useGroupedStatuses(txHash); @@ -159,6 +166,7 @@ const ToastElement: FC<{ const GMPTransaction: FC<{ txHash: `0x${string}`; chainId: number; + txType?: keyof typeof TX_LABEL_MAP; }> = (props) => { const { computed: { chains: total, executed }, @@ -198,13 +206,10 @@ const GMPTransaction: FC<{ actions.removeTransaction(props.txHash); } - toast.custom( - , - { - id: props.txHash, - duration: Infinity, - } - ); + toast.custom(, { + id: props.txHash, + duration: Infinity, + }); // eslint-disable-next-line @typescript-eslint/no-floating-promises task(props.txHash); @@ -212,7 +217,7 @@ const GMPTransaction: FC<{ return () => { window.clearInterval(intervalRef.current); }; - }, [actions, props.chainId, props.txHash, watchTxToCompletion]); + }, [actions, props, watchTxToCompletion]); return <>; }; @@ -237,7 +242,12 @@ const Transactions = () => { return null; } return ( - + ); })} diff --git a/apps/maestro/src/lib/hooks/useTransactionState.ts b/apps/maestro/src/lib/hooks/useTransactionState.ts index 77c525c3b..f0c1b60a4 100644 --- a/apps/maestro/src/lib/hooks/useTransactionState.ts +++ b/apps/maestro/src/lib/hooks/useTransactionState.ts @@ -7,10 +7,12 @@ export type UnsubmittedTransactionState = | { status: "awaiting_spend_approval"; amount: bigint } | { status: "awaiting_approval" }; +export type TxType = "INTERCHAIN_DEPLOYMENT" | "INTERCHAIN_TRANSFER"; export type SubmittedTransactionState = | { status: "submitted"; hash: `0x${string}`; + txType?: TxType; chainId: number; isGMP?: boolean; } @@ -18,6 +20,7 @@ export type SubmittedTransactionState = status: "confirmed"; receipt: TransactionReceipt; hash?: `0x${string}`; + txType?: TxType; chainId?: number; isGMP?: boolean; } @@ -25,6 +28,7 @@ export type SubmittedTransactionState = status: "reverted"; error: TError; hash?: `0x${string}`; + txType?: TxType; chainId?: number; isGMP?: boolean; }; @@ -55,6 +59,9 @@ export function useTransactionState( // retain the chainId from the previous state if nil chainId: "chainId" in newState ? newState.chainId : prevState.chainId, + // retain the txType from the previous state if nil + txType: + "txType" in newState ? newState.txType : prevState.txType, } as TransactionState; default: return newState; diff --git a/apps/maestro/src/services/gmp/hooks.ts b/apps/maestro/src/services/gmp/hooks.ts index cac0dfc0a..0ba271679 100644 --- a/apps/maestro/src/services/gmp/hooks.ts +++ b/apps/maestro/src/services/gmp/hooks.ts @@ -66,7 +66,12 @@ export function useInterchainTokensQuery(input: { }; } -export function useGetTransactionType(input: { txHash?: `0x${string}` }) { +export function useGetTransactionType( + input: { txHash?: `0x${string}` }, + options: { + enabled?: boolean; + } +) { const { data, ...query } = useQuery( ["gmp-get-transaction-type", input.txHash], async () => { @@ -90,7 +95,9 @@ export function useGetTransactionType(input: { txHash?: `0x${string}` }) { return null; }, { - enabled: Boolean(input.txHash?.match(/^(0x)?[0-9a-f]{64}/i)), + enabled: Boolean( + input.txHash?.match(/^(0x)?[0-9a-f]{64}/i) && options.enabled + ), } ); diff --git a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx index b460f8755..847019e1b 100644 --- a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx +++ b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx @@ -350,14 +350,12 @@ const ConnectedInterchainTokensPage: FC = ( status: "submitted", hash: txState.hash, chainId: originToken.chainId, + txType: "INTERCHAIN_DEPLOYMENT", }); break; case "confirmed": setSessionState((draft) => { - draft.selectedChainIds = without( - [txState.chainId], - draft.selectedChainIds - ) as number[]; + draft.selectedChainIds = []; }); break; default: diff --git a/packages/ui/package.json b/packages/ui/package.json index 0d7ce762e..0070ff0b6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -112,7 +112,7 @@ "autoprefixer": "^10.4.16", "commander": "^10.0.1", "csstype": "^3.1.3", - "daisyui": "^3.9.4", + "daisyui": "^4.6.0", "fast-check": "^3.15.0", "happy-dom": "^9.20.3", "husky": "^8.0.3", diff --git a/packages/ui/src/StoryPlayground/StoryPlayground.tsx b/packages/ui/src/StoryPlayground/StoryPlayground.tsx index 0e995b6fb..f874255b5 100644 --- a/packages/ui/src/StoryPlayground/StoryPlayground.tsx +++ b/packages/ui/src/StoryPlayground/StoryPlayground.tsx @@ -5,6 +5,7 @@ import { ComponentProps, FC, ReactNode, useState } from "react"; import type { StoryFn } from "@storybook/react"; import { Card } from "../components/Card"; +import { Radio } from "../components/Radio"; import { ThemeSwitcher } from "../components/ThemeSwitcher"; import { cn } from "../utils"; @@ -104,16 +105,18 @@ const Variants = < return (
-
+
{VIEW_OPTIONS.map((device) => ( - + ))}
@@ -129,7 +132,6 @@ const Variants = < ))} -
( ({ variant, inputSize, className, ...props }, ref) => ( diff --git a/packages/ui/src/components/InputGroup/InputGroup.stories.tsx b/packages/ui/src/components/InputGroup/InputGroup.stories.tsx deleted file mode 100644 index 9089faa9d..000000000 --- a/packages/ui/src/components/InputGroup/InputGroup.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { Meta, StoryFn } from "@storybook/react"; - -import { InputGroup } from "./InputGroup"; - -export default { - title: "components/InputGroup", - component: InputGroup, - docs: { - description: { - component: "InputGroup, InputGroup, does whatever a InputGroup do.", - }, - }, -} as Meta; - -const Template: StoryFn = (args) => { - return ; -}; - -export const Default = Template.bind({}); - -Default.args = {}; diff --git a/packages/ui/src/components/InputGroup/InputGroup.tsx b/packages/ui/src/components/InputGroup/InputGroup.tsx deleted file mode 100644 index 7bd8a8975..000000000 --- a/packages/ui/src/components/InputGroup/InputGroup.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { ComponentProps, FC } from "react"; - -import { cva, VariantProps } from "class-variance-authority"; -import { twMerge } from "tailwind-merge"; - -import tw from "../../tw"; - -const StyledInputGroup = tw.label``; - -const inputGroupVariance = cva("input-group", { - variants: { - size: { - xs: "input-group-xs", - sm: "input-group-sm", - md: "input-group-md", - lg: "input-group-lg", - }, - vertical: { - true: "input-group-vertical", - }, - }, -}); - -type BaseInputGroupProps = VariantProps; - -export interface InputGroupProps - extends ComponentProps, - BaseInputGroupProps {} - -export const InputGroup: FC = ({ - size, - vertical, - className, - ...props -}) => ( - -); diff --git a/packages/ui/src/components/InputGroup/index.ts b/packages/ui/src/components/InputGroup/index.ts deleted file mode 100644 index 097daff63..000000000 --- a/packages/ui/src/components/InputGroup/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./InputGroup"; diff --git a/packages/ui/src/components/InputGroup/InputGroup.spec.tsx b/packages/ui/src/components/Radio/Radio.spec.tsx similarity index 79% rename from packages/ui/src/components/InputGroup/InputGroup.spec.tsx rename to packages/ui/src/components/Radio/Radio.spec.tsx index f4c72180b..ea4245a45 100644 --- a/packages/ui/src/components/InputGroup/InputGroup.spec.tsx +++ b/packages/ui/src/components/Radio/Radio.spec.tsx @@ -1,11 +1,11 @@ import { composeStories } from "@storybook/react"; import { render } from "@testing-library/react"; -import * as stories from "./InputGroup.stories"; +import * as stories from "./Radio.stories"; const { Default } = composeStories(stories); -describe("InputGroup", () => { +describe("Radio", () => { it("renders Default component story without breaking", () => { const { container } = render(); expect(container).toBeVisible(); diff --git a/packages/ui/src/components/Radio/Radio.stories.tsx b/packages/ui/src/components/Radio/Radio.stories.tsx new file mode 100644 index 000000000..6ecafac5b --- /dev/null +++ b/packages/ui/src/components/Radio/Radio.stories.tsx @@ -0,0 +1,46 @@ +import { pluralizeKeys } from "@axelarjs/utils"; + +import type { Meta, StoryFn } from "@storybook/react"; + +import { configurePlayground } from "../../StoryPlayground"; +import { COLOR_VARIANTS, SIZE_VARIANTS } from "../../theme"; +import { Radio } from "./Radio"; + +export default { + title: "components/Radio", + component: Radio, + docs: { + description: { + component: "Radio, Radio, does whatever a Radio do.", + }, + }, +} as Meta; + +const Template: StoryFn = (args) => { + return ; +}; + +export const Default = Template.bind({}); + +Default.args = {}; + +const { InputSizes, Variants } = pluralizeKeys( + configurePlayground( + Radio, + { + inputSize: { + values: SIZE_VARIANTS, + noChildren: true, + }, + variant: { + values: COLOR_VARIANTS, + noChildren: true, + }, + }, + { + defaultChecked: true, + } + ) +); + +export { InputSizes, Variants }; diff --git a/packages/ui/src/components/Radio/Radio.tsx b/packages/ui/src/components/Radio/Radio.tsx new file mode 100644 index 000000000..08525d6e1 --- /dev/null +++ b/packages/ui/src/components/Radio/Radio.tsx @@ -0,0 +1,55 @@ +import { forwardRef } from "react"; + +import { cva, VariantProps } from "class-variance-authority"; +import { twMerge } from "tailwind-merge"; + +const inputVariance = cva("radio", { + variants: { + variant: { + primary: "radio-primary", + secondary: "radio-secondary", + accent: "radio-accent", + success: "radio-success", + warning: "radio-warning", + error: "radio-error", + info: "radio-info", + }, + inputSize: { + xs: "radio-xs", + sm: "radio-sm", + md: "radio-md", + lg: "radio-lg", + }, + }, +}); + +type VProps = VariantProps; + +type InputElement = Omit; + +export interface RadioProps extends InputElement, VProps { + type?: never; + placeholder?: never; +} + +/** + * A radio input component + */ +export const Radio = forwardRef( + ({ variant, inputSize, className, ...props }, ref) => ( + + ) +); + +Radio.displayName = "Radio"; diff --git a/packages/ui/src/components/Radio/index.ts b/packages/ui/src/components/Radio/index.ts new file mode 100644 index 000000000..5c21d1563 --- /dev/null +++ b/packages/ui/src/components/Radio/index.ts @@ -0,0 +1 @@ +export * from "./Radio"; diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 160247bed..06157de81 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -12,12 +12,12 @@ export * from "./DropdownMenu"; export * from "./Footer"; export * from "./icons"; export * from "./Indicator"; -export * from "./InputGroup"; export * from "./Kbd"; export * from "./Loading"; export * from "./Menu"; export * from "./Modal"; export * from "./Navbar"; +export * from "./Radio"; export * from "./Steps"; export * from "./Table"; export * from "./Tabs"; diff --git a/packages/ui/themes/base.cjs b/packages/ui/themes/base.cjs index 75c19e300..1534c8c47 100644 --- a/packages/ui/themes/base.cjs +++ b/packages/ui/themes/base.cjs @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ /** @type {import('daisyui').CustomTheme} */ module.exports = { "--btn-text-case": "none", diff --git a/packages/ui/themes/dark.cjs b/packages/ui/themes/dark.cjs index b9aa81925..f1514d94c 100644 --- a/packages/ui/themes/dark.cjs +++ b/packages/ui/themes/dark.cjs @@ -1,4 +1,8 @@ -const darkTheme = require("daisyui/src/theming/themes")["[data-theme=dark]"]; +/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +const darkTheme = require("daisyui/src/theming/themes")["dark"]; + const base = require("./base.cjs"); /** @type {import('daisyui').CustomTheme} */ diff --git a/packages/ui/themes/light.cjs b/packages/ui/themes/light.cjs index db507303d..436d28789 100644 --- a/packages/ui/themes/light.cjs +++ b/packages/ui/themes/light.cjs @@ -1,4 +1,7 @@ -const lightTheme = require("daisyui/src/theming/themes")["[data-theme=light]"]; +/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +const lightTheme = require("daisyui/src/theming/themes")["light"]; const base = require("./base.cjs"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcf302848..aaad57e85 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -744,8 +744,8 @@ importers: specifier: ^3.1.3 version: 3.1.3 daisyui: - specifier: ^3.9.4 - version: 3.9.4(ts-node@10.9.1) + specifier: ^4.6.0 + version: 4.6.0(postcss@8.4.33) fast-check: specifier: ^3.15.0 version: 3.15.0 @@ -10646,10 +10646,6 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - /colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - dev: true - /colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} dev: false @@ -11053,6 +11049,11 @@ packages: stream-transform: 2.1.3 dev: true + /culori@3.3.0: + resolution: {integrity: sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /d@1.0.1: resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} dependencies: @@ -11060,17 +11061,16 @@ packages: type: 1.2.0 dev: true - /daisyui@3.9.4(ts-node@10.9.1): - resolution: {integrity: sha512-fvi2RGH4YV617/6DntOVGcOugOPym9jTGWW2XySb5ZpvdWO4L7bEG77VHirrnbRUEWvIEVXkBpxUz2KFj0rVnA==} + /daisyui@4.6.0(postcss@8.4.33): + resolution: {integrity: sha512-B5ZB/sczXpp4LMdo/SZrtYY/U2hq+Vr9I15QawuWZ0VwgtSAbuZpAZUftKVryEsPuv3BM0yVlBED0nAmtis/dw==} engines: {node: '>=16.9.0'} dependencies: - colord: 2.9.3 css-selector-tokenizer: 0.8.0 - postcss: 8.4.33 + culori: 3.3.0 + picocolors: 1.0.0 postcss-js: 4.0.1(postcss@8.4.33) - tailwindcss: 3.4.1(ts-node@10.9.1) transitivePeerDependencies: - - ts-node + - postcss dev: true /damerau-levenshtein@1.0.8: @@ -12093,7 +12093,7 @@ packages: eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.56.0) - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.18.1)(eslint@8.56.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.56.0) eslint-plugin-react: 7.33.2(eslint@8.56.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.56.0) @@ -12124,7 +12124,7 @@ packages: enhanced-resolve: 5.15.0 eslint: 8.56.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0) - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.18.1)(eslint@8.56.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0) fast-glob: 3.3.1 get-tsconfig: 4.7.2 is-core-module: 2.13.0 @@ -12166,36 +12166,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.18.1)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - '@typescript-eslint/parser': 6.18.1(eslint@8.56.0)(typescript@5.3.3) - debug: 3.2.7 - eslint: 8.56.0 - eslint-import-resolver-node: 0.3.9 - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.18.1)(eslint@8.56.0): + /eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0): resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} engines: {node: '>=4'} peerDependencies: @@ -12205,7 +12176,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.18.1(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@5.3.3) array-includes: 3.1.6 array.prototype.findlastindex: 1.2.2 array.prototype.flat: 1.3.1 @@ -12214,7 +12185,7 @@ packages: doctrine: 2.1.0 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.1)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.56.0) has: 1.0.3 is-core-module: 2.13.0 is-glob: 4.0.3