brunner56's picture
implement app
0bfe2e3
'use client';
import { cva, VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { AppLayoutAnatomy } from '.';
import { cn, ComponentAnatomy, defineStyleAnatomy } from '../core/styling';
import { Drawer, DrawerProps } from '../drawer';
/* -------------------------------------------------------------------------------------------------
* Context
* -----------------------------------------------------------------------------------------------*/
export const __AppSidebarContext = React.createContext<{
open: boolean;
setOpen: (open: boolean) => void;
size: VariantProps<typeof AppLayoutAnatomy.root>['sidebarSize'];
setSize: (
size: VariantProps<typeof AppLayoutAnatomy.root>['sidebarSize']
) => void;
isBelowBreakpoint: boolean;
}>({
open: false,
setOpen: () => {},
setSize: () => {},
size: 'md',
isBelowBreakpoint: false,
});
export function useAppSidebarContext() {
const ctx = React.useContext(__AppSidebarContext);
if (!ctx)
throw new Error(
'useAppSidebarContext must be used within a AppSidebarProvider'
);
return ctx;
}
/* -------------------------------------------------------------------------------------------------
* Anatomy
* -----------------------------------------------------------------------------------------------*/
export const AppSidebarAnatomy = defineStyleAnatomy({
sidebar: cva([
'UI-AppSidebar__sidebar',
'flex flex-grow flex-col overflow-y-auto border-r border-transparent bg-[--background]',
]),
});
export const AppSidebarTriggerAnatomy = defineStyleAnatomy({
trigger: cva([
'UI-AppSidebarTrigger__trigger',
'block lg:hidden',
'items-center justify-center rounded-[--radius] p-2 text-[--muted] hover:bg-[--subtle] hover:text-[--foreground] transition-colors',
'focus:outline-none focus:ring-2 focus:ring-inset focus:ring-[--ring]',
]),
});
/* -------------------------------------------------------------------------------------------------
* AppSidebar
* -----------------------------------------------------------------------------------------------*/
export type AppSidebarProps = React.ComponentPropsWithoutRef<'div'> &
ComponentAnatomy<typeof AppSidebarAnatomy> & {
mobileDrawerProps?: Partial<DrawerProps>;
};
export const AppSidebar = React.forwardRef<HTMLDivElement, AppSidebarProps>(
(props, ref) => {
const { children, className, ...rest } = props;
const ctx = React.useContext(__AppSidebarContext);
return (
<>
<div
ref={ref}
className={cn(
AppSidebarAnatomy.sidebar(),
// process.env.NEXT_PUBLIC_PLATFORM === "desktop" && "pt-4",
className
)}
{...rest}
>
{children}
</div>
<Drawer
open={ctx.open}
onOpenChange={(v) => ctx.setOpen(v)}
side="left"
>
{children}
</Drawer>
</>
);
}
);
AppSidebar.displayName = 'AppSidebar';
/* -------------------------------------------------------------------------------------------------
* AppSidebarTrigger
* -----------------------------------------------------------------------------------------------*/
export type AppSidebarTriggerProps = React.ComponentPropsWithoutRef<'button'> &
ComponentAnatomy<typeof AppSidebarTriggerAnatomy>;
export const AppSidebarTrigger = React.forwardRef<
HTMLButtonElement,
AppSidebarTriggerProps
>((props, ref) => {
const { children, className, ...rest } = props;
const ctx = React.useContext(__AppSidebarContext);
return (
<button
ref={ref}
className={cn(AppSidebarTriggerAnatomy.trigger(), className)}
onClick={() => ctx.setOpen(!ctx.open)}
{...rest}
>
<span className="sr-only">Open main menu</span>
{ctx.open ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="block h-6 w-6"
>
<line x1="18" x2="6" y1="6" y2="18"></line>
<line x1="6" x2="18" y1="6" y2="18"></line>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="block h-6 w-6"
>
<line x1="4" x2="20" y1="12" y2="12"></line>
<line x1="4" x2="20" y1="6" y2="6"></line>
<line x1="4" x2="20" y1="18" y2="18"></line>
</svg>
)}
</button>
);
});
AppSidebarTrigger.displayName = 'AppSidebarTrigger';
/* -------------------------------------------------------------------------------------------------
* AppSidebarProvider
* -----------------------------------------------------------------------------------------------*/
export type AppSidebarProviderProps = {
children?: React.ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
onSizeChange?: (
size: VariantProps<typeof AppLayoutAnatomy.root>['sidebarSize']
) => void;
};
export const AppSidebarProvider: React.FC<AppSidebarProviderProps> = ({
children,
onOpenChange,
onSizeChange,
}) => {
const [open, setOpen] = React.useState(false);
const [size, setSize] =
React.useState<VariantProps<typeof AppLayoutAnatomy.root>['sidebarSize']>(
undefined
);
const [isBelowBreakpoint, setIsBelowBreakpoint] =
React.useState<boolean>(false);
React.useEffect(() => {
const handleResize = () => setIsBelowBreakpoint(window.innerWidth < 1024); // lg breakpoint
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [isBelowBreakpoint]);
return (
<__AppSidebarContext.Provider
value={{
open,
setOpen: (open: boolean) => {
onOpenChange?.(open);
setOpen(open);
},
setSize: (
size: VariantProps<typeof AppLayoutAnatomy.root>['sidebarSize']
) => {
onSizeChange?.(size);
setSize(size);
},
size: size,
isBelowBreakpoint,
}}
>
{children}
</__AppSidebarContext.Provider>
);
};
AppSidebarProvider.displayName = 'AppSidebarProvider';