diff --git a/package.json b/package.json index 6494087..2581ecb 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-hover-card": "^1.1.14", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.7", "@radix-ui/react-progress": "^1.1.3", "@radix-ui/react-radio-group": "^1.2.3", @@ -64,9 +65,9 @@ "eslint-config-next": "15.2.1", "eslint-plugin-react-hooks": "^5.2.0", "husky": "^9.1.7", + "rehype-highlight": "^7.0.2", "tailwindcss": "^4", - "typescript": "^5", - "rehype-highlight": "^7.0.2" + "typescript": "^5" }, "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b", "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c093c28..f6ccbe8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: '@radix-ui/react-label': specifier: ^2.1.2 version: 2.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-navigation-menu': + specifier: ^1.2.13 + version: 1.2.13(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-popover': specifier: ^1.1.7 version: 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -869,6 +872,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-navigation-menu@1.2.13': + resolution: {integrity: sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popover@1.1.7': resolution: {integrity: sha512-I38OYWDmJF2kbO74LX8UsFydSHWOJuQ7LxPnTefjxxvdvPLempvAnmsyX9UsBlywcbSGpRH7oMLfkUf+ij4nrw==} peerDependencies: @@ -1338,6 +1354,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-rect@1.1.0': resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} peerDependencies: @@ -1400,6 +1425,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} @@ -4188,6 +4226,28 @@ snapshots: '@types/react': 19.0.10 '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-navigation-menu@1.2.13(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-popover@1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -4650,6 +4710,12 @@ snapshots: optionalDependencies: '@types/react': 19.0.10 + '@radix-ui/react-use-previous@1.1.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-use-rect@1.1.0(@types/react@19.0.10)(react@19.0.0)': dependencies: '@radix-ui/rect': 1.1.0 @@ -4696,6 +4762,15 @@ snapshots: '@types/react': 19.0.10 '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/rect@1.1.0': {} '@radix-ui/rect@1.1.1': {} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index a826b6c..979f74f 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -27,7 +27,7 @@ import {ApiResponse} from '@/lib/api' import {Label} from '@/components/ui/label' import logo from '@/assets/logo.webp' import bg from './_assets/bg.webp' -import {useProfileStore} from '@/components/providers/StoreProvider' +import {useProfileStore} from '@/app/stores' import Link from 'next/link' export type LoginPageProps = {} diff --git a/src/app/(home)/@header/_client/help.tsx b/src/app/(home)/@header/_client/help.tsx index 4d3af40..43eb5e3 100644 --- a/src/app/(home)/@header/_client/help.tsx +++ b/src/app/(home)/@header/_client/help.tsx @@ -8,7 +8,7 @@ import banner from '@/assets/header/help/banner.webp' export default function HelpMenu() { return ( - + - banner + banner ) } diff --git a/src/app/(home)/@header/_client/navs.tsx b/src/app/(home)/@header/_client/navs.tsx index 925b206..f98acc8 100644 --- a/src/app/(home)/@header/_client/navs.tsx +++ b/src/app/(home)/@header/_client/navs.tsx @@ -7,7 +7,7 @@ export function LinkItem(props: { href: string }) { return ( -
  • +
  • void }) { return ( -
  • +
  • - ) : ( + return ( diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index b39b313..5b96999 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -4,21 +4,22 @@ import {cva, VariantProps} from 'class-variance-authority' export const buttonVariants = cva( [ - `transition-all duration-200 ease-in-out`, - `h-10 px-4 rounded-md cursor-pointer whitespace-nowrap`, - 'inline-flex items-center justify-center gap-2', + `transition-all duration-200 ease-in-out`, // 过渡动画 + `h-10 px-4 rounded-md cursor-pointer whitespace-nowrap`, // 样式 + 'outline-none focus-visible:ring-4 ring-blue-200', // 焦点样式 + 'disabled:pointer-events-none disabled:opacity-50 ', // 禁用样式 + 'aria-invalid:ring-fail/20 dark:aria-invalid:ring-fail/40 aria-invalid:border-fail', // 无效状态样式 + 'inline-flex items-center justify-center gap-2', // 布局 '[&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 shrink-0 [&_svg]:shrink-0 ', - 'outline-none focus-visible:ring-4 ring-blue-200', - 'disabled:pointer-events-none disabled:opacity-50 ', - 'aria-invalid:ring-fail/20 dark:aria-invalid:ring-fail/40 aria-invalid:border-fail', ], { variants: { theme: { - gradient: 'bg-gradient-to-r from-blue-400 to-cyan-300 text-white ring-offset-2', + gradient: 'bg-gradient-to-r from-blue-400 to-cyan-300 text-white ring-offset-2 hover:from-blue-500 hover:to-cyan-400', default: 'bg-primary text-primary-foreground hover:bg-primary/90', outline: 'border bg-background hover:bg-secondary hover:text-secondary-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', ghost: 'text-foreground bg-transparent', + text: '', accent: 'bg-accent text-accent-foreground hover:bg-accent/90', fail: 'bg-fail text-white hover:bg-fail/90', warn: '', @@ -70,6 +71,11 @@ export const buttonVariants = cva( color: 'fail', className: 'hover:bg-fail/10 text-fail', }, + { + theme: 'text', + color: 'primary', + className: 'hover:text-primary', + }, ], defaultVariants: { theme: 'default', diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx new file mode 100644 index 0000000..aadb438 --- /dev/null +++ b/src/components/ui/navigation-menu.tsx @@ -0,0 +1,126 @@ +import {ComponentProps, ReactNode} from 'react' +import Link from 'next/link' +import * as Primitive from '@radix-ui/react-navigation-menu' +import {ChevronDownIcon, ChevronUpIcon} from 'lucide-react' +import {Button, buttonVariants} from './button' +import {merge} from '@/lib/utils' + +const NavigationMenuItemStyle = buttonVariants({ + theme: 'text', + color: `primary`, + className: 'h-full rounded-none', +}) + +function Navigation(props: ComponentProps) { + return +} + +function NavigationGroup(props: ComponentProps) { + return +} + +function NavigationItem(props: ComponentProps) { + return +} + +function NavigationLink(props: { + text: ReactNode + href: string + classNameOverride?: string +}) { + return ( + + + {props.text} + + + ) +} + +function NavigationTrigger(props: { + suffix?: boolean + text: ReactNode +}) { + const suffix = props.suffix ?? true + + return ( + + + + ) +} + +function NavigationItemContent(props: ComponentProps) { + return ( + + ) +} + +function NavigationLinkItem(props: { + href: string + text: ReactNode +}) { + return ( + + + + ) +} + +function NavigationTriggerItem(props: { + text: ReactNode + suffix?: boolean + children?: ReactNode + className?: string +}) { + return ( + + + + {props.children} + + + ) +} + +function NavigationIndicator() { + return ( + + ) +} + +function NavigationMenuViewport(props: ComponentProps) { + return ( +
    + +
    + ) +} + +export { + Navigation, + NavigationGroup, + NavigationItem, + NavigationItemContent, + NavigationTrigger, + NavigationLink, + NavigationIndicator, + NavigationMenuViewport, + NavigationLinkItem, + NavigationTriggerItem, +} diff --git a/src/lib/stores/client.ts b/src/lib/stores/client.ts new file mode 100644 index 0000000..4b5f080 --- /dev/null +++ b/src/lib/stores/client.ts @@ -0,0 +1,46 @@ +import {createStore} from 'zustand/vanilla' +import {persist} from 'zustand/middleware' + +export type ClientStore = ClientState & ClientActions + +type Point = 'sm' | 'md' | 'lg' | 'xl' + +export type ClientState = { + breakpoint: { + sm: boolean + md: boolean + lg: boolean + xl: boolean + } +} + +export type ClientActions = { + setBreakpoints: (breakpoints: Partial) => void +} + +export const createClientStore = () => { + return createStore()(persist( + setState => ({ + breakpoint: { + sm: false, + md: false, + lg: false, + xl: false, + }, + + setBreakpoints: breakpoints => setState(state => ({ + breakpoint: { + ...state.breakpoint, + ...breakpoints, + }, + })), + }), + + { + name: 'client-store', + partialize: state => ({ + device: state.breakpoint, + }), + }, + )) +} diff --git a/src/lib/stores/layout.ts b/src/lib/stores/layout.ts index a6054ab..7cee0da 100644 --- a/src/lib/stores/layout.ts +++ b/src/lib/stores/layout.ts @@ -1,4 +1,5 @@ import {createStore} from 'zustand/vanilla' +import {persist} from 'zustand/middleware' export type LayoutStore = LayoutState & LayoutActions @@ -11,14 +12,25 @@ export type LayoutActions = { setNavbar: (navbar: boolean) => void } -export const createLayoutStore = (open: boolean) => { - return createStore()(setState => ({ - navbar: open, - toggleNavbar: () => setState((state) => { - return {navbar: !state.navbar} +export const createLayoutStore = () => { + return createStore()(persist( + setState => ({ + navbar: false, + + toggleNavbar: () => setState((state) => { + return {navbar: !state.navbar} + }), + + setNavbar: navbar => setState((_) => { + return {navbar} + }), }), - setNavbar: navbar => setState((_) => { - return {navbar} - }), - })) + + { + name: 'layout-store', + partialize: state => ({ + navbar: state.navbar, + }), + }, + )) }