Update Shadcn Sidebar (#73)

Migrated Sidebar to use Shadcn UI's
This commit is contained in:
Giancarlo Buomprisco
2024-10-25 09:43:34 +02:00
committed by GitHub
parent df944bb1e5
commit 14c2220904
48 changed files with 1863 additions and 543 deletions

View File

@@ -17,7 +17,7 @@
"@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/node": "^22.7.8"
"@types/node": "^22.7.9"
},
"eslintConfig": {
"root": true,

View File

@@ -31,7 +31,7 @@
"@types/react": "^18.3.11",
"date-fns": "^4.1.0",
"lucide-react": "^0.453.0",
"next": "15.0.0",
"next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021",
"react-hook-form": "^7.53.1",
"react-i18next": "^15.1.0",

View File

@@ -26,7 +26,7 @@
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@types/react": "^18.3.11",
"next": "15.0.0",
"next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021",
"zod": "^3.23.8"
},

View File

@@ -30,7 +30,7 @@
"@kit/ui": "workspace:^",
"@types/react": "^18.3.11",
"date-fns": "^4.1.0",
"next": "15.0.0",
"next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021",
"zod": "^3.23.8"
},

View File

@@ -19,7 +19,7 @@
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/wordpress": "workspace:^",
"@types/node": "^22.7.8"
"@types/node": "^22.7.9"
},
"eslintConfig": {
"root": true,

View File

@@ -16,7 +16,7 @@
"./route-handler": "./src/keystatic-route-handler.ts"
},
"dependencies": {
"@keystatic/core": "0.5.38",
"@keystatic/core": "0.5.39",
"@keystatic/next": "^5.0.1",
"@markdoc/markdoc": "^0.4.0"
},
@@ -26,7 +26,7 @@
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@types/node": "^22.7.8",
"@types/node": "^22.7.9",
"@types/react": "^18.3.11",
"react": "19.0.0-rc-69d4b800-20241021",
"zod": "^3.23.8"

View File

@@ -20,7 +20,7 @@
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@types/node": "^22.7.8",
"@types/node": "^22.7.9",
"@types/react": "^18.3.11",
"wp-types": "^4.66.1"
},

View File

@@ -35,11 +35,11 @@
"@kit/ui": "workspace:^",
"@radix-ui/react-icons": "^1.3.0",
"@supabase/supabase-js": "^2.45.6",
"@tanstack/react-query": "5.59.15",
"@tanstack/react-query": "5.59.16",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"lucide-react": "^0.453.0",
"next": "15.0.0",
"next": "15.0.1",
"next-themes": "0.3.0",
"react": "19.0.0-rc-69d4b800-20241021",
"react-dom": "19.0.0-rc-69d4b800-20241021",

View File

