Dashboard: Completely remove Chakra UI (#7755)

<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on removing the `@chakra-ui/react` library and its related components from the project, transitioning to a custom UI implementation.

### Detailed summary
- Deleted multiple `chakra` components and utility files.
- Removed `ChakraProviderSetup` usage from various components.
- Updated styles and layout in the `ContractPageLayout` and `ContractSplitPage`.
- Modified ESLint rules to disable chakra-specific restrictions.
- Updated `package.json` and `pnpm-lock.yaml` to remove `@chakra-ui` dependencies.

> The following files were skipped due to too many changes: `pnpm-lock.yaml`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
This commit is contained in:
MananTank
2025-07-29 18:16:01 +00:00
parent 5c5b910570
commit 2e901123c4
20 changed files with 168 additions and 2147 deletions
-61
View File
@@ -10,13 +10,6 @@ module.exports = {
"plugin:storybook/recommended",
],
overrides: [
// disable restricted imports in chakra
{
files: "src/chakra/**/*",
rules: {
"no-restricted-imports": ["off"],
},
},
// allow direct PostHog imports inside analytics helpers
{
files: "src/@/analytics/**/*",
@@ -91,60 +84,6 @@ module.exports = {
"error",
{
paths: [
{
// these are provided by chakra folder, so we don't want to import them from chakra directly
importNames: [
"Card",
"Button",
"Checkbox",
"Badge",
"Drawer",
"Heading",
"Text",
"FormLabel",
"FormHelperText",
"FormErrorMessage",
"MenuGroup",
"VStack",
"HStack",
"AspectRatio",
"useToast",
"useClipboard",
"Badge",
"Stack",
// also the types
"ButtonProps",
"BadgeProps",
"DrawerProps",
"HeadingProps",
"TextProps",
"FormLabelProps",
"HelpTextProps",
"MenuGroupProps",
"MenuItemProps",
"AspectRatioProps",
"BadgeProps",
"StackProps",
],
message:
'import component from "chakra" folder instead if you have to use chakra component, But use shadcn component otherwise',
name: "@chakra-ui/react",
},
{
message:
"Import from `@chakra-ui/react` instead of `@chakra-ui/layout`.",
name: "@chakra-ui/layout",
},
{
message:
"Import from `@chakra-ui/react` instead of `@chakra-ui/button`.",
name: "@chakra-ui/button",
},
{
message:
"Import from `@chakra-ui/react` instead of `@chakra-ui/menu`.",
name: "@chakra-ui/menu",
},
{
importNames: ["useRouter"],
message:
-5
View File
@@ -20,11 +20,6 @@ const config: StorybookConfig = {
name: getAbsolutePath("@storybook/nextjs"),
options: {},
},
refs: {
"@chakra-ui/react": {
disable: true,
},
},
staticDirs: ["../public"],
features: {
experimentalRSC: true,
-3
View File
@@ -115,9 +115,6 @@ const SENTRY_OPTIONS: SentryBuildOptions = {
const FRAMER_ADDITIONAL_LANGUAGES = ["es"];
const baseNextConfig: NextConfig = {
compiler: {
emotion: true,
},
eslint: {
ignoreDuringBuilds: true,
},
+1 -11
View File
@@ -1,10 +1,5 @@
{
"dependencies": {
"@chakra-ui/react": "^2.8.2",
"@chakra-ui/styled-system": "^2.9.2",
"@chakra-ui/theme-tools": "^2.1.2",
"@emotion/react": "11.14.0",
"@emotion/styled": "11.14.1",
"@hookform/resolvers": "^3.9.1",
"@marsidev/react-turnstile": "^1.1.0",
"@radix-ui/react-accordion": "^1.2.11",
@@ -36,11 +31,9 @@
"abitype": "1.0.8",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"color": "5.0.0",
"compare-versions": "^6.1.0",
"date-fns": "4.1.0",
"fast-xml-parser": "^5.2.5",
"framer-motion": "12.23.0",
"fuse.js": "7.1.0",
"input-otp": "^1.4.1",
"ioredis": "^5.6.1",
@@ -85,7 +78,6 @@
},
"devDependencies": {
"@biomejs/biome": "2.0.6",
"@chakra-ui/cli": "^2.4.1",
"@chromatic-com/storybook": "4.0.1",
"@next/bundle-analyzer": "15.3.5",
"@next/eslint-plugin-next": "15.3.5",
@@ -127,12 +119,10 @@
"dev": "next dev --turbopack",
"fix": "biome check ./src --fix && eslint ./src --fix",
"format": "biome format ./src --write",
"gen:theme-typings": "chakra-cli tokens src/chakra/theme/index.ts",
"knip": "knip",
"lint": "biome check ./src && knip && eslint ./src",
"playwright": "playwright test",
"postbuild": "next-sitemap",
"postinstall": "pnpm run gen:theme-typings",
"preinstall": "npx only-allow pnpm",
"start": "next start",
"storybook": "storybook dev -p 6006",
@@ -140,4 +130,4 @@
"update-checkly": "npx checkly deploy"
},
"version": "3.0.0"
}
}
@@ -1,5 +1,4 @@
import { DeprecatedAlert } from "@app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/DeprecatedAlert";
import { ChakraProviderSetup } from "chakra/ChakraProviderSetup";
import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import type { SidebarLink } from "@/components/blocks/Sidebar";
@@ -36,7 +35,7 @@ export function ContractPageLayout(props: {
} = props;
return (
<ChakraProviderSetup>
<div>
<div className="border-border border-b py-8">
<div className="container flex flex-col gap-4">
<div className="flex flex-col justify-between gap-4 md:flex-row">
@@ -67,6 +66,6 @@ export function ContractPageLayout(props: {
{props.children}
<div className="h-20" />
</SidebarLayout>
</ChakraProviderSetup>
</div>
);
}
@@ -1,16 +1,5 @@
"use client";
import {
Flex,
SimpleGrid,
Spinner,
Stat,
StatLabel,
StatNumber,
} from "@chakra-ui/react";
import { Card } from "chakra/card";
import { Heading } from "chakra/heading";
import { Text } from "chakra/text";
import { useMemo } from "react";
import {
type ThirdwebContract,
@@ -24,6 +13,17 @@ import {
useReadContract,
useWalletBalance,
} from "thirdweb/react";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useAllChainsData } from "@/hooks/chains/allChains";
import { useSplitBalances } from "@/hooks/useSplit";
import { shortenIfAddress } from "@/utils/usedapp-external";
@@ -42,10 +42,7 @@ interface SplitPageProps {
isLoggedIn: boolean;
}
export const ContractSplitPage: React.FC<SplitPageProps> = ({
contract,
isLoggedIn,
}) => {
export function ContractSplitPage({ contract, isLoggedIn }: SplitPageProps) {
const address = useActiveAccount()?.address;
const { idToChain } = useAllChainsData();
const chainId = contract.chain.id;
@@ -104,120 +101,127 @@ export const ContractSplitPage: React.FC<SplitPageProps> = ({
);
}, [allRecipientsPercentages, balances, address]);
const isPending = balanceQuery.isPending || nativeBalanceQuery.isPending;
return (
<Flex direction="column" gap={6}>
<Flex align="center" direction="row" justify="space-between">
<Heading size="title.sm">Balances</Heading>
<Flex gap={4}>
<DistributeButton
balances={balances as Balance[]}
balancesIsError={balanceQuery.isError && nativeBalanceQuery.isError}
balancesIsPending={
balanceQuery.isPending || nativeBalanceQuery.isPending
}
contract={contract}
isLoggedIn={isLoggedIn}
/>
</Flex>
</Flex>
<div className="flex flex-col gap-8">
<Flex flexDir="column" gap={4}>
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={{ base: 3, md: 6 }}>
<Card as={Stat}>
<StatLabel mb={{ base: 1, md: 0 }}>
{nativeBalanceQuery.data?.symbol}
</StatLabel>
<StatNumber>{nativeBalanceQuery?.data?.displayValue}</StatNumber>
{shareOfBalancesForConnectedWallet[ZERO_ADDRESS] && (
<StatNumber>
<Text size="body.md">
<Text as="span" size="label.md">
Your Share:
</Text>{" "}
{shareOfBalancesForConnectedWallet[ZERO_ADDRESS]}
</Text>
</StatNumber>
)}
</Card>
{balanceQuery.isPending ? (
<div className="flex items-center justify-center">
<Spinner />
</div>
) : (
(balanceQuery?.data || [])
<div>
{/* header */}
<div className="flex flex-col gap-3 lg:flex-row lg:items-center justify-between mb-6 lg:mb-3">
<h2 className="text-2xl font-semibold tracking-tight">Balances</h2>
<DistributeButton
balances={balances as Balance[]}
balancesIsError={balanceQuery.isError && nativeBalanceQuery.isError}
balancesIsPending={
balanceQuery.isPending || nativeBalanceQuery.isPending
}
contract={contract}
isLoggedIn={isLoggedIn}
/>
</div>
{/* balances */}
<div className="flex flex-col gap-4 mb-8">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-6">
{isPending ? (
new Array(4).fill(null).map((_, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: ok
<Skeleton key={index} className="h-[86px]" />
))
) : (
<>
<BalanceCard
symbol={nativeBalanceQuery.data?.symbol || "Native Token"}
displayValue={nativeBalanceQuery?.data?.displayValue || "0"}
userShare={shareOfBalancesForConnectedWallet[ZERO_ADDRESS]}
/>
{(balanceQuery?.data || [])
?.filter((bl) => bl.name !== "Native Token")
?.map((balance) => (
<Card as={Stat} key={balance.token_address} maxWidth="2xs">
<StatLabel as={Heading} size="label.lg">
{balance.name === "Native Token"
<BalanceCard
key={balance.token_address}
symbol={
balance.name === "Native Token"
? v4Chain?.nativeCurrency.symbol || "Native Token"
: balance.symbol ||
shortenIfAddress(balance.token_address)}
</StatLabel>
<StatNumber>
<Text size="body.md">{balance.display_balance}</Text>
</StatNumber>
{shareOfBalancesForConnectedWallet[
balance.token_address
] && (
<StatNumber>
<Text size="body.md">
<Text as="span" size="label.md">
Your Share:
</Text>{" "}
{
shareOfBalancesForConnectedWallet[
balance.token_address
]
}
</Text>
</StatNumber>
)}
</Card>
))
)}
</SimpleGrid>
{balanceQuery.isError && (
<Text color="red.500">
{(balanceQuery?.error as Error).message === "Invalid chain!"
? "Showing ERC20 balances for this network is not currently supported. You can distribute ERC20 funds from the Explorer tab."
: "Error loading balances"}
</Text>
shortenIfAddress(balance.token_address)
}
displayValue={balance.display_balance}
userShare={
shareOfBalancesForConnectedWallet[balance.token_address]
}
/>
))}
</>
)}
<Text fontStyle="italic">
The Split can receive funds in the native token or in any ERC20.
Balances may take a couple of minutes to display after being
received.
<br />
{/* We currently use Moralis and high chances are they don't recognize all ERC20 tokens in the contract */}
If you are looking to distribute an ERC20 token and it's not being
recognized on this page, you can manually call the `distribute`
method in the Explorer page
</Text>
</Flex>
<div className="flex flex-col gap-2">
<Heading mb="8px" size="label.lg">
Split Recipients
</Heading>
{(allRecipientsPercentages || []).map((split) => (
<Card key={split.address}>
<Text>
<Text as="span" size="label.md">
Address:
</Text>{" "}
{split.address}
</Text>
<Text>
<Text as="span" size="label.md">
Percentage:
</Text>{" "}
{split.splitPercentage}%
</Text>
</Card>
))}
</div>
{balanceQuery.isError && (
<p className="text-red-500">
{(balanceQuery?.error as Error).message === "Invalid chain!"
? "Showing ERC20 balances for this network is not currently supported. You can distribute ERC20 funds from the Explorer tab."
: "Error loading balances"}
</p>
)}
<p className="text-sm text-muted-foreground">
The Split can receive funds in the native token or in any ERC20.
Balances may take a couple of minutes to display after being received.
<br />
{/* We currently use Moralis and high chances are they don't recognize all ERC20 tokens in the contract */}
If you are looking to distribute an ERC20 token and it's not being
recognized on this page, you can manually call the `distribute` method
in the Explorer page
</p>
</div>
</Flex>
{/* recipients e */}
<div>
<h3 className="text-lg font-semibold mb-2">Split Recipients</h3>
<TableContainer>
<Table>
<TableHeader>
<TableRow>
<TableHead>Address</TableHead>
<TableHead>Percentage</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{(allRecipientsPercentages || []).map((split) => (
<TableRow key={split.address}>
<TableCell>
<WalletAddress
address={split.address}
client={contract.client}
/>
</TableCell>
<TableCell>{split.splitPercentage}%</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
</div>
);
};
}
function BalanceCard(props: {
symbol: string;
displayValue: string;
userShare: string | undefined;
}) {
return (
<div className="p-4 border rounded-lg bg-card space-y-1">
<div className="text-xl font-semibold">
{props.displayValue} {props.symbol}
</div>
{props.userShare && (
<div className="text-sm">
<span className="text-sm font-medium">Your Share:</span>{" "}
{props.userShare}
</div>
)}
</div>
);
}
@@ -1,28 +1,28 @@
"use client";
import { SplitIcon } from "lucide-react";
import { useMemo } from "react";
import { toast } from "sonner";
import type { ThirdwebContract } from "thirdweb";
import { TransactionButton } from "@/components/tx-button";
import { Button } from "@/components/ui/button";
import { ToolTipLabel } from "@/components/ui/tooltip";
import { useSplitDistributeFunds } from "@/hooks/useSplit";
import { useTxNotifications } from "@/hooks/useTxNotifications";
import { parseError } from "@/utils/errorParser";
import type { Balance } from "../ContractSplitPage";
interface DistributeButtonProps {
contract: ThirdwebContract;
balances: Balance[];
balancesIsPending: boolean;
balancesIsError: boolean;
isLoggedIn: boolean;
}
export const DistributeButton: React.FC<DistributeButtonProps> = ({
export const DistributeButton = ({
contract,
balances,
balancesIsPending,
balancesIsError,
isLoggedIn,
...restButtonProps
}: {
contract: ThirdwebContract;
balances: Balance[];
balancesIsPending: boolean;
balancesIsError: boolean;
isLoggedIn: boolean;
}) => {
const validBalances = balances.filter(
(item) => item.balance !== "0" && item.balance !== "0.0",
@@ -44,19 +44,16 @@ export const DistributeButton: React.FC<DistributeButtonProps> = ({
const mutation = useSplitDistributeFunds(contract);
const { onSuccess, onError } = useTxNotifications(
"Funds splitted successfully",
"Failed to process transaction",
);
const distributeFunds = () => {
mutation.mutate(undefined, {
onError: (error) => {
toast.error("Failed to process transaction", {
description: parseError(error),
});
console.error(error);
onError(error);
},
onSuccess: () => {
onSuccess();
toast.success("Funds splitted successfully");
},
});
};
@@ -71,7 +68,6 @@ export const DistributeButton: React.FC<DistributeButtonProps> = ({
transactionCount={undefined}
// if we fail to get the balances, we can't know how many transactions there are going to be
txChainID={contract.chain.id}
{...restButtonProps}
>
Distribute Funds
</TransactionButton>
@@ -80,9 +76,12 @@ export const DistributeButton: React.FC<DistributeButtonProps> = ({
if (numTransactions === 0) {
return (
<Button disabled variant="primary" {...restButtonProps}>
Nothing to distribute
</Button>
<ToolTipLabel label="Nothing to distribute">
<Button disabled variant="default" size="sm" className="gap-2">
<SplitIcon className="size-3.5" />
Distribute Funds
</Button>
</ToolTipLabel>
);
}
@@ -93,9 +92,11 @@ export const DistributeButton: React.FC<DistributeButtonProps> = ({
isPending={mutation.isPending}
onClick={distributeFunds}
transactionCount={numTransactions}
{...restButtonProps}
txChainID={contract.chain.id}
variant="default"
size="sm"
>
<SplitIcon className="size-3.5" />
Distribute Funds
</TransactionButton>
);
@@ -1,4 +1,3 @@
import { ChakraProviderSetup } from "chakra/ChakraProviderSetup";
import { revalidatePath } from "next/cache";
import { notFound, redirect } from "next/navigation";
import { fetchDeployMetadata } from "thirdweb/contract";
@@ -74,22 +73,20 @@ export default async function PublishContractPage(
return (
<div className="container flex max-w-[1130px] flex-col gap-8 py-8">
<ChakraProviderSetup>
<ContractPublishForm
client={userThirdwebClient}
isLoggedIn={!!token}
onPublishSuccess={async () => {
"use server";
// we are pretty brutal here and simply invalidate ALL published contracts (for everyone!) and versions no matter what
// TODO: we can be more granular here and only invalidate the specific contract and version etc
revalidatePath(
"/(dashboard)/published-contract/[publisher]/[contract_id]",
"layout",
);
}}
publishMetadata={publishMetadata}
/>
</ChakraProviderSetup>
<ContractPublishForm
client={userThirdwebClient}
isLoggedIn={!!token}
onPublishSuccess={async () => {
"use server";
// we are pretty brutal here and simply invalidate ALL published contracts (for everyone!) and versions no matter what
// TODO: we can be more granular here and only invalidate the specific contract and version etc
revalidatePath(
"/(dashboard)/published-contract/[publisher]/[contract_id]",
"layout",
);
}}
publishMetadata={publishMetadata}
/>
</div>
);
}
@@ -1,36 +0,0 @@
"use client";
import { ChakraProvider, useColorMode } from "@chakra-ui/react";
import { useTheme } from "next-themes";
import { useEffect } from "react";
import chakraTheme from "./theme";
export function ChakraProviderSetup(props: { children: React.ReactNode }) {
return (
<ChakraProvider theme={chakraTheme}>
{props.children}
<SyncTheme />
</ChakraProvider>
);
}
function SyncTheme() {
const { theme, setTheme } = useTheme();
const { setColorMode } = useColorMode();
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
setColorMode(theme === "light" ? "light" : "dark");
}, [setColorMode, theme]);
// handle dashboard with now old "system" set
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
if (theme === "system") {
setTheme("dark");
setColorMode("dark");
}
}, [theme, setTheme, setColorMode]);
return null;
}
@@ -1,3 +0,0 @@
# DO NOT ADD NEW THINGS INSIDE THIS FOLDER OR SUB_FOLDERS
This part of the dashboard is deprecated, and we are actively working on removing it. Please do not add new things here. If you need to add new things, please add them to the appropriate folder in the src folder.
-85
View File
@@ -1,85 +0,0 @@
import { Box, type BoxProps } from "@chakra-ui/react";
type DefaultedBoxProps = Pick<
BoxProps,
"shadow" | "py" | "px" | "borderRadius" | "borderWidth" | "borderColor"
>;
const defaultBoxProps: Required<DefaultedBoxProps> = {
borderColor: "borderColor",
borderRadius: "xl",
borderWidth: "1px",
px: 4,
py: 4,
shadow: "sm",
};
const borderRadiusMap = {
"2xl": "xl",
"3xl": "2xl",
lg: "md",
md: "sm",
none: "none",
sm: "none",
xl: "lg",
};
interface CardProps extends BoxProps {
outlineBorder?: {
gradient: string;
width: string;
};
}
function getBorderRadius(
borderRadius: BoxProps["borderRadius"],
): BoxProps["borderRadius"] {
try {
// biome-ignore lint/suspicious/noExplicitAny: FIXME
return (borderRadiusMap as any)[borderRadius as any];
} catch {
return borderRadius;
}
}
export const Card: React.FC<CardProps> = ({
children,
outlineBorder,
...requiredBoxProps
}) => {
const combinedProps = { ...{ ...defaultBoxProps, ...requiredBoxProps } };
return (
<>
{outlineBorder ? (
<Box
borderRadius={combinedProps.borderRadius}
overflow="hidden"
p={outlineBorder.width}
position="relative"
w={combinedProps.w || combinedProps.width}
>
<Box
bgGradient={outlineBorder.gradient}
h="full"
left={0}
position="absolute"
top={0}
w="full"
zIndex={-1}
/>
<Box
{...combinedProps}
borderRadius={getBorderRadius(combinedProps.borderRadius)}
w="full"
>
{children}
</Box>
</Box>
) : (
<Box {...combinedProps}>{children}</Box>
)}
</>
);
};
-35
View File
@@ -1,35 +0,0 @@
import {
Heading as ChakraHeading,
type HeadingProps as ChakraHeadingProps,
} from "@chakra-ui/react";
import {
fontWeights,
type HeadingBase,
type HeadingSizes,
letterSpacings,
lineHeights,
type TypographySize,
} from "chakra/theme/typography";
import type { ComponentWithChildren } from "@/types/component-with-children";
import { convertFontSizeToCSSVar } from "./utils/typography";
interface HeadingProps extends Omit<ChakraHeadingProps, "size"> {
size?: HeadingSizes;
}
export const Heading: ComponentWithChildren<HeadingProps> = ({
size = "title.lg",
...restProps
}) => {
const [base] = size.split(".") as [HeadingBase, TypographySize];
return (
<ChakraHeading
fontSize={convertFontSizeToCSSVar(size)}
fontWeight={fontWeights[base]}
letterSpacing={letterSpacings[base]}
lineHeight={lineHeights[base]}
{...restProps}
/>
);
};
-35
View File
@@ -1,35 +0,0 @@
import {
Text as ChakraText,
type TextProps as ChakraTextProps,
} from "@chakra-ui/react";
import {
fontWeights,
letterSpacings,
lineHeights,
type TextBase,
type TextSizes,
type TypographySize,
} from "chakra/theme/typography";
import type { ComponentWithChildren } from "@/types/component-with-children";
import { convertFontSizeToCSSVar } from "./utils/typography";
interface TextProps extends Omit<ChakraTextProps, "size"> {
size?: TextSizes;
}
export const Text: ComponentWithChildren<TextProps> = ({
size = "body.md",
...restProps
}) => {
const [base] = size.split(".") as [TextBase, TypographySize];
return (
<ChakraText
fontSize={convertFontSizeToCSSVar(size)}
fontWeight={fontWeights[base]}
letterSpacing={letterSpacings[base]}
lineHeight={lineHeights[base]}
{...restProps}
/>
);
};
-202
View File
@@ -1,202 +0,0 @@
import color from "color";
const config = {
darkest: {
lightness: 10,
rotate: 0,
saturate: 0,
},
lightest: {
lightness: 95,
rotate: 0,
saturate: 0,
},
};
function createShades(centerColor: string): Record<number, string> {
const _color = centerColor;
const darkSteps = 4;
const lightSteps = 5;
const lightnessStep = (config.lightest.lightness - 50) / lightSteps;
const darknessStep = (50 - config.darkest.lightness) / darkSteps;
const lightRotateStep = config.lightest.rotate / lightSteps;
const darkRotateStep = config.darkest.rotate / darkSteps;
const lightSaturateStep = config.lightest.saturate / lightSteps;
const darkSaturateStep = config.darkest.saturate / darkSteps;
return {
50: color(_color)
.lightness(50 + lightnessStep * 5)
.rotate(lightRotateStep * 5)
.saturate(lightSaturateStep * 5)
.hex(),
100: color(_color)
.lightness(50 + lightnessStep * 4)
.rotate(lightRotateStep * 4)
.saturate(lightSaturateStep * 4)
.hex(),
200: color(_color)
.lightness(50 + lightnessStep * 3)
.rotate(lightRotateStep * 3)
.saturate(lightSaturateStep * 3)
.hex(),
300: color(_color)
.lightness(50 + lightnessStep * 2)
.rotate(lightRotateStep * 2)
.saturate(lightSaturateStep * 2)
.hex(),
400: color(_color)
.lightness(50 + Number(lightnessStep))
.rotate(Number(lightRotateStep))
.saturate(Number(lightSaturateStep))
.hex(),
500: centerColor,
600: color(_color)
.lightness(50 - Number(darknessStep))
.rotate(Number(darkRotateStep))
.saturate(Number(darkSaturateStep))
.hex(),
700: color(_color)
.lightness(50 - darknessStep * 2)
.rotate(darkRotateStep * 2)
.saturate(darkSaturateStep * 2)
.hex(),
800: color(_color)
.lightness(50 - darknessStep * 3)
.rotate(darkRotateStep * 3)
.saturate(darkSaturateStep * 3)
.hex(),
900: color(_color)
.lightness(50 - darknessStep * 4)
.rotate(darkRotateStep * 4)
.saturate(darkSaturateStep * 4)
.hex(),
};
}
const blue = {
50: "#F0F9FF",
100: "#E0F2FE",
200: "#3385FF",
300: "#4A92FE",
400: "#4A92FE",
500: "#3385FF",
600: "#2D66BB",
700: "#224A85",
800: "#075985",
900: "#0C4A6E",
};
const themeColors = {
blackAlpha: {
50: "rgba(0,0,0,0.05)",
100: "rgba(0,0,0,0.1)",
200: "rgba(0,0,0,0.2)",
300: "rgba(0,0,0,0.3)",
400: "rgba(0,0,0,0.4)",
500: "rgba(0,0,0,0.5)",
600: "rgba(0,0,0,0.6)",
700: "rgba(0,0,0,0.7)",
800: "rgba(0,0,0,0.8)",
900: "rgba(0,0,0,0.9)",
},
blue,
cyan: blue,
gray: {
50: "#F2F2F7",
100: "#E5E5EA",
200: "#E5E5EA",
300: "#D1D1D6",
400: "#C7C7CC",
500: "#AEAEB2",
600: "#8E8E93",
700: "#636366",
800: "#3A3A3C",
900: "#1C1C1E",
},
green: {
50: "#ECFDF5",
100: "#D1FAE5",
200: "#A7F3D0",
300: "#6EE7B7",
400: "#34D399",
500: "#10B981",
600: "#059669",
700: "#047857",
800: "#065F46",
900: "#064E3B",
},
orange: {
50: "#FFF7ED",
100: "#FFEDD5",
200: "#FED7AA",
300: "#FDBA74",
400: "#FB923C",
500: "#F97316",
600: "#EA580C",
700: "#C2410C",
800: "#9A3412",
900: "#7C2D12",
},
pink: blue,
purple: blue,
red: {
50: "#FEF2F2",
100: "#FEE2E2",
200: "#FECACA",
300: "#FCA5A5",
400: "#F87171",
500: "#EF4444",
600: "#DC2626",
700: "#B91C1C",
800: "#991B1B",
900: "#7F1D1D",
},
teal: blue,
transparent: "transparent",
whiteAlpha: {
50: "rgba(255,255,255,0.05)",
100: "rgba(255,255,255,0.1)",
200: "rgba(255,255,255,0.2)",
300: "rgba(255,255,255,0.3)",
400: "rgba(255,255,255,0.4)",
500: "rgba(255,255,255,0.5)",
600: "rgba(255,255,255,0.6)",
700: "rgba(255,255,255,0.7)",
800: "rgba(255,255,255,0.8)",
900: "rgba(255,255,255,0.9)",
},
yellow: {
50: "#FEFCE8",
100: "#FEF9C3",
200: "#FEF08A",
300: "#FDE047",
400: "#FACC15",
500: "#EAB308",
600: "#CA8A04",
700: "#A16207",
800: "#854D0E",
900: "#713F12",
},
};
export const colors = {
// generated
discord: createShades("#314db5"),
opensea: createShades("#3191e8"),
// theme colors
...themeColors,
backgroundDark: "#0E0E10",
// random
backgroundLight: "#F5F6F8",
// blue: themeColors.primary,
// purple: themeColors.primary,
primary: themeColors.blue,
};
-389
View File
@@ -1,389 +0,0 @@
"use client";
import { extendTheme, type Theme } from "@chakra-ui/react";
import { getColor, mode } from "@chakra-ui/theme-tools";
import { colors } from "./colors";
import { skeletonTheme } from "./skeleton";
import { fontWeights, letterSpacings, lineHeights } from "./typography";
const chakraTheme: Theme = extendTheme({
colors,
components: {
Badge: {
baseStyle: {
backgroundColor: "badgeBg",
},
},
Button: {
baseStyle: {
borderRadius: "md",
},
variants: {
// biome-ignore lint/suspicious/noExplicitAny: FIXME
gradient: (props: any) => {
const { theme, fromcolor, tocolor } = props;
const lgFrom = getColor(theme, fromcolor);
const lgTo = getColor(theme, tocolor);
const bgColor = getColor(theme, mode("white", "gray.800")(props));
return {
_hover: {
"> *": {
backgroundPosition: "right",
},
backgroundPosition: "right",
opacity: 0.9,
},
"> *": {
background: `linear-gradient(135deg, ${lgFrom}, ${lgTo}, ${lgFrom})`,
backgroundClip: "text",
backgroundSize: "200%",
textFillColor: "transparent",
transitionDuration: "slow",
transitionProperty: "background",
transitionTimingFunction: "easeOut",
},
background: `linear-gradient(${bgColor}, ${bgColor}) padding-box,
linear-gradient(135deg, ${lgFrom}, ${lgTo}, ${lgFrom}) border-box`,
backgroundSize: "200%",
border: "3px solid",
borderColor: "transparent",
borderRadius: "md",
transitionDuration: "slow",
transitionProperty: "opacity, background",
};
},
// biome-ignore lint/suspicious/noExplicitAny: FIXME
highlighted: (props: any) => ({
_hover: {
boxShadow: "0 0 10px 2px rgba(51, 133, 255, 1)",
transform: "scale(1.05)",
},
bg: "#151515",
borderRadius: props.fullCircle ? "full" : "12px",
size: "lg",
}),
inverted: {
_disabled: {
_hover: {
bg: "bgBlack !important",
},
},
_hover: {
opacity: 0.8,
},
bg: "bgBlack",
color: "bgWhite",
},
outline: {
_hover: {
bg: "transparent",
borderColor: "inputBorderHover",
},
borderColor: "inputBorder",
borderWidth: "1px",
},
},
},
Divider: {
baseStyle: {
borderColor: "borderColor",
},
},
Drawer: {
baseStyle: {
dialog: {
background: "backgroundHighlight",
},
overlay: {
backdropFilter: "blur(5px)",
},
},
},
Heading: {
baseStyle: {
color: "heading",
},
},
Input: {
defaultProps: {
variant: "filled",
},
sizes: {
xl: {
addon: {
borderRadius: "md",
fontSize: "lg",
h: 14,
px: 4,
},
field: {
borderRadius: "md",
fontSize: "lg",
h: 14,
px: 4,
},
},
},
variants: {
filled: {
field: {
_focus: {
borderColor: "blue.500",
},
_hover: {
background: "transparent",
borderColor: "inputBorderHover",
},
background: "transparent",
borderColor: "inputBorder",
borderWidth: "1px",
},
},
},
},
Menu: {
baseStyle: {
item: {
_hover: {
bg: "hsl(var(--muted))",
},
bg: "backgroundHighlight",
py: 3,
},
list: {
bg: "backgroundHighlight",
borderColor: "borderColor",
borderRadius: "lg",
borderWidth: 1,
overflow: "hidden",
py: 0,
},
},
},
Modal: {
baseStyle: {
dialog: {
background: "backgroundHighlight",
},
overlay: {
backdropFilter: "blur(5px)",
},
},
},
NumberInput: {
defaultProps: {
variant: "filled",
},
sizes: {
xl: {
addon: {
borderRadius: "md",
fontSize: "lg",
h: 14,
px: 4,
},
field: {
borderRadius: "md",
fontSize: "lg",
h: 14,
px: 4,
},
},
},
variants: {
filled: {
field: {
_hover: {
background: "inputBgHover",
borderColor: "blue.500",
},
_invalid: {
borderColor: "inputBorder",
},
background: "inputBg",
borderColor: "inputBorder",
borderWidth: "1px",
},
},
},
},
Select: {
defaultProps: {
variant: "filled",
},
variants: {
filled: {
field: {
_hover: {
background: "inputBgHover",
borderColor: "blue.500",
},
background: "inputBg",
borderColor: "borderColor",
borderWidth: "1px",
},
},
},
},
Skeleton: skeletonTheme,
Table: {
baseStyle: {
cell: {
borderColor: "borderColor",
},
tbody: {
background: "backgroundHighlight",
},
thead: {
background: "backgroundHighlight",
borderBottomColor: "hsl(var(--border))",
borderBottomWidth: 1,
},
},
sizes: {
md: {
td: {
borderBottom: "none",
fontSize: "14px",
px: 6,
py: 2,
},
th: {
borderBottom: "none",
color: "faded",
fontSize: "12px",
fontWeight: "600",
py: 4,
},
},
},
},
Text: {
baseStyle: {
color: "paragraph",
},
},
Textarea: {
defaultProps: {
variant: "filled",
},
variants: {
filled: {
_focus: {
borderColor: "blue.500",
},
_hover: {
background: "transparent",
borderColor: "inputBorderHover",
},
background: "transparent",
borderColor: "inputBorder",
borderWidth: "1px",
},
},
},
},
config: {
initialColorMode: "dark",
useSystemColorMode: false,
} as Theme["config"],
fontSizes: [],
fonts: {
body: "Inter, sans-serif",
heading: "Inter, sans-serif",
mono: "IBM Plex Mono, monospace",
},
fontWeights,
letterSpacings,
lineHeights,
semanticTokens: {
colors: {
backgroundBody: {
_dark: "hsl(var(--background))",
default: "hsl(var(--background))",
},
backgroundCardHighlight: {
_dark: "#0b0b0b",
// equivalent to muted/50 but without transparency
default: "#f8f8f8",
},
backgroundHighlight: {
_dark: "#0b0b0b",
// equivalent to muted/50 but without transparency
default: "#f8f8f8",
},
// other
badgeBg: {
_dark: "hsl(var(--background))",
default: "hsl(var(--background))",
},
bgBlack: { _dark: "white", default: "black" },
bgWhite: { _dark: "black", default: "#fff" },
borderColor: {
_dark: "hsl(var(--border))",
default: "hsl(var(--border))",
},
faded: {
_dark: "hsl(var(--muted-foreground))",
default: "hsl(var(--muted-foreground))",
},
heading: {
_dark: "hsl(var(--foreground))",
default: "hsl(var(--foreground))",
},
// inputs
inputBg: { _dark: "hsl(var(--card))", default: "hsl(var(--card))" },
inputBgHover: { _dark: "hsl(var(--card))", default: "hsl(var(--card))" },
inputBorder: { _dark: "hsl(var(--input))", default: "hsl(var(--input))" },
inputBorderHover: {
_dark: "hsl(var(--input))",
default: "hsl(var(--input))",
},
paragraph: {
_dark: "hsl(var(--muted-foreground))",
default: "hsl(var(--muted-foreground))",
},
secondaryCardHighlight: {
_dark: "hsl(var(--background))",
default: "hsl(var(--background))",
},
wordmark: {
_dark: "hsl(var(--muted-foreground))",
default: "hsl(var(--muted-foreground))",
},
},
},
sizes: {
container: {
hero: "1440px",
page: "1170px",
},
},
styles: {
global: {
"::-moz-selection": {
backgroundColor: "#90cdf4",
color: "#fefefe",
},
"::selection": {
backgroundColor: "#90cdf4",
color: "#fefefe",
},
body: {
colorScheme: "dark",
},
"html, body": {
background: "#000",
fontFeatureSettings: `'zero' 1`,
margin: 0,
padding: 0,
scrollBehavior: "smooth",
},
},
},
}) as Theme;
export default chakraTheme;
@@ -1,21 +0,0 @@
import { cssVar, defineStyle, defineStyleConfig } from "@chakra-ui/react";
const $startColor = cssVar("skeleton-start-color");
const $endColor = cssVar("skeleton-end-color");
const thirdweb = defineStyle({
_dark: {
[$startColor.variable]: "colors.gray.900",
[$endColor.variable]: "colors.gray.800",
},
_light: {
[$startColor.variable]: "colors.gray.100",
[$endColor.variable]: "colors.gray.300",
},
});
export const skeletonTheme = defineStyleConfig({
defaultProps: {
variant: "thirdweb",
},
variants: { thirdweb },
});
@@ -1,39 +0,0 @@
export const fontWeights = {
body: 400,
display: 800,
label: 600,
subtitle: 500,
title: 700,
} as const;
export const lineHeights = {
body: 1.6,
display: 1.2,
label: 1,
subtitle: 1.6,
title: 1.125,
} as const;
export const letterSpacings = {
body: "initial",
display: -1.5,
label: "initial",
subtitle: 0.1,
title: 0.15,
} as const;
type TypographyBase = "display" | "title" | "subtitle" | "label" | "body";
export type TypographySize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
export type TypographyFontSize = `${TypographyBase}.${TypographySize}`;
export type HeadingBase = Exclude<TypographyBase, "body">;
export type HeadingSizes = `${HeadingBase}.${TypographySize}`;
export type TextBase = Exclude<
TypographyBase,
"display" | "title" | "subtitle"
>;
export type TextSizes = `${TextBase}.${TypographySize}`;
@@ -1,11 +0,0 @@
import type { TypographyFontSize } from "chakra/theme/typography";
const FONT_SIZE_CSS_VAR_PREFIX = "--tw-font-size-" as const;
function createCssVariable(size: TypographyFontSize) {
return `${FONT_SIZE_CSS_VAR_PREFIX}${size.replace(/\./g, "-")}`;
}
export function convertFontSizeToCSSVar(fontSize: TypographyFontSize) {
return `var(${createCssVariable(fontSize)})`;
}
-8
View File
@@ -195,14 +195,6 @@ input:-webkit-autofill:active {
text-decoration: var(--shiki-dark-text-decoration) !important;
}
/* Remove this when chakra is removed */
/* Make chakra modals have same z-index as shadcn dialog */
body {
--chakra-zIndices-modal: 50;
--chakra-zIndices-overlay: 50;
--chakra-colors-chakra-border-color: hsl(var(--border));
}
* {
scrollbar-width: thin;
scrollbar-color: hsl(var(--muted)) transparent;
-1037
View File
File diff suppressed because it is too large Load Diff