diff --git a/apps/web/app/admin/_components/admin-sidebar.tsx b/apps/web/app/admin/_components/admin-sidebar.tsx index b72cb045f..90d6ff054 100644 --- a/apps/web/app/admin/_components/admin-sidebar.tsx +++ b/apps/web/app/admin/_components/admin-sidebar.tsx @@ -15,7 +15,6 @@ import { SidebarHeader, SidebarMenu, SidebarMenuButton, - SidebarProvider, } from '@kit/ui/shadcn-sidebar'; import { AppLogo } from '~/components/app-logo'; @@ -25,46 +24,44 @@ export function AdminSidebar() { const path = usePathname(); return ( - - - - - + + + + - - - Admin + + + Admin - - - - - - Dashboard - - + + + + + + Dashboard + + - + - - - Accounts - - - - - - + + Accounts + + + + + + - - - - - + + + + ); } diff --git a/apps/web/app/admin/accounts/[id]/page.tsx b/apps/web/app/admin/accounts/[id]/page.tsx index 31db7e2e7..20d2447f7 100644 --- a/apps/web/app/admin/accounts/[id]/page.tsx +++ b/apps/web/app/admin/accounts/[id]/page.tsx @@ -3,7 +3,6 @@ import { cache } from 'react'; import { AdminAccountPage } from '@kit/admin/components/admin-account-page'; import { AdminGuard } from '@kit/admin/components/admin-guard'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; -import { PageBody } from '@kit/ui/page'; interface Params { params: Promise<{ @@ -24,11 +23,7 @@ async function AccountPage(props: Params) { const params = await props.params; const account = await loadAccount(params.id); - return ( - - - - ); + return ; } export default AdminGuard(AccountPage); diff --git a/apps/web/app/admin/accounts/page.tsx b/apps/web/app/admin/accounts/page.tsx index 9406a9c46..781b9707e 100644 --- a/apps/web/app/admin/accounts/page.tsx +++ b/apps/web/app/admin/accounts/page.tsx @@ -3,6 +3,7 @@ import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs'; import { AdminAccountsTable } from '@kit/admin/components/admin-accounts-table'; import { AdminGuard } from '@kit/admin/components/admin-guard'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; +import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { PageBody, PageHeader } from '@kit/ui/page'; interface SearchParams { @@ -28,10 +29,7 @@ async function AccountsPage(props: AdminAccountsPageProps) { return ( <> - + } /> - - - + + + + + - - - + + + - {props.children} - + {props.children} + + ); } + +async function getLayoutState() { + const cookieStore = await cookies(); + const sidebarOpenCookie = cookieStore.get('sidebar:state'); + + return { + open: sidebarOpenCookie?.value !== 'true', + }; +} diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index f39d6f9a6..8be8bd84c 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -5,10 +5,7 @@ import { PageBody, PageHeader } from '@kit/ui/page'; function AdminPage() { return ( <> - + diff --git a/apps/web/app/home/(user)/_components/home-account-selector.tsx b/apps/web/app/home/(user)/_components/home-account-selector.tsx index 814f05773..315b3b800 100644 --- a/apps/web/app/home/(user)/_components/home-account-selector.tsx +++ b/apps/web/app/home/(user)/_components/home-account-selector.tsx @@ -29,7 +29,7 @@ export function HomeAccountSelector(props: { return ( , ) { return ( - - {props.children} - + {props.children} ); } diff --git a/apps/web/app/home/(user)/_components/home-sidebar.tsx b/apps/web/app/home/(user)/_components/home-sidebar.tsx index 132113bab..8609e43f0 100644 --- a/apps/web/app/home/(user)/_components/home-sidebar.tsx +++ b/apps/web/app/home/(user)/_components/home-sidebar.tsx @@ -20,24 +20,23 @@ import { HomeAccountSelector } from './home-account-selector'; interface HomeSidebarProps { workspace: UserWorkspace; - minimized: boolean; } export function HomeSidebar(props: HomeSidebarProps) { const { workspace, user, accounts } = props.workspace; + const collapsible = personalAccountNavigationConfig.sidebarCollapsedStyle; return ( - +
} > diff --git a/apps/web/app/home/(user)/layout.tsx b/apps/web/app/home/(user)/layout.tsx index 4d9e8e0af..814a997d5 100644 --- a/apps/web/app/home/(user)/layout.tsx +++ b/apps/web/app/home/(user)/layout.tsx @@ -3,12 +3,7 @@ import { use } from 'react'; import { cookies } from 'next/headers'; import { UserWorkspaceContextProvider } from '@kit/accounts/components'; -import { - Page, - PageLayoutStyle, - PageMobileNavigation, - PageNavigation, -} from '@kit/ui/page'; +import { Page, PageMobileNavigation, PageNavigation } from '@kit/ui/page'; import { SidebarProvider } from '@kit/ui/shadcn-sidebar'; import { AppLogo } from '~/components/app-logo'; @@ -22,9 +17,9 @@ import { HomeSidebar } from './_components/home-sidebar'; import { loadUserWorkspace } from './_lib/server/load-user-workspace'; function UserHomeLayout({ children }: React.PropsWithChildren) { - const style = use(getLayoutStyle()); + const state = use(getLayoutState()); - if (style === 'sidebar') { + if (state.style === 'sidebar') { return {children}; } @@ -35,14 +30,16 @@ export default withI18n(UserHomeLayout); function SidebarLayout({ children }: React.PropsWithChildren) { const workspace = use(loadUserWorkspace()); - const sidebarMinimized = personalAccountNavigationConfig.sidebarCollapsed; + const state = use(getLayoutState()); + + console.log('state', state); return ( - + - + @@ -90,11 +87,21 @@ function MobileNavigation({ ); } -async function getLayoutStyle() { +async function getLayoutState() { const cookieStore = await cookies(); - return ( - (cookieStore.get('layout-style')?.value as PageLayoutStyle) ?? - personalAccountNavigationConfig.style - ); + const layoutStyleCookie = cookieStore.get('layout-style'); + const sidebarOpenCookie = cookieStore.get('sidebar:state'); + + const sidebarOpenCookieValue = sidebarOpenCookie + ? sidebarOpenCookie.value === 'false' + : personalAccountNavigationConfig.sidebarCollapsed; + + const style = + layoutStyleCookie?.value ?? personalAccountNavigationConfig.style; + + return { + open: sidebarOpenCookieValue, + style, + }; } diff --git a/apps/web/app/home/[account]/_components/team-account-accounts-selector.tsx b/apps/web/app/home/[account]/_components/team-account-accounts-selector.tsx index 23463586f..e1da4772b 100644 --- a/apps/web/app/home/[account]/_components/team-account-accounts-selector.tsx +++ b/apps/web/app/home/[account]/_components/team-account-accounts-selector.tsx @@ -1,8 +1,11 @@ 'use client'; +import { useContext } from 'react'; + import { useRouter } from 'next/navigation'; import { AccountSelector } from '@kit/accounts/account-selector'; +import { SidebarContext } from '@kit/ui/shadcn-sidebar'; import featureFlagsConfig from '~/config/feature-flags.config'; import pathsConfig from '~/config/paths.config'; @@ -20,17 +23,16 @@ export function TeamAccountAccountsSelector(params: { value: string | null; image: string | null; }>; - - collapsed?: boolean; }) { const router = useRouter(); + const ctx = useContext(SidebarContext); return ( { const path = value diff --git a/apps/web/app/home/[account]/_components/team-account-layout-page-header.tsx b/apps/web/app/home/[account]/_components/team-account-layout-page-header.tsx index 80491ead9..cecd62b30 100644 --- a/apps/web/app/home/[account]/_components/team-account-layout-page-header.tsx +++ b/apps/web/app/home/[account]/_components/team-account-layout-page-header.tsx @@ -8,8 +8,6 @@ export function TeamAccountLayoutPageHeader( }>, ) { return ( - - {props.children} - + {props.children} ); } diff --git a/apps/web/app/home/[account]/_components/team-account-layout-sidebar-navigation.tsx b/apps/web/app/home/[account]/_components/team-account-layout-sidebar-navigation.tsx index 2163fe5b8..9e1eeb456 100644 --- a/apps/web/app/home/[account]/_components/team-account-layout-sidebar-navigation.tsx +++ b/apps/web/app/home/[account]/_components/team-account-layout-sidebar-navigation.tsx @@ -1,13 +1,12 @@ +import { z } from 'zod'; + +import { NavigationConfigSchema } from '@kit/ui/navigation-schema'; import { SidebarNavigation } from '@kit/ui/shadcn-sidebar'; -import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config'; - export function TeamAccountLayoutSidebarNavigation({ - account, + config, }: React.PropsWithChildren<{ - account: string; + config: z.infer; }>) { - const routes = getTeamAccountSidebarConfig(account); - - return ; + return ; } diff --git a/apps/web/app/home/[account]/_components/team-account-layout-sidebar.tsx b/apps/web/app/home/[account]/_components/team-account-layout-sidebar.tsx index 69b8db6f7..8b3c3855e 100644 --- a/apps/web/app/home/[account]/_components/team-account-layout-sidebar.tsx +++ b/apps/web/app/home/[account]/_components/team-account-layout-sidebar.tsx @@ -1,5 +1,3 @@ -'use client'; - import type { User } from '@supabase/supabase-js'; import { @@ -7,11 +5,10 @@ import { SidebarContent, SidebarFooter, SidebarHeader, - useSidebar, } from '@kit/ui/shadcn-sidebar'; -import { cn } from '@kit/ui/utils'; import { ProfileAccountDropdownContainer } from '~/components//personal-account-dropdown-container'; +import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config'; import { TeamAccountNotifications } from '~/home/[account]/_components/team-account-notifications'; import { TeamAccountAccountsSelector } from '../_components/team-account-accounts-selector'; @@ -47,27 +44,21 @@ function SidebarContainer(props: { }) { const { account, accounts, user } = props; const userId = user.id; - const { minimized } = useSidebar(); - const className = cn( - 'flex max-w-full items-center justify-between space-x-4', - { - 'w-full justify-start space-x-0': minimized, - }, - ); + const config = getTeamAccountSidebarConfig(account); + const collapsible = config.sidebarCollapsedStyle; return ( - + -
+
-
+
- + diff --git a/apps/web/app/home/[account]/billing/error.tsx b/apps/web/app/home/[account]/billing/error.tsx index ef97b93c9..974e826f3 100644 --- a/apps/web/app/home/[account]/billing/error.tsx +++ b/apps/web/app/home/[account]/billing/error.tsx @@ -20,10 +20,7 @@ export default function BillingErrorPage({ return ( <> - } - description={} - /> + } />
diff --git a/apps/web/app/home/[account]/layout.tsx b/apps/web/app/home/[account]/layout.tsx index a91eeb9e6..0bb3c6693 100644 --- a/apps/web/app/home/[account]/layout.tsx +++ b/apps/web/app/home/[account]/layout.tsx @@ -27,9 +27,9 @@ type TeamWorkspaceLayoutProps = React.PropsWithChildren<{ function TeamWorkspaceLayout({ children, params }: TeamWorkspaceLayoutProps) { const account = use(params).account; - const style = use(getLayoutStyle(account)); + const state = use(getLayoutState(account)); - if (style === 'sidebar') { + if (state.style === 'sidebar') { return {children}; } @@ -43,6 +43,7 @@ function SidebarLayout({ account: string; }>) { const data = use(loadTeamWorkspace(account)); + const state = use(getLayoutState(account)); const accounts = data.accounts.map(({ name, slug, picture_url }) => ({ label: name, @@ -50,11 +51,9 @@ function SidebarLayout({ image: picture_url, })); - const minimized = getTeamAccountSidebarConfig(account).sidebarCollapsed; - return ( - + - - -
-
-
- - - - {props.account.name} - -
- - Personal Account - - - Banned - -
- -
+ <> + + } + > +
- @@ -101,15 +86,15 @@ async function PersonalAccountPage(props: { account: Account }) { - - @@ -122,20 +107,43 @@ async function PersonalAccountPage(props: { account: Account }) {
-
+ -
- + +
+
+
+ -
- Teams + + {props.account.name} + +
-
- + Personal Account + + + Banned +
-
-
+ +
+ + +
+ Teams + +
+ +
+
+
+
+ ); } @@ -145,50 +153,57 @@ async function TeamAccountPage(props: { const members = await getMembers(props.account.slug ?? ''); return ( -
- - -
-
-
- - - - {props.account.name} - -
- - Team Account -
- + <> + + } + > -
+ -
-
- + +
+
+
+ -
- Team Members + + {props.account.name} + +
- + Team Account
-
-
+ +
+
+ + +
+ Team Members + + +
+
+
+ + ); } diff --git a/packages/ui/src/makerkit/navigation-config.schema.ts b/packages/ui/src/makerkit/navigation-config.schema.ts index 37082d8a8..7d9a9710b 100644 --- a/packages/ui/src/makerkit/navigation-config.schema.ts +++ b/packages/ui/src/makerkit/navigation-config.schema.ts @@ -40,8 +40,9 @@ export const NavigationConfigSchema = z.object({ style: z.enum(['custom', 'sidebar', 'header']).default('sidebar'), sidebarCollapsed: z .enum(['false', 'true']) - .default('false') + .default('true') .optional() .transform((value) => value === `true`), + sidebarCollapsedStyle: z.enum(['offcanvas', 'icon', 'none']).default('icon'), routes: z.array(z.union([RouteGroup, Divider])), }); diff --git a/packages/ui/src/makerkit/page.tsx b/packages/ui/src/makerkit/page.tsx index 8652bdbc0..190d39b1d 100644 --- a/packages/ui/src/makerkit/page.tsx +++ b/packages/ui/src/makerkit/page.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import { cn } from '../lib/utils'; +import { Separator } from '../shadcn/separator'; +import { SidebarTrigger } from '../shadcn/sidebar'; import { If } from './if'; export type PageLayoutStyle = 'sidebar' | 'header' | 'custom'; @@ -12,6 +14,10 @@ type PageProps = React.PropsWithChildren<{ sticky?: boolean; }>; +const ENABLE_SIDEBAR_TRIGGER = process.env.NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER + ? process.env.NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER === 'true' + : true; + export function Page(props: PageProps) { switch (props.style) { case 'header': @@ -118,7 +124,7 @@ export function PageNavigation(props: React.PropsWithChildren) { export function PageDescription(props: React.PropsWithChildren) { return ( -
+
{props.children}
@@ -130,7 +136,7 @@ export function PageTitle(props: React.PropsWithChildren) { return (

{props.children} @@ -147,10 +153,12 @@ export function PageHeader({ title, description, className, + displaySidebarTrigger = ENABLE_SIDEBAR_TRIGGER, }: React.PropsWithChildren<{ className?: string; title?: string | React.ReactNode; description?: string | React.ReactNode; + displaySidebarTrigger?: boolean; }>) { return (
-
- - {description} - +
+
+ {displaySidebarTrigger ? ( + + ) : null} + + + + + + + {description} + +
{title} diff --git a/packages/ui/src/makerkit/version-updater.tsx b/packages/ui/src/makerkit/version-updater.tsx index 92b9877f3..28bdbb822 100644 --- a/packages/ui/src/makerkit/version-updater.tsx +++ b/packages/ui/src/makerkit/version-updater.tsx @@ -50,9 +50,11 @@ export function VersionUpdater(props: { intervalTimeInSecond?: number }) { - + - + + + diff --git a/packages/ui/src/shadcn/sidebar.tsx b/packages/ui/src/shadcn/sidebar.tsx index 29c45e23d..9ea913cb9 100644 --- a/packages/ui/src/shadcn/sidebar.tsx +++ b/packages/ui/src/shadcn/sidebar.tsx @@ -36,9 +36,9 @@ const SIDEBAR_COOKIE_NAME = 'sidebar:state'; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; const SIDEBAR_WIDTH = '16rem'; const SIDEBAR_WIDTH_MOBILE = '18rem'; -const SIDEBAR_WIDTH_ICON = '3rem'; +const SIDEBAR_WIDTH_ICON = '4rem'; const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; -const SIDEBAR_MINIMIZED_WIDTH = '4rem'; +const SIDEBAR_MINIMIZED_WIDTH = SIDEBAR_WIDTH_ICON; type SidebarContext = { state: 'expanded' | 'collapsed'; @@ -46,12 +46,8 @@ type SidebarContext = { setOpen: (open: boolean) => void; openMobile: boolean; setOpenMobile: (open: boolean) => void; - setMinimized: (minimized: boolean) => void; isMobile: boolean; toggleSidebar: () => void; - minimized: boolean; - startMinimized: boolean; - expandOnHover: boolean; }; export const SidebarContext = React.createContext(null); @@ -71,13 +67,10 @@ const SidebarProvider: React.FC< defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; - minimized?: boolean; - expandOnHover?: boolean; } > = ({ ref, defaultOpen = true, - minimized: isMinimized = false, open: openProp, onOpenChange: setOpenProp, className, @@ -87,11 +80,7 @@ const SidebarProvider: React.FC< }) => { const isMobile = useIsMobile(); const [openMobile, setOpenMobile] = React.useState(false); - const [minimized, setMinimized] = React.useState(isMinimized); - - const expandOnHover = - props.expandOnHover ?? - process.env.NEXT_PUBLIC_EXPAND_SIDEBAR_ON_HOVER === 'true'; + const collapsibleStyle = process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE; // This is the internal state of the sidebar. // We use openProp and setOpenProp for control from outside the component. @@ -136,7 +125,6 @@ const SidebarProvider: React.FC< // We add a state so that we can do data-state="expanded" or "collapsed". // This makes it easier to style the sidebar with Tailwind classes. const state = open ? 'expanded' : 'collapsed'; - const startMinimized = isMinimized; const contextValue = React.useMemo( () => ({ @@ -144,43 +132,33 @@ const SidebarProvider: React.FC< open, setOpen, isMobile, - minimized, - setMinimized, - expandOnHover, openMobile, setOpenMobile, toggleSidebar, - startMinimized, }), - [ - state, - open, - setOpen, - isMobile, - openMobile, - setOpenMobile, - toggleSidebar, - expandOnHover, - minimized, - setMinimized, - startMinimized, - ], + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], ); + const sidebarWidth = !open + ? collapsibleStyle === 'icon' + ? SIDEBAR_WIDTH_ICON + : collapsibleStyle === 'offcanvas' + ? 0 + : SIDEBAR_MINIMIZED_WIDTH + : SIDEBAR_WIDTH; + return (
{ - const { - isMobile, - state, - openMobile, - setOpenMobile, - minimized, - setMinimized, - expandOnHover, - startMinimized, - } = useSidebar(); - useSidebar(); - - const isExpandedRef = React.useRef(false); - - const onMouseEnter = - startMinimized && expandOnHover - ? () => { - setMinimized(false); - isExpandedRef.current = true; - } - : undefined; - - const onMouseLeave = - startMinimized && expandOnHover - ? () => { - if (!isRadixPopupOpen()) { - setMinimized(true); - isExpandedRef.current = false; - } else { - onRadixPopupClose(() => { - setMinimized(true); - isExpandedRef.current = false; - }); - } - } - : undefined; + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); if (collapsible === 'none') { return ( @@ -256,7 +199,7 @@ const Sidebar: React.FC< 'bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col', className, { - [SIDEBAR_MINIMIZED_WIDTH]: minimized, + [SIDEBAR_MINIMIZED_WIDTH]: !open, }, )} ref={ref} @@ -301,8 +244,6 @@ const Sidebar: React.FC< data-collapsible={state === 'collapsed' ? collapsible : ''} data-variant={variant} data-side={side} - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} > {/* This is what handles the sidebar gap on desktop */}
> = ({ }) => (
    ); @@ -581,7 +525,10 @@ const SidebarMenuItem: React.FC> = ({ }) => (
  • ); @@ -625,7 +572,7 @@ const SidebarMenuButton: React.FC< ...props }) => { const Comp = asChild ? Slot : 'button'; - const { isMobile, minimized } = useSidebar(); + const { isMobile, open } = useSidebar(); const { t } = useTranslation(); const button = ( @@ -656,7 +603,7 @@ const SidebarMenuButton: React.FC<