@@ -86,7 +86,7 @@ export function AccountSelector({
pictureUrl ? (
<UserAvatar pictureUrl={pictureUrl} />
) : (
<PersonIcon className="h-4 min-h-4 w-4 min-w-4" />
<PersonIcon className="h-5 min-h-5 w-5 min-w-5" />
);
return (
@@ -103,7 +103,7 @@ export function AccountSelector({
'dark:shadow-primary/10 group w-full min-w-0 px-2 lg:w-auto lg:max-w-fit',
{
'justify-start': !collapsed,
'm-auto justify-center px-4 lg:w-full': collapsed,
'm-auto justify-center px-2 lg:w-full': collapsed,
},
className,
)}
@@ -111,7 +111,7 @@ export function AccountSelector({
<If
condition={selected}
fallback={
<span className={'flex max-w-full items-center space-x-2'}>
<span className={'flex max-w-full items-center space-x-4'}>
<PersonalAccountAvatar />
<span
@@ -125,11 +125,11 @@ export function AccountSelector({
}
>
{(account) => (
<span className={'flex max-w-full items-center space-x-2'}>
<Avatar className={'h-5 w-5'}>
<span className={'flex max-w-full items-center space-x-4'}>
<Avatar className={'h-6 w-6 rounded-sm'}>
<AvatarImage src={account.image ?? undefined} />
<AvatarFallback className={'group-hover:bg-background'}>
<AvatarFallback className={'group-hover:bg-background rounded-sm'}>
{account.label ? account.label[0] : ''}
</AvatarFallback>
</Avatar>
@@ -210,11 +210,11 @@ export function AccountSelector({
}}
>
<div className={'flex items-center'}>
<Avatar className={'mr-2 h-5 w-5'}>
<Avatar className={'mr-2 h-6 w-6 rounded-sm'}>
<AvatarImage src={account.image ?? undefined} />
<AvatarFallback
className={cn({
className={cn('rounded-sm', {
['bg-background']: value === account.value,
['group-hover:bg-background']:
value !== account.value,
@@ -274,7 +274,7 @@ export function AccountSelector({
function UserAvatar(props: { pictureUrl?: string }) {
return (
<Avatar className={'h-6 w-6'}>
<Avatar className={'h-6 w-6 rounded-sm'}>
<AvatarImage src={props.pictureUrl} />
</Avatar>
);

View File

@@ -7,7 +7,7 @@ import Link from 'next/link';
import type { User } from '@supabase/supabase-js';
import {
EllipsisVertical,
ChevronsUpDown,
Home,
LogOut,
MessageCircleQuestion,
@@ -85,16 +85,17 @@ export function PersonalAccountDropdown({
aria-label="Open your profile menu"
data-test={'account-dropdown-trigger'}
className={cn(
'animate-in fade-in group flex cursor-pointer items-center focus:outline-none',
'animate-in fade-in focus:outline-primary flex cursor-pointer items-center duration-500 group-data-[minimized=true]:px-0',
className ?? '',
{
['active:bg-secondary/50 items-center space-x-2.5 rounded-md' +
['active:bg-secondary/50 items-center space-x-4 rounded-md' +
' hover:bg-secondary p-2 transition-colors']: showProfileName,
},
)}
>
<ProfileAvatar
className={'group-hover:border-primary/10 border border-transparent'}
className={'rounded-md'}
fallbackClassName={'rounded-md border'}
displayName={displayName ?? user?.email ?? ''}
pictureUrl={personalAccountData?.picture_url}
/>
@@ -102,7 +103,7 @@ export function PersonalAccountDropdown({
<If condition={showProfileName}>
<div
className={
'fade-in animate-in flex w-full flex-col truncate text-left'
'fade-in animate-in flex w-full flex-col truncate text-left group-data-[minimized=true]:hidden'
}
>
<span
@@ -120,17 +121,15 @@ export function PersonalAccountDropdown({
</span>
</div>
<EllipsisVertical
className={'text-muted-foreground mr-1 hidden h-8 group-hover:flex'}
<ChevronsUpDown
className={
'text-muted-foreground mr-1 h-8 group-data-[minimized=true]:hidden'
}
/>
</If>
</DropdownMenuTrigger>
<DropdownMenuContent
className={'xl:!min-w-[15rem]'}
collisionPadding={{ right: 20, left: 20 }}
sideOffset={10}
>
<DropdownMenuContent className={'xl:!min-w-[15rem]'}>
<DropdownMenuItem className={'!h-10 rounded-none'}>
<div
className={'flex flex-col justify-start truncate text-left text-xs'}

View File

@@ -22,11 +22,11 @@
"@makerkit/data-loader-supabase-core": "^0.0.8",
"@makerkit/data-loader-supabase-nextjs": "^1.2.3",
"@supabase/supabase-js": "^2.45.6",
"@tanstack/react-query": "5.59.15",
"@tanstack/react-query": "5.59.16",
"@tanstack/react-table": "^8.20.5",
"@types/react": "^18.3.11",
"lucide-react": "^0.453.0",
"next": "15.0.0",
"next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021",
"react-dom": "19.0.0-rc-69d4b800-20241021",
"react-hook-form": "^7.53.1",

View File

@@ -29,10 +29,10 @@
"@marsidev/react-turnstile": "^1.0.2",
"@radix-ui/react-icons": "^1.3.0",
"@supabase/supabase-js": "^2.45.6",
"@tanstack/react-query": "5.59.15",
"@tanstack/react-query": "5.59.16",
"@types/react": "^18.3.11",
"lucide-react": "^0.453.0",
"next": "15.0.0",
"next": "15.0.1",
"react-hook-form": "^7.53.1",
"react-i18next": "^15.1.0",
"sonner": "^1.5.0",

View File

@@ -21,7 +21,7 @@
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "^2.45.6",
"@tanstack/react-query": "5.59.15",
"@tanstack/react-query": "5.59.16",
"@types/react": "^18.3.11",
"lucide-react": "^0.453.0",
"react": "19.0.0-rc-69d4b800-20241021",

View File

@@ -33,14 +33,14 @@
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@supabase/supabase-js": "^2.45.6",
"@tanstack/react-query": "5.59.15",
"@tanstack/react-query": "5.59.16",
"@tanstack/react-table": "^8.20.5",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"class-variance-authority": "^0.7.0",
"date-fns": "^4.1.0",
"lucide-react": "^0.453.0",
"next": "15.0.0",
"next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021",
"react-dom": "19.0.0-rc-69d4b800-20241021",
"react-hook-form": "^7.53.1",

View File

@@ -21,14 +21,14 @@
"@kit/shared": "workspace:^",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@tanstack/react-query": "5.59.15",
"@tanstack/react-query": "5.59.16",
"react-i18next": "^15.1.0"
},
"dependencies": {
"i18next": "^23.16.2",
"i18next-browser-languagedetector": "8.0.0",
"i18next-resources-to-backend": "^1.2.1",
"next": "15.0.0",
"next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021",
"react-dom": "19.0.0-rc-69d4b800-20241021"
},

View File

@@ -19,7 +19,7 @@
"@kit/resend": "workspace:^",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/node": "^22.7.8",
"@types/node": "^22.7.9",
"zod": "^3.23.8"
},
"eslintConfig": {

View File

@@ -18,7 +18,7 @@
"@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/node": "^22.7.8",
"@types/node": "^22.7.9",
"zod": "^3.23.8"
},
"eslintConfig": {

View File

@@ -22,7 +22,7 @@
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@supabase/supabase-js": "^2.45.6",
"next": "15.0.0",
"next": "15.0.1",
"zod": "^3.23.8"
},
"eslintConfig": {

View File

@@ -30,9 +30,9 @@
"@kit/tsconfig": "workspace:*",
"@supabase/ssr": "^0.5.1",
"@supabase/supabase-js": "^2.45.6",
"@tanstack/react-query": "5.59.15",
"@tanstack/react-query": "5.59.16",
"@types/react": "^18.3.11",
"next": "15.0.0",
"next": "15.0.1",
"react": "19.0.0-rc-69d4b800-20241021",
"server-only": "^0.0.1",
"zod": "^3.23.8"

View File

@@ -14,6 +14,7 @@
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
@@ -42,14 +43,14 @@
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@radix-ui/react-icons": "^1.3.0",
"@tanstack/react-query": "5.59.15",
"@tanstack/react-query": "5.59.16",
"@tanstack/react-table": "^8.20.5",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"class-variance-authority": "^0.7.0",
"date-fns": "^4.1.0",
"eslint": "^8.57.0",
"next": "15.0.0",
"next": "15.0.1",
"next-themes": "0.3.0",
"prettier": "^3.3.3",
"react-day-picker": "^8.10.1",
@@ -103,6 +104,8 @@
"./switch": "./src/shadcn/switch.tsx",
"./breadcrumb": "./src/shadcn/breadcrumb.tsx",
"./chart": "./src/shadcn/chart.tsx",
"./skeleton": "./src/shadcn/skeleton.tsx",
"./shadcn-sidebar": "./src/shadcn/sidebar.tsx",
"./utils": "./src/lib/utils/index.ts",
"./if": "./src/makerkit/if.tsx",
"./trans": "./src/makerkit/trans.tsx",

View File

@@ -0,0 +1,21 @@
import * as React from 'react';
const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener('change', onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener('change', onChange);
}, []);
return !!isMobile;
}

View File

@@ -5,6 +5,30 @@ const RouteMatchingEnd = z
.default(false)
.optional();
const Divider = z.object({
divider: z.literal(true),
});
const RouteChildren = z.array(
z.object({
label: z.string(),
path: z.string(),
Icon: z.custom<React.ReactNode>(),
end: RouteMatchingEnd,
children: z
.array(
z.object({
label: z.string(),
path: z.string(),
Icon: z.custom<React.ReactNode>(),
end: RouteMatchingEnd,
}),
)
.default([])
.optional(),
}),
);
export const NavigationConfigSchema = z.object({
style: z.enum(['custom', 'sidebar', 'header']).default('sidebar'),
sidebarCollapsed: z
@@ -14,28 +38,14 @@ export const NavigationConfigSchema = z.object({
.transform((value) => value === `true`),
routes: z.array(
z.union([
z.object({
label: z.string(),
path: z.string(),
Icon: z.custom<React.ReactNode>(),
end: RouteMatchingEnd,
}),
z.object({
label: z.string(),
collapsible: z.boolean().optional(),
collapsed: z.boolean().optional(),
children: z.array(
z.object({
label: z.string(),
path: z.string(),
Icon: z.custom<React.ReactNode>(),
end: RouteMatchingEnd,
}),
),
}),
z.object({
divider: z.literal(true),
children: RouteChildren,
renderAction: z.custom<React.ReactNode>().optional(),
}),
Divider,
]),
),
});

View File

@@ -29,12 +29,7 @@ function PageWithSidebar(props: PageProps) {
const { Navigation, Children, MobileNavigation } = getSlotsFromPage(props);
return (
<div
className={cn(
'flex bg-gray-50/95 dark:bg-background/85',
props.className,
)}
>
<div className={cn('flex', props.className)}>
{Navigation}
<div
@@ -47,7 +42,7 @@ function PageWithSidebar(props: PageProps) {
<div
className={
'flex flex-1 flex-col overflow-y-auto bg-background px-4 lg:m-1.5 lg:ml-0 lg:rounded-lg lg:border lg:px-0'
'flex flex-1 flex-col overflow-y-auto bg-background px-4 lg:px-0'
}
>
{Children}

View File

@@ -12,6 +12,7 @@ type TextProps = {
type ProfileAvatarProps = (SessionProps | TextProps) & {
className?: string;
fallbackClassName?: string;
};
export function ProfileAvatar(props: ProfileAvatarProps) {
@@ -23,8 +24,13 @@ export function ProfileAvatar(props: ProfileAvatarProps) {
if ('text' in props) {
return (
<Avatar className={avatarClassName}>
<AvatarFallback>
<span className={'uppercase'}>{props.text.slice(0, 1)}</span>
<AvatarFallback
className={cn(
props.fallbackClassName,
'uppercase animate-in fade-in',
)}
>
{props.text.slice(0, 1)}
</AvatarFallback>
</Avatar>
);
@@ -36,7 +42,9 @@ export function ProfileAvatar(props: ProfileAvatarProps) {
<Avatar className={avatarClassName}>
<AvatarImage src={props.pictureUrl ?? undefined} />
<AvatarFallback className={'animate-in fade-in'}>
<AvatarFallback
className={cn(props.fallbackClassName, 'animate-in fade-in')}
>
<span suppressHydrationWarning className={'uppercase'}>
{initials}
</span>

View File

@@ -26,6 +26,11 @@ export type SidebarConfig = z.infer<typeof NavigationConfigSchema>;
export { SidebarContext };
/**
* @deprecated
* This component is deprecated and will be removed in a future version.
* Please use the Shadcn Sidebar component instead.
*/
export function Sidebar(props: {
collapsed?: boolean;
expandOnHover?: boolean;
@@ -338,17 +343,6 @@ export function SidebarNavigation({
</SidebarGroup>
);
}
return (
<SidebarItem
key={item.path}
end={item.end}
path={item.path}
Icon={item.Icon}
>
<Trans i18nKey={item.label} defaults={item.label} />
</SidebarItem>
);
})}
</>
);

View File

@@ -0,0 +1,11 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

@@ -3,9 +3,8 @@
import * as React from 'react';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';
import { Cross2Icon } from '@radix-ui/react-icons';
import { type VariantProps, cva } from 'class-variance-authority';
import { cn } from '../lib/utils';
@@ -33,7 +32,7 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
{
variants: {
side: {
@@ -42,7 +41,7 @@ const sheetVariants = cva(
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right:
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
@@ -66,11 +65,11 @@ const SheetContent = React.forwardRef<
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
));

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
import { cn } from '../lib/utils';
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn('animate-pulse rounded-md bg-primary/10', className)}
{...props}
/>
);
}
export { Skeleton };