From e3df4fcf42ea3b4dc2d0336e330654f0abf8f2c4 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Sun, 1 Feb 2026 09:03:12 +0800 Subject: [PATCH] refactor(sidebar): split nav views and add router decorator Signed-off-by: Noa Virellia --- client/cms/.storybook/preview.tsx | 16 ++++++- .../cms/src/components/hoc/with-fallback.tsx | 20 --------- .../{app-sidebar.tsx => app-sidebar.view.tsx} | 9 ++-- .../{nav-main.tsx => nav-main.view.tsx} | 0 ...v-secondary.tsx => nav-secondary.view.tsx} | 0 .../components/sidebar/nav-user.container.tsx | 11 +++++ .../components/sidebar/nav-user.skeletion.tsx | 18 ++++++++ .../{nav-user.tsx => nav-user.view.tsx} | 27 ++---------- client/cms/src/routes/_workbenchLayout.tsx | 15 ++++++- client/cms/src/stories/exampleUser.ts | 8 ++++ client/cms/src/stories/nav-user.stories.tsx | 12 ------ .../profile/edit-profile.dialog.stories.tsx | 10 +---- .../src/stories/profile/profile.stories.tsx | 13 ++---- client/cms/src/stories/sidebar.stories.tsx | 43 +++++++++++++++++++ 14 files changed, 119 insertions(+), 83 deletions(-) delete mode 100644 client/cms/src/components/hoc/with-fallback.tsx rename client/cms/src/components/sidebar/{app-sidebar.tsx => app-sidebar.view.tsx} (81%) rename client/cms/src/components/sidebar/{nav-main.tsx => nav-main.view.tsx} (100%) rename client/cms/src/components/sidebar/{nav-secondary.tsx => nav-secondary.view.tsx} (100%) create mode 100644 client/cms/src/components/sidebar/nav-user.container.tsx create mode 100644 client/cms/src/components/sidebar/nav-user.skeletion.tsx rename client/cms/src/components/sidebar/{nav-user.tsx => nav-user.view.tsx} (81%) create mode 100644 client/cms/src/stories/exampleUser.ts delete mode 100644 client/cms/src/stories/nav-user.stories.tsx create mode 100644 client/cms/src/stories/sidebar.stories.tsx diff --git a/client/cms/.storybook/preview.tsx b/client/cms/.storybook/preview.tsx index ab9f5f5..b9c2ec1 100644 --- a/client/cms/.storybook/preview.tsx +++ b/client/cms/.storybook/preview.tsx @@ -1,9 +1,21 @@ -import type { Preview } from '@storybook/react-vite'; +import type { Decorator, Preview } from '@storybook/react-vite'; import { ThemeProvider } from '../src/components/theme-provider'; import '../src/index.css'; +import { createRootRoute, createRouter, RouterProvider } from '@tanstack/react-router'; + +const RouterDecorator: Decorator = (Story) => { + const rootRoute = createRootRoute({ component: () => }); + const routeTree = rootRoute; + const router = createRouter({ routeTree }); + return ; +}; + +const ThemeDecorator: Decorator = (Story) => { + return ; +}; const preview: Preview = { - decorators: [(Story) => ], + decorators: [RouterDecorator, ThemeDecorator], parameters: { controls: { matchers: { diff --git a/client/cms/src/components/hoc/with-fallback.tsx b/client/cms/src/components/hoc/with-fallback.tsx deleted file mode 100644 index 5d4c0e6..0000000 --- a/client/cms/src/components/hoc/with-fallback.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { ReactNode } from 'react'; -import React, { Suspense } from 'react'; - -export function withFallback

( - Component: React.ComponentType

, - fallback: ReactNode, -) { - const Wrapped: React.FC

= (props) => { - return ( - - - - ); - }; - - Wrapped.displayName = `withFallback(${Component.displayName! || Component.name || 'Component' - })`; - - return Wrapped; -} diff --git a/client/cms/src/components/sidebar/app-sidebar.tsx b/client/cms/src/components/sidebar/app-sidebar.view.tsx similarity index 81% rename from client/cms/src/components/sidebar/app-sidebar.tsx rename to client/cms/src/components/sidebar/app-sidebar.view.tsx index 5b971d5..f6f1017 100644 --- a/client/cms/src/components/sidebar/app-sidebar.tsx +++ b/client/cms/src/components/sidebar/app-sidebar.view.tsx @@ -1,8 +1,8 @@ import type { NavData } from '@/lib/navData'; import * as React from 'react'; import NixOSLogo from '@/assets/nixos.svg?react'; -import { NavMain } from '@/components/sidebar/nav-main'; -import { NavSecondary } from '@/components/sidebar/nav-secondary'; +import { NavMain } from '@/components/sidebar/nav-main.view'; +import { NavSecondary } from '@/components/sidebar/nav-secondary.view'; import { Sidebar, SidebarContent, @@ -12,9 +12,8 @@ import { SidebarMenuButton, SidebarMenuItem, } from '@/components/ui/sidebar'; -import { NavUser } from './nav-user'; -export function AppSidebar({ navData, ...props }: React.ComponentProps & { navData: NavData }) { +export function AppSidebar({ navData, footerWidget, ...props }: React.ComponentProps & { navData: NavData; footerWidget: React.ReactNode }) { return ( @@ -37,7 +36,7 @@ export function AppSidebar({ navData, ...props }: React.ComponentProps - + {footerWidget} ); diff --git a/client/cms/src/components/sidebar/nav-main.tsx b/client/cms/src/components/sidebar/nav-main.view.tsx similarity index 100% rename from client/cms/src/components/sidebar/nav-main.tsx rename to client/cms/src/components/sidebar/nav-main.view.tsx diff --git a/client/cms/src/components/sidebar/nav-secondary.tsx b/client/cms/src/components/sidebar/nav-secondary.view.tsx similarity index 100% rename from client/cms/src/components/sidebar/nav-secondary.tsx rename to client/cms/src/components/sidebar/nav-secondary.view.tsx diff --git a/client/cms/src/components/sidebar/nav-user.container.tsx b/client/cms/src/components/sidebar/nav-user.container.tsx new file mode 100644 index 0000000..3e65113 --- /dev/null +++ b/client/cms/src/components/sidebar/nav-user.container.tsx @@ -0,0 +1,11 @@ +import { useUserInfo } from '@/hooks/data/useUserInfo'; +import { NavUserView } from './nav-user.view'; + +export function NavUserContainer() { + const { data } = useUserInfo(); + return ( + + ); +} diff --git a/client/cms/src/components/sidebar/nav-user.skeletion.tsx b/client/cms/src/components/sidebar/nav-user.skeletion.tsx new file mode 100644 index 0000000..de9d3c7 --- /dev/null +++ b/client/cms/src/components/sidebar/nav-user.skeletion.tsx @@ -0,0 +1,18 @@ +import { IconDotsVertical } from '@tabler/icons-react'; +import { SidebarMenuButton } from '../ui/sidebar'; +import { Skeleton } from '../ui/skeleton'; + +export function NavUserSkeleton() { + return ( + + +

+ + +
+ + + ); +} diff --git a/client/cms/src/components/sidebar/nav-user.tsx b/client/cms/src/components/sidebar/nav-user.view.tsx similarity index 81% rename from client/cms/src/components/sidebar/nav-user.tsx rename to client/cms/src/components/sidebar/nav-user.view.tsx index b60a467..6ba97ff 100644 --- a/client/cms/src/components/sidebar/nav-user.tsx +++ b/client/cms/src/components/sidebar/nav-user.view.tsx @@ -1,5 +1,6 @@ -import { identicon } from '@dicebear/collection'; +import type { ServiceUserUserInfoData } from '@/client'; +import { identicon } from '@dicebear/collection'; import { createAvatar } from '@dicebear/core'; import { IconDotsVertical, @@ -25,15 +26,10 @@ import { SidebarMenuItem, useSidebar, } from '@/components/ui/sidebar'; -import { useUserInfo } from '@/hooks/data/useUserInfo'; import { logout } from '@/lib/token'; -import { withFallback } from '../hoc/with-fallback'; -import { Skeleton } from '../ui/skeleton'; -export function NavUser_() { +export function NavUserView({ user }: { user: ServiceUserUserInfoData }) { const { isMobile } = useSidebar(); - const { data } = useUserInfo(); - const user = data.data!; const IdentIcon = useMemo(() => { const avatar = createAvatar(identicon, { @@ -94,20 +90,3 @@ export function NavUser_() { ); } - -function NavUserSkeleton() { - return ( - - -
- - -
- -
- ); -} - -export const NavUser = withFallback(NavUser_, ); diff --git a/client/cms/src/routes/_workbenchLayout.tsx b/client/cms/src/routes/_workbenchLayout.tsx index 4bf19ef..65a366f 100644 --- a/client/cms/src/routes/_workbenchLayout.tsx +++ b/client/cms/src/routes/_workbenchLayout.tsx @@ -1,5 +1,8 @@ import { createFileRoute, Outlet, useRouterState } from '@tanstack/react-router'; -import { AppSidebar } from '@/components/sidebar/app-sidebar'; +import { Suspense } from 'react'; +import { AppSidebar } from '@/components/sidebar/app-sidebar.view'; +import { NavUserContainer } from '@/components/sidebar/nav-user.container'; +import { NavUserSkeleton } from '@/components/sidebar/nav-user.skeletion'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; import { navData } from '@/lib/navData'; @@ -27,7 +30,15 @@ function RouteComponent() { } as React.CSSProperties } > - + }> + + + )} + variant="inset" + />
diff --git a/client/cms/src/stories/exampleUser.ts b/client/cms/src/stories/exampleUser.ts new file mode 100644 index 0000000..40800ff --- /dev/null +++ b/client/cms/src/stories/exampleUser.ts @@ -0,0 +1,8 @@ +export const user = { + username: 'nvirellia', + nickname: 'Noa Virellia', + subtitle: '天生骄傲', + email: 'noa@requiem.garden', + bio: '', + avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', +}; diff --git a/client/cms/src/stories/nav-user.stories.tsx b/client/cms/src/stories/nav-user.stories.tsx deleted file mode 100644 index 679366a..0000000 --- a/client/cms/src/stories/nav-user.stories.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { NavUser_ } from '@/components/sidebar/nav-user'; - -const meta = { - title: 'NavUser', - component: NavUser_, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Primary: Story = {}; diff --git a/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx b/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx index d3dcebf..e67e929 100644 --- a/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx +++ b/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { EditProfileDialogView } from '@/components/profile/edit-profile.dialog.view'; +import { user } from '../exampleUser'; const meta = { title: 'Profile/EditDialog', @@ -27,14 +28,7 @@ type Story = StoryObj; export const Primary: Story = { args: { - user: { - username: 'nvirellia', - nickname: 'Noa Virellia', - subtitle: '天生骄傲', - email: 'noa@requiem.garden', - bio: '', - avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', - }, + user, updateProfile: async () => { }, }, parameters: { diff --git a/client/cms/src/stories/profile/profile.stories.tsx b/client/cms/src/stories/profile/profile.stories.tsx index 8f84d18..7590c49 100644 --- a/client/cms/src/stories/profile/profile.stories.tsx +++ b/client/cms/src/stories/profile/profile.stories.tsx @@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ProfileError } from '@/components/profile/profile.error'; import { ProfileSkeleton } from '@/components/profile/profile.skeleton'; import { ProfileView } from '@/components/profile/profile.view'; +import { user } from '../exampleUser'; const queryClient = new QueryClient(); @@ -23,20 +24,12 @@ type Story = StoryObj; export const Primary: Story = { args: { - user: { - username: 'nvirellia', - nickname: 'Noa Virellia', - subtitle: '天生骄傲', - email: 'noa@requiem.garden', - bio: '', - avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', - }, + user, onSaveBio: async () => Promise.resolve(), }, - }; -export const Skeleton: Story = { +export const Loading: Story = { render: () => , args: { user: {}, diff --git a/client/cms/src/stories/sidebar.stories.tsx b/client/cms/src/stories/sidebar.stories.tsx new file mode 100644 index 0000000..06dfae0 --- /dev/null +++ b/client/cms/src/stories/sidebar.stories.tsx @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { AppSidebar } from '@/components/sidebar/app-sidebar.view'; +import { NavUserSkeleton } from '@/components/sidebar/nav-user.skeletion'; +import { NavUserView } from '@/components/sidebar/nav-user.view'; +import { SidebarProvider } from '@/components/ui/sidebar'; +import { navData } from '@/lib/navData'; +import { user } from './exampleUser'; + +const meta = { + title: 'Navigation/Sidebar', + component: AppSidebar, + decorators: [ + Story => ( + + + + ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + navData, + footerWidget: , + }, +}; + +export const Loading: Story = { + args: { + navData, + footerWidget: , + }, +};