Added tabs variant

This commit is contained in:
Peter Zimon 2024-11-21 19:11:07 +01:00
parent 53a0ccfa1c
commit 85a4f72545
24 changed files with 7286 additions and 4494 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
import { N as t, j as o } from "./index-7a29597a.mjs";
import { N as t, j as o } from "./index-0846be11.mjs";
const a = t.create(() => /* @__PURE__ */ o.jsx(o.Fragment, {})), s = { DemoModal: a };
export {
s as default
};
//# sourceMappingURL=modals-cffa8516.mjs.map
//# sourceMappingURL=modals-eeac580f.mjs.map

View File

@ -1 +1 @@
{"version":3,"file":"modals-cffa8516.mjs","sources":["../src/components/DemoModal.tsx","../src/components/modals.tsx"],"sourcesContent":["import NiceModal from '@ebay/nice-modal-react';\n// import {Heading, Modal} from '@tryghost/shade';\n// import {useRouting} from '@tryghost/admin-x-framework/routing';\n\nconst DemoModal = NiceModal.create(() => {\n // const {updateRoute} = useRouting();\n // const modal = NiceModal.useModal();\n\n return ( <></>\n // <Modal\n // afterClose={() => {\n // updateRoute('');\n // }}\n // cancelLabel=''\n // okLabel='Close'\n // size='sm'\n // title='About'\n // onOk={() => {\n // updateRoute('');\n // modal.remove();\n // }}\n // >\n // <div className='mt-3 flex flex-col gap-4'>\n // <p>{`You're looking at a React app inside Ghost Admin. It uses common AdminX framework and Design System packages, and works seamlessly with the current Admin's routing.`}</p>\n // <p>{`At the moment the look and feel follows the current Admin's style to blend in with existing pages. However the system is built in a very flexible way to allow easy updates in the future.`}</p>\n // <Heading className='-mb-2 mt-4' level={5}>Contents</Heading>\n // <p>{`The demo uses a mocked list of members — it's `}<strong>not</strong> {`the actual or future design of members in Ghost Admin. Instead, the pages showcase common design patterns like a list and detail, navigation, modals and toasts.`}</p>\n // </div>\n // </Modal>\n );\n});\n\nexport default DemoModal;\n","import DemoModal from './DemoModal';\nimport {ModalComponent} from '@tryghost/admin-x-framework/routing';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst modals = {DemoModal} satisfies {[key: string]: ModalComponent<any>};\n\nexport default modals;\n\nexport type ModalName = keyof typeof modals;\n"],"names":["DemoModal","NiceModal","jsx","Fragment","modals"],"mappings":";AAIA,MAAMA,IAAYC,EAAU,OAAO,MAIpBC,gBAAAA,EAAA,IAAAC,YAAA,CAAA,CAAA,CAsBd,GC1BKC,IAAS,EAAC,WAAAJ,EAAS;"}
{"version":3,"file":"modals-eeac580f.mjs","sources":["../src/components/DemoModal.tsx","../src/components/modals.tsx"],"sourcesContent":["import NiceModal from '@ebay/nice-modal-react';\n// import {Heading, Modal} from '@tryghost/shade';\n// import {useRouting} from '@tryghost/admin-x-framework/routing';\n\nconst DemoModal = NiceModal.create(() => {\n // const {updateRoute} = useRouting();\n // const modal = NiceModal.useModal();\n\n return ( <></>\n // <Modal\n // afterClose={() => {\n // updateRoute('');\n // }}\n // cancelLabel=''\n // okLabel='Close'\n // size='sm'\n // title='About'\n // onOk={() => {\n // updateRoute('');\n // modal.remove();\n // }}\n // >\n // <div className='mt-3 flex flex-col gap-4'>\n // <p>{`You're looking at a React app inside Ghost Admin. It uses common AdminX framework and Design System packages, and works seamlessly with the current Admin's routing.`}</p>\n // <p>{`At the moment the look and feel follows the current Admin's style to blend in with existing pages. However the system is built in a very flexible way to allow easy updates in the future.`}</p>\n // <Heading className='-mb-2 mt-4' level={5}>Contents</Heading>\n // <p>{`The demo uses a mocked list of members — it's `}<strong>not</strong> {`the actual or future design of members in Ghost Admin. Instead, the pages showcase common design patterns like a list and detail, navigation, modals and toasts.`}</p>\n // </div>\n // </Modal>\n );\n});\n\nexport default DemoModal;\n","import DemoModal from './DemoModal';\nimport {ModalComponent} from '@tryghost/admin-x-framework/routing';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst modals = {DemoModal} satisfies {[key: string]: ModalComponent<any>};\n\nexport default modals;\n\nexport type ModalName = keyof typeof modals;\n"],"names":["DemoModal","NiceModal","jsx","Fragment","modals"],"mappings":";AAIA,MAAMA,IAAYC,EAAU,OAAO,MAIpBC,gBAAAA,EAAA,IAAAC,YAAA,CAAA,CAAA,CAsBd,GC1BKC,IAAS,EAAC,WAAAJ,EAAS;"}

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import {ShadeApp, ShadeAppProps} from '@tryghost/shade';
import {ShadeApp, ShadeAppProps, SidebarProvider} from '@tryghost/shade';
import {FrameworkProvider, TopLevelFrameworkProps} from '@tryghost/admin-x-framework';
import {RoutingProvider} from '@tryghost/admin-x-framework/routing';
import PostAnalytics from './PostAnalytics';
@ -20,11 +20,12 @@ const App: React.FC<AppProps> = ({framework, designSystem}) => {
<FrameworkProvider {...framework}>
<RoutingProvider basePath='post-analytics-spike' modals={modals}>
<ShadeApp className='post-analytics-spike' {...designSystem}>
<SidebarProvider>
{/* @TODO: should be a component */}
<div className='max-w-[1280px] w-full mx-auto'>
<PostAnalytics />
</div>
</SidebarProvider>
</ShadeApp>
</RoutingProvider>
</FrameworkProvider>

View File

@ -1,4 +1,102 @@
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, Heading, Icon, Tabs, TabsContent, TabsList, TabsTrigger } from "@tryghost/shade";
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, Heading, Icon, Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem, Tabs, TabsContent, TabsList, TabsTrigger } from "@tryghost/shade";
const EmailTab = () => {
return (
<div className="flex">
<Tabs orientation="vertical" defaultValue="sent" className="w-full flex">
<div className="flex-1">
<TabsContent value="sent">
This uses a tab on the left which is semantically correct. We need to implement variants for top and right tabs.
</TabsContent>
<TabsContent value="opened">
Opened
</TabsContent>
<TabsContent value="clicked">
Clicked
</TabsContent>
<TabsContent value="unsubscribed">
Unsubscribed
</TabsContent>
<TabsContent value="feedback">
Feedback
</TabsContent>
<TabsContent value="spam">
Marked as spam
</TabsContent>
<TabsContent value="bounced">
Bounced
</TabsContent>
</div>
<TabsList className="w-48 flex flex-col justify-start h-auto">
<TabsTrigger value="sent">
Sent
</TabsTrigger>
<TabsTrigger value="opened">
Opened
</TabsTrigger>
<TabsTrigger value="clicked">
Clicked
</TabsTrigger>
<TabsTrigger value="unsubscribed">
Unsubscribed
</TabsTrigger>
<TabsTrigger value="feedback">
Feedback
</TabsTrigger>
<TabsTrigger value="spam">
Marked as spam
</TabsTrigger>
<TabsTrigger value="bounced">
Bounced
</TabsTrigger>
</TabsList>
</Tabs>
</div>
);
}
const EmailSidebar = () => {
return (
<div className="w-full grid grid-cols-5">
<div className="col-span-4">
This uses a `sidebar` component which is nicer but not as straightforward to use as tabs
</div>
<Sidebar collapsible="none" className="bg-transparent w-full">
<SidebarContent>
<SidebarGroup className="border-b last:border-none">
<SidebarGroupContent className="gap-0">
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton isActive>
<span>Sent</span>
</SidebarMenuButton>
<SidebarMenuButton>
<span>Opened</span>
</SidebarMenuButton>
<SidebarMenuButton>
<span>Clicked</span>
</SidebarMenuButton>
<SidebarMenuButton>
<span>Unsubscribed</span>
</SidebarMenuButton>
<SidebarMenuButton>
<span>Feedback</span>
</SidebarMenuButton>
<SidebarMenuButton>
<span>Marked as spam</span>
</SidebarMenuButton>
<SidebarMenuButton>
<span>Bounced</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</div>
);
}
const PostAnalytics = () => {
return (
@ -42,21 +140,21 @@ const PostAnalytics = () => {
</div>
</div>
</header>
<Tabs className="mt-8" defaultValue="overview">
<Tabs className="mt-8" defaultValue="overview" variant="outline">
<TabsList className="grid w-full grid-cols-5">
<TabsTrigger value="overview">
<TabsTrigger value="overview" className="justify-start">
Overview
</TabsTrigger>
<TabsTrigger value="email">
<TabsTrigger value="email" className="justify-start">
Email
</TabsTrigger>
<TabsTrigger value="web">
<TabsTrigger value="web" className="justify-start">
Web
</TabsTrigger>
<TabsTrigger value="comments">
<TabsTrigger value="comments" className="justify-start">
Comments
</TabsTrigger>
<TabsTrigger value="growth">
<TabsTrigger value="growth" className="justify-start">
Growth
</TabsTrigger>
</TabsList>
@ -64,10 +162,10 @@ const PostAnalytics = () => {
Overview
</TabsContent>
<TabsContent value="email">
Email
<EmailTab />
</TabsContent>
<TabsContent value="web">
Web
<EmailSidebar />
</TabsContent>
<TabsContent value="comments">
Comments

View File

@ -4,7 +4,7 @@
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"config": "tailwind.config.cjs",
"css": "styles.css",
"baseColor": "neutral",
"cssVariables": false,

View File

@ -67,15 +67,16 @@
"@ebay/nice-modal-react": "1.2.13",
"@radix-ui/react-avatar": "1.1.0",
"@radix-ui/react-checkbox": "1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-form": "0.0.3",
"@radix-ui/react-popover": "1.1.1",
"@radix-ui/react-radio-group": "1.2.0",
"@radix-ui/react-separator": "1.1.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "1.1.2",
"@radix-ui/react-tooltip": "^1.1.4",
"@sentry/react": "7.119.2",
"@tailwindcss/forms": "0.5.9",
"@tailwindcss/line-clamp": "0.4.4",

View File

@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border border-neutral-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 dark:border-neutral-800 dark:focus:ring-neutral-300",
{
variants: {
variant: {
default:
"border-transparent bg-neutral-900 text-neutral-50 hover:bg-neutral-900/80 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/80",
secondary:
"border-transparent bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
destructive:
"border-transparent bg-red-500 text-neutral-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/80",
outline: "text-neutral-950 dark:text-neutral-50",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@ -0,0 +1,22 @@
import * as React from 'react';
import {cn} from '@/lib/utils';
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
({className, type, ...props}, ref) => {
return (
<input
ref={ref}
className={cn(
'flex h-10 w-full rounded-md border border-neutral-200 bg-white px-3 py-2 text-base ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-neutral-950 placeholder:text-neutral-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:border-neutral-800 dark:bg-neutral-950 dark:ring-offset-neutral-950 dark:file:text-neutral-50 dark:placeholder:text-neutral-400 dark:focus-visible:ring-neutral-300',
className
)}
type={type}
{...props}
/>
);
}
);
Input.displayName = 'Input';
export {Input};

View File

@ -0,0 +1,31 @@
// 'use client';
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import {cn} from '@/lib/utils';
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{className, orientation = 'horizontal', decorative = true, ...props},
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
className={cn(
'shrink-0 bg-neutral-200 dark:bg-neutral-800',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className
)}
decorative={decorative}
orientation={orientation}
{...props}
/>
)
);
Separator.displayName = SeparatorPrimitive.Root.displayName;
export {Separator};

View File

@ -0,0 +1,138 @@
import * as React from 'react';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import {cva, type VariantProps} from 'class-variance-authority';
import {X} from 'lucide-react';
import {cn} from '@/lib/utils';
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({className, ...props}, ref) => (
<SheetPrimitive.Overlay
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
'fixed z-50 gap-4 bg-white 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 dark:bg-neutral-950',
{
variants: {
side: {
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom:
'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'
}
},
defaultVariants: {
side: 'right'
}
}
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({side = 'right', className, children, ...props}, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({side}), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-2 text-center sm:text-left',
className
)}
{...props}
/>
);
SheetHeader.displayName = 'SheetHeader';
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className
)}
{...props}
/>
);
SheetFooter.displayName = 'SheetFooter';
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({className, ...props}, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold text-neutral-950 dark:text-neutral-50', className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({className, ...props}, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-sm text-neutral-500 dark:text-neutral-400', className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription
};

View File

@ -0,0 +1,762 @@
import * as React from 'react';
import {Slot} from '@radix-ui/react-slot';
import {VariantProps, cva} from 'class-variance-authority';
import {PanelLeft} from 'lucide-react';
import {useIsMobile} from '@/hooks/use-mobile';
import {cn} from '@/lib/utils';
import {Button} from '@/components/ui/button';
import {Input} from '@/components/ui/input';
import {Separator} from '@/components/ui/separator';
import {Sheet, SheetContent} from '@/components/ui/sheet';
import {Skeleton} from '@/components/ui/skeleton';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from '@/components/ui/tooltip';
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_KEYBOARD_SHORTCUT = 'b';
type SidebarContext = {
state: 'expanded' | 'collapsed'
open: boolean
setOpen: (open: boolean) => void
openMobile: boolean
setOpenMobile: (open: boolean) => void
isMobile: boolean
toggleSidebar: () => void
}
const SidebarContext = React.createContext<SidebarContext | null>(null);
function useSidebar() {
const context = React.useContext(SidebarContext);
if (!context) {
throw new Error('useSidebar must be used within a SidebarProvider.');
}
return context;
}
const SidebarProvider = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'> & {
defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}
>(
(
{
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
},
ref
) => {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen);
const open = openProp ?? _open;
const setOpen = React.useCallback(
(newValue: boolean | ((value: boolean) => boolean)) => {
const openState = typeof newValue === 'function' ? newValue(open) : newValue;
if (setOpenProp) {
setOpenProp(openState);
} else {
_setOpen(openState);
}
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
[setOpenProp, open]
);
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile
? setOpenMobile(state => !state)
: setOpen(state => !state);
}, [isMobile, setOpen, setOpenMobile]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault();
toggleSidebar();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [toggleSidebar]);
// 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 contextValue = React.useMemo<SidebarContext>(
() => ({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
);
return (
<SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div
ref={ref}
className={cn(
'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar',
className
)}
style={
{
'--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
...style
} as React.CSSProperties
}
{...props}
>
{children}
</div>
</TooltipProvider>
</SidebarContext.Provider>
);
}
);
SidebarProvider.displayName = 'SidebarProvider';
const Sidebar = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'> & {
side?: 'left' | 'right'
variant?: 'sidebar' | 'floating' | 'inset'
collapsible?: 'offcanvas' | 'icon' | 'none'
}
>(
(
{
side = 'left',
variant = 'sidebar',
collapsible = 'offcanvas',
className,
children,
...props
},
ref
) => {
const {isMobile, state, openMobile, setOpenMobile} = useSidebar();
if (collapsible === 'none') {
return (
<div
ref={ref}
className={cn(
'flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground',
className
)}
{...props}
>
{children}
</div>
);
}
if (isMobile) {
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent
className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
data-mobile="true"
data-sidebar="sidebar"
side={side}
style={
{
'--sidebar-width': SIDEBAR_WIDTH_MOBILE
} as React.CSSProperties
}
>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
);
}
return (
<div
ref={ref}
className="group peer hidden text-sidebar-foreground md:block"
data-collapsible={state === 'collapsed' ? collapsible : ''}
data-side={side}
data-state={state}
data-variant={variant}
>
{/* This is what handles the sidebar gap on desktop */}
<div
className={cn(
'duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear',
'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]'
)}
/>
<div
className={cn(
'duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex',
side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
: 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
className
)}
{...props}
>
<div
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
data-sidebar="sidebar"
>
{children}
</div>
</div>
</div>
);
}
);
Sidebar.displayName = 'Sidebar';
const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button>
>(({className, onClick, ...props}, ref) => {
const {toggleSidebar} = useSidebar();
return (
<Button
ref={ref}
className={cn('h-7 w-7', className)}
data-sidebar="trigger"
size="icon"
variant="ghost"
onClick={(event) => {
onClick?.(event);
toggleSidebar();
}}
{...props}
>
<PanelLeft />
<span className="sr-only">Toggle Sidebar</span>
</Button>
);
});
SidebarTrigger.displayName = 'SidebarTrigger';
const SidebarRail = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<'button'>
>(({className, ...props}, ref) => {
const {toggleSidebar} = useSidebar();
return (
<button
ref={ref}
aria-label="Toggle Sidebar"
className={cn(
'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
className
)}
data-sidebar="rail"
tabIndex={-1}
title="Toggle Sidebar"
type="button"
onClick={toggleSidebar}
{...props}
/>
);
});
SidebarRail.displayName = 'SidebarRail';
const SidebarInset = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'main'>
>(({className, ...props}, ref) => {
return (
<main
ref={ref}
className={cn(
'relative flex min-h-svh flex-1 flex-col bg-white dark:bg-neutral-950',
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
className
)}
{...props}
/>
);
});
SidebarInset.displayName = 'SidebarInset';
const SidebarInput = React.forwardRef<
React.ElementRef<typeof Input>,
React.ComponentProps<typeof Input>
>(({className, ...props}, ref) => {
return (
<Input
ref={ref}
className={cn(
'h-8 w-full bg-white shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring dark:bg-neutral-950',
className
)}
data-sidebar="input"
{...props}
/>
);
});
SidebarInput.displayName = 'SidebarInput';
const SidebarHeader = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({className, ...props}, ref) => {
return (
<div
ref={ref}
className={cn('flex flex-col gap-2 p-2', className)}
data-sidebar="header"
{...props}
/>
);
});
SidebarHeader.displayName = 'SidebarHeader';
const SidebarFooter = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({className, ...props}, ref) => {
return (
<div
ref={ref}
className={cn('flex flex-col gap-2 p-2', className)}
data-sidebar="footer"
{...props}
/>
);
});
SidebarFooter.displayName = 'SidebarFooter';
const SidebarSeparator = React.forwardRef<
React.ElementRef<typeof Separator>,
React.ComponentProps<typeof Separator>
>(({className, ...props}, ref) => {
return (
<Separator
ref={ref}
className={cn('mx-2 w-auto bg-sidebar-border', className)}
data-sidebar="separator"
{...props}
/>
);
});
SidebarSeparator.displayName = 'SidebarSeparator';
const SidebarContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({className, ...props}, ref) => {
return (
<div
ref={ref}
className={cn(
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
className
)}
data-sidebar="content"
{...props}
/>
);
});
SidebarContent.displayName = 'SidebarContent';
const SidebarGroup = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({className, ...props}, ref) => {
return (
<div
ref={ref}
className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
data-sidebar="group"
{...props}
/>
);
});
SidebarGroup.displayName = 'SidebarGroup';
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'> & { asChild?: boolean }
>(({className, asChild = false, ...props}, ref) => {
const Comp = asChild ? Slot : 'div';
return (
<Comp
ref={ref}
className={cn(
'duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
className
)}
data-sidebar="group-label"
{...props}
/>
);
});
SidebarGroupLabel.displayName = 'SidebarGroupLabel';
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<'button'> & { asChild?: boolean }
>(({className, asChild = false, ...props}, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
ref={ref}
className={cn(
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden',
'group-data-[collapsible=icon]:hidden',
className
)}
data-sidebar="group-action"
{...props}
/>
);
});
SidebarGroupAction.displayName = 'SidebarGroupAction';
const SidebarGroupContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({className, ...props}, ref) => (
<div
ref={ref}
className={cn('w-full text-sm', className)}
data-sidebar="group-content"
{...props}
/>
));
SidebarGroupContent.displayName = 'SidebarGroupContent';
const SidebarMenu = React.forwardRef<
HTMLUListElement,
React.ComponentProps<'ul'>
>(({className, ...props}, ref) => (
<ul
ref={ref}
className={cn('flex w-full min-w-0 flex-col gap-1', className)}
data-sidebar="menu"
{...props}
/>
));
SidebarMenu.displayName = 'SidebarMenu';
const SidebarMenuItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<'li'>
>(({className, ...props}, ref) => (
<li
ref={ref}
className={cn('group/menu-item relative', className)}
data-sidebar="menu-item"
{...props}
/>
));
SidebarMenuItem.displayName = 'SidebarMenuItem';
const sidebarMenuButtonVariants = cva(
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
{
variants: {
variant: {
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
outline:
'bg-white shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))] dark:bg-neutral-950'
},
size: {
default: 'h-8 text-sm',
sm: 'h-7 text-xs',
lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
}
);
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<'button'> & {
asChild?: boolean
isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent>
} & VariantProps<typeof sidebarMenuButtonVariants>
>(
(
{
asChild = false,
isActive = false,
variant = 'default',
size = 'default',
tooltip,
className,
...props
},
ref
) => {
const Comp = asChild ? Slot : 'button';
const {isMobile, state} = useSidebar();
const button = (
<Comp
ref={ref}
className={cn(sidebarMenuButtonVariants({variant, size}), className)}
data-active={isActive}
data-sidebar="menu-button"
data-size={size}
{...props}
/>
);
if (!tooltip) {
return button;
}
if (typeof tooltip === 'string') {
tooltip = {
children: tooltip
};
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
align="center"
hidden={state !== 'collapsed' || isMobile}
side="right"
{...tooltip}
/>
</Tooltip>
);
}
);
SidebarMenuButton.displayName = 'SidebarMenuButton';
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<'button'> & {
asChild?: boolean
showOnHover?: boolean
}
>(({className, asChild = false, showOnHover = false, ...props}, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
ref={ref}
className={cn(
'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 after:md:hidden',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
showOnHover &&
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
className
)}
data-sidebar="menu-action"
{...props}
/>
);
});
SidebarMenuAction.displayName = 'SidebarMenuAction';
const SidebarMenuBadge = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({className, ...props}, ref) => (
<div
ref={ref}
className={cn(
'absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none',
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
className
)}
data-sidebar="menu-badge"
{...props}
/>
));
SidebarMenuBadge.displayName = 'SidebarMenuBadge';
const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'> & {
showIcon?: boolean
}
>(({className, showIcon = false, ...props}, ref) => {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`;
}, []);
return (
<div
ref={ref}
className={cn('rounded-md h-8 flex gap-2 px-2 items-center', className)}
data-sidebar="menu-skeleton"
{...props}
>
{showIcon && (
<Skeleton
className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton
className="h-4 max-w-[--skeleton-width] flex-1"
data-sidebar="menu-skeleton-text"
style={
{
'--skeleton-width': width
} as React.CSSProperties
}
/>
</div>
);
});
SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton';
const SidebarMenuSub = React.forwardRef<
HTMLUListElement,
React.ComponentProps<'ul'>
>(({className, ...props}, ref) => (
<ul
ref={ref}
className={cn(
'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
'group-data-[collapsible=icon]:hidden',
className
)}
data-sidebar="menu-sub"
{...props}
/>
));
SidebarMenuSub.displayName = 'SidebarMenuSub';
const SidebarMenuSubItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<'li'>
>(({...props}, ref) => <li ref={ref} {...props} />);
SidebarMenuSubItem.displayName = 'SidebarMenuSubItem';
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
React.ComponentProps<'a'> & {
asChild?: boolean
size?: 'sm' | 'md'
isActive?: boolean
}
>(({asChild = false, size = 'md', isActive, className, ...props}, ref) => {
const Comp = asChild ? Slot : 'a';
return (
<Comp
ref={ref}
className={cn(
'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs',
size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden',
className
)}
data-active={isActive}
data-sidebar="menu-sub-button"
data-size={size}
{...props}
/>
);
});
SidebarMenuSubButton.displayName = 'SidebarMenuSubButton';
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar
};

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-neutral-100 dark:bg-neutral-800', className)}
{...props}
/>
);
}
export {Skeleton};

View File

@ -1,28 +1,54 @@
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import {cn} from '@/lib/utils';
const Tabs = TabsPrimitive.Root;
// Add variant types
type TabsVariant = 'default' | 'outline';
const TabsVariantContext = React.createContext<TabsVariant>('default');
interface TabsProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> {
variant?: TabsVariant;
}
// const Tabs = TabsPrimitive.Root;
const Tabs = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Root>,
TabsProps
>(({variant = 'default', ...props}, ref) => (
<TabsVariantContext.Provider value={variant}>
<TabsPrimitive.Root ref={ref} {...props} />
</TabsVariantContext.Provider>
));
Tabs.displayName = TabsPrimitive.Root.displayName;
const tabsListVariants: Record<TabsVariant, string> = {
default: 'bg-neutral-100 dark:bg-neutral-800',
outline: 'border border-neutral-200 dark:border-neutral-800 bg-transparent'
};
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({className, ...props}, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 p-1 text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400',
className
)}
{...props}
/>
));
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({className, ...props}, ref) => {
const variant = React.useContext(TabsVariantContext);
return (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 p-1 text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400',
tabsListVariants[variant],
className
)}
{...props}
/>
);
});
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({className, ...props}, ref) => (
<TabsPrimitive.Trigger
ref={ref}
@ -36,8 +62,8 @@ const TabsTrigger = React.forwardRef<
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({className, ...props}, ref) => (
<TabsPrimitive.Content
ref={ref}

View File

@ -0,0 +1,30 @@
// 'use client';
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import {cn} from '@/lib/utils';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({className, sideOffset = 4, ...props}, ref) => (
<TooltipPrimitive.Content
ref={ref}
className={cn(
'z-50 overflow-hidden rounded-md border border-neutral-200 bg-white px-3 py-1.5 text-sm text-neutral-950 shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50',
className
)}
sideOffset={sideOffset}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export {Tooltip, TooltipTrigger, TooltipContent, TooltipProvider};

View File

@ -0,0 +1,19 @@
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

@ -6,6 +6,7 @@ export * from './components/ui/breadcrumb';
export * from './components/ui/button';
export * from './components/ui/dropdown-menu';
export * from './components/ui/tabs';
export * from './components/ui/sidebar';
export {default as useGlobalDirtyState} from './hooks/useGlobalDirtyState';
export {usePagination} from './hooks/usePagination';

View File

@ -72,6 +72,26 @@
@apply text-md leading-normal;
}
}
:root {
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
.shade {

View File

@ -6,343 +6,355 @@ module.exports = {
corePlugins: {
preflight: false // we're providing our own scoped CSS reset
},
darkMode: 'class',
darkMode: ['class', 'class'],
plugins: [require('tailwindcss-animate')],
theme: {
screens: {
sm: '480px',
md: '640px',
lg: '1024px',
xl: '1320px',
xxl: '1440px',
xxxl: '1600px',
tablet: '860px'
},
colors: {
transparent: 'transparent',
current: 'currentColor',
accent: 'var(--accent-color, #ff0095)',
white: '#FFF',
black: '#15171A',
grey: {
DEFAULT: '#ABB4BE',
50: '#FAFAFB',
75: '#F9FAFB',
100: '#F4F5F6',
150: '#F1F3F4',
200: '#EBEEF0',
250: '#E5E9ED',
300: '#DDE1E5',
400: '#CED4D9',
500: '#AEB7C1',
600: '#95A1AD',
700: '#7C8B9A',
800: '#626D79',
900: '#394047',
925: '#2E3338',
950: '#222427',
975: '#191B1E'
},
neutral: {
DEFAULT: '#ABB4BE',
50: '#FAFAFB',
75: '#F9FAFB',
100: '#F4F5F6',
150: '#F1F3F4',
200: '#EBEEF0',
250: '#E5E9ED',
300: '#DDE1E5',
400: '#CED4D9',
500: '#AEB7C1',
600: '#95A1AD',
700: '#7C8B9A',
800: '#626D79',
900: '#394047',
925: '#2E3338',
950: '#222427',
975: '#191B1E'
},
green: {
DEFAULT: '#30CF43',
100: '#E1F9E4',
400: '#58DA67',
500: '#30CF43',
600: '#2AB23A'
},
blue: {
DEFAULT: '#14B8FF',
100: '#DBF4FF',
400: '#42C6FF',
500: '#14B8FF',
600: '#00A4EB'
},
purple: {
DEFAULT: '#8E42FF',
100: '#EDE0FF',
400: '#A366FF',
500: '#8E42FF',
600: '7B1FFF'
},
pink: {
DEFAULT: '#FB2D8D',
100: '#FFDFEE',
400: '#FF5CA8',
500: '#FB2D8D',
600: '#F70878'
},
red: {
DEFAULT: '#F50B23',
100: '#FFE0E0',
400: '#F9394C',
500: '#F50B23',
600: '#DC091E'
},
yellow: {
DEFAULT: '#FFB41F',
100: '#FFF1D6',
400: '#FFC247',
500: '#FFB41F',
600: '#F0A000'
},
lime: {
DEFAULT: '#B5FF18'
}
},
fontFamily: {
cardo: 'Cardo',
manrope: 'Manrope',
merriweather: 'Merriweather',
nunito: 'Nunito',
'tenor-sans': 'Tenor Sans',
'old-standard-tt': 'Old Standard TT',
prata: 'Prata',
roboto: 'Roboto',
rufina: 'Rufina',
inter: 'Inter',
sans: 'Inter, -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif',
serif: 'Georgia, serif',
mono: 'Consolas, Liberation Mono, Menlo, Courier, monospace',
inherit: 'inherit',
'space-grotesk': 'Space Grotesk',
'chakra-petch': 'Chakra Petch',
'noto-sans': 'Noto Sans',
poppins: 'Poppins',
'fira-sans': 'Fira Sans',
'noto-serif': 'Noto Serif',
lora: 'Lora',
'ibm-plex-serif': 'IBM Plex Serif',
'space-mono': 'Space Mono',
'fira-mono': 'Fira Mono',
'jetbrains-mono': 'JetBrains Mono'
},
boxShadow: {
DEFAULT: '0 0 1px rgba(0,0,0,.05), 0 5px 18px rgba(0,0,0,.08)',
xs: '0 0 1px rgba(0,0,0,0.04), 0 1px 3px rgba(0,0,0,0.03), 0 8px 10px -12px rgba(0,0,0,.1)',
sm: '0 0 1px rgba(0,0,0,.12), 0 1px 6px rgba(0,0,0,0.03), 0 8px 10px -8px rgba(0,0,0,.1)',
md: '0 0 1px rgba(0,0,0,0.12), 0 1px 6px rgba(0,0,0,0.03), 0 8px 10px -8px rgba(0,0,0,0.05), 0px 24px 37px -21px rgba(0, 0, 0, 0.05)',
'md-heavy': '0 0 1px rgba(0,0,0,0.22), 0 1px 6px rgba(0,0,0,0.15), 0 8px 10px -8px rgba(0,0,0,0.16), 0px 24px 37px -21px rgba(0, 0, 0, 0.46)',
lg: '0 0 7px rgba(0, 0, 0, 0.08), 0 2.1px 2.2px -5px rgba(0, 0, 0, 0.011), 0 5.1px 5.3px -5px rgba(0, 0, 0, 0.016), 0 9.5px 10px -5px rgba(0, 0, 0, 0.02), 0 17px 17.9px -5px rgba(0, 0, 0, 0.024), 0 31.8px 33.4px -5px rgba(0, 0, 0, 0.029), 0 76px 80px -5px rgba(0, 0, 0, 0.04)',
xl: '0 2.8px 2.2px rgba(0, 0, 0, 0.02), 0 6.7px 5.3px rgba(0, 0, 0, 0.028), 0 12.5px 10px rgba(0, 0, 0, 0.035), 0 22.3px 17.9px rgba(0, 0, 0, 0.042), 0 41.8px 33.4px rgba(0, 0, 0, 0.05), 0 100px 80px rgba(0, 0, 0, 0.07)',
inner: 'inset 0 0 4px 0 rgb(0 0 0 / 0.08)',
none: '0 0 #0000'
},
extend: {
keyframes: {
toasterIn: {
'0.00%': {
transform: 'translateY(100%)'
},
'26.52%': {
transform: 'translateY(-3.90px)'
},
'63.26%': {
transform: 'translateY(1.2px)'
},
'100.00%': {
transform: 'translateY(0px)'
}
},
toasterTopIn: {
'0.00%': {
transform: 'translateY(-82px)'
},
'26.52%': {
transform: 'translateY(5.90px)'
},
'63.26%': {
transform: 'translateY(-1.77px)'
},
'100.00%': {
transform: 'translateY(0px)'
}
},
toasterOut: {
'0%': {
opacity: '1'
},
'100%': {
opacity: '0'
}
},
fadeIn: {
'0%': {
opacity: '0'
},
'100%': {
opacity: '1'
}
},
fadeOut: {
'0%': {
opacity: '1'
},
'100%': {
opacity: '0'
}
},
modalIn: {
'0%': {
transform: 'translateY(32px)'
},
'100%': {
transform: 'translateY(0px)'
}
},
modalInFromRight: {
'0%': {
transform: 'translateX(32px)',
opacity: '0'
},
'100%': {
transform: 'translateX(0px)',
opacity: '1'
}
},
modalInReverse: {
'0%': {
transform: 'translateY(-32px)'
},
'100%': {
transform: 'translateY(0px)'
}
},
spin: {
'0%': {
transform: 'rotate(0deg)'
},
'100%': {
transform: 'rotate(360deg)'
}
}
},
animation: {
'toaster-in': 'toasterIn 0.8s cubic-bezier(0.445, 0.050, 0.550, 0.950)',
'toaster-out': 'toasterOut 0.4s 0s 1 ease forwards',
'toaster-top-in': 'toasterTopIn 0.8s cubic-bezier(0.445, 0.050, 0.550, 0.950)',
'fade-in': 'fadeIn 0.15s ease forwards',
'fade-out': 'fadeOut 0.15s ease forwards',
'setting-highlight-fade-out': 'fadeOut 0.2s 1.4s ease forwards',
'modal-backdrop-in': 'fadeIn 0.15s ease forwards',
'modal-in': 'modalIn 0.25s ease forwards',
'modal-in-from-right': 'modalInFromRight 0.25s ease forwards',
'modal-in-reverse': 'modalInReverse 0.25s ease forwards',
spin: 'spin 1s linear infinite'
},
spacing: {
px: '1px',
0: '0px',
0.5: '0.2rem',
1: '0.4rem',
1.5: '0.6rem',
2: '0.8rem',
2.5: '1rem',
3: '1.2rem',
3.5: '1.4rem',
4: '1.6rem',
5: '2rem',
6: '2.4rem',
7: '2.8rem',
8: '3.2rem',
9: '3.6rem',
10: '4rem',
11: '4.4rem',
12: '4.8rem',
14: '5.6rem',
16: '6.4rem',
18: '7.2rem',
20: '8rem',
22: '9.2rem',
24: '9.6rem',
28: '11.2rem',
32: '12.8rem',
36: '14.4rem',
40: '16rem',
44: '17.6rem',
48: '19.2rem',
52: '20.8rem',
56: '22.4rem',
60: '24rem',
64: '25.6rem',
72: '28.8rem',
80: '32rem',
96: '38.4rem'
},
maxWidth: {
none: 'none',
0: '0rem',
xs: '32rem',
sm: '38.4rem',
md: '44.8rem',
lg: '51.2rem',
xl: '57.6rem',
'2xl': '67.2rem',
'3xl': '76.8rem',
'4xl': '89.6rem',
'5xl': '102.4rem',
'6xl': '115.2rem',
'7xl': '132rem',
'8xl': '140rem',
'9xl': '156rem',
full: '100%',
min: 'min-content',
max: 'max-content',
fit: 'fit-content',
prose: '65ch'
},
borderRadius: {
sm: '0.3rem',
DEFAULT: '0.4rem',
md: '0.6rem',
lg: '0.8rem',
xl: '1.2rem',
'2xl': '1.6rem',
'3xl': '2.4rem',
full: '9999px'
},
fontSize: {
'2xs': '1.0rem',
base: '1.4rem',
xs: '1.2rem',
sm: '1.3rem',
md: '1.4rem',
lg: '1.65rem',
xl: '2rem',
'2xl': '2.4rem',
'3xl': '3.2rem',
'4xl': '3.6rem',
'5xl': ['4.2rem', '1.15'],
'6xl': ['6rem', '1'],
'7xl': ['7.2rem', '1'],
'8xl': ['9.6rem', '1'],
'9xl': ['12.8rem', '1'],
inherit: 'inherit'
},
lineHeight: {
base: '1.5em',
tight: '1.35em',
tighter: '1.25em',
supertight: '1.1em'
}
}
screens: {
sm: '480px',
md: '640px',
lg: '1024px',
xl: '1320px',
xxl: '1440px',
xxxl: '1600px',
tablet: '860px'
},
colors: {
transparent: 'transparent',
current: 'currentColor',
accent: 'var(--accent-color, #ff0095)',
white: '#FFF',
black: '#15171A',
grey: {
'50': '#FAFAFB',
'75': '#F9FAFB',
'100': '#F4F5F6',
'150': '#F1F3F4',
'200': '#EBEEF0',
'250': '#E5E9ED',
'300': '#DDE1E5',
'400': '#CED4D9',
'500': '#AEB7C1',
'600': '#95A1AD',
'700': '#7C8B9A',
'800': '#626D79',
'900': '#394047',
'925': '#2E3338',
'950': '#222427',
'975': '#191B1E',
DEFAULT: '#ABB4BE'
},
neutral: {
'50': '#FAFAFB',
'75': '#F9FAFB',
'100': '#F4F5F6',
'150': '#F1F3F4',
'200': '#EBEEF0',
'250': '#E5E9ED',
'300': '#DDE1E5',
'400': '#CED4D9',
'500': '#AEB7C1',
'600': '#95A1AD',
'700': '#7C8B9A',
'800': '#626D79',
'900': '#394047',
'925': '#2E3338',
'950': '#222427',
'975': '#191B1E',
DEFAULT: '#ABB4BE'
},
green: {
'100': '#E1F9E4',
'400': '#58DA67',
'500': '#30CF43',
'600': '#2AB23A',
DEFAULT: '#30CF43'
},
blue: {
'100': '#DBF4FF',
'400': '#42C6FF',
'500': '#14B8FF',
'600': '#00A4EB',
DEFAULT: '#14B8FF'
},
purple: {
'100': '#EDE0FF',
'400': '#A366FF',
'500': '#8E42FF',
'600': '7B1FFF',
DEFAULT: '#8E42FF'
},
pink: {
'100': '#FFDFEE',
'400': '#FF5CA8',
'500': '#FB2D8D',
'600': '#F70878',
DEFAULT: '#FB2D8D'
},
red: {
'100': '#FFE0E0',
'400': '#F9394C',
'500': '#F50B23',
'600': '#DC091E',
DEFAULT: '#F50B23'
},
yellow: {
'100': '#FFF1D6',
'400': '#FFC247',
'500': '#FFB41F',
'600': '#F0A000',
DEFAULT: '#FFB41F'
},
lime: {
DEFAULT: '#B5FF18'
}
},
fontFamily: {
cardo: 'Cardo',
manrope: 'Manrope',
merriweather: 'Merriweather',
nunito: 'Nunito',
'tenor-sans': 'Tenor Sans',
'old-standard-tt': 'Old Standard TT',
prata: 'Prata',
roboto: 'Roboto',
rufina: 'Rufina',
inter: 'Inter',
sans: 'Inter, -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif',
serif: 'Georgia, serif',
mono: 'Consolas, Liberation Mono, Menlo, Courier, monospace',
inherit: 'inherit',
'space-grotesk': 'Space Grotesk',
'chakra-petch': 'Chakra Petch',
'noto-sans': 'Noto Sans',
poppins: 'Poppins',
'fira-sans': 'Fira Sans',
'noto-serif': 'Noto Serif',
lora: 'Lora',
'ibm-plex-serif': 'IBM Plex Serif',
'space-mono': 'Space Mono',
'fira-mono': 'Fira Mono',
'jetbrains-mono': 'JetBrains Mono'
},
boxShadow: {
DEFAULT: '0 0 1px rgba(0,0,0,.05), 0 5px 18px rgba(0,0,0,.08)',
xs: '0 0 1px rgba(0,0,0,0.04), 0 1px 3px rgba(0,0,0,0.03), 0 8px 10px -12px rgba(0,0,0,.1)',
sm: '0 0 1px rgba(0,0,0,.12), 0 1px 6px rgba(0,0,0,0.03), 0 8px 10px -8px rgba(0,0,0,.1)',
md: '0 0 1px rgba(0,0,0,0.12), 0 1px 6px rgba(0,0,0,0.03), 0 8px 10px -8px rgba(0,0,0,0.05), 0px 24px 37px -21px rgba(0, 0, 0, 0.05)',
'md-heavy': '0 0 1px rgba(0,0,0,0.22), 0 1px 6px rgba(0,0,0,0.15), 0 8px 10px -8px rgba(0,0,0,0.16), 0px 24px 37px -21px rgba(0, 0, 0, 0.46)',
lg: '0 0 7px rgba(0, 0, 0, 0.08), 0 2.1px 2.2px -5px rgba(0, 0, 0, 0.011), 0 5.1px 5.3px -5px rgba(0, 0, 0, 0.016), 0 9.5px 10px -5px rgba(0, 0, 0, 0.02), 0 17px 17.9px -5px rgba(0, 0, 0, 0.024), 0 31.8px 33.4px -5px rgba(0, 0, 0, 0.029), 0 76px 80px -5px rgba(0, 0, 0, 0.04)',
xl: '0 2.8px 2.2px rgba(0, 0, 0, 0.02), 0 6.7px 5.3px rgba(0, 0, 0, 0.028), 0 12.5px 10px rgba(0, 0, 0, 0.035), 0 22.3px 17.9px rgba(0, 0, 0, 0.042), 0 41.8px 33.4px rgba(0, 0, 0, 0.05), 0 100px 80px rgba(0, 0, 0, 0.07)',
inner: 'inset 0 0 4px 0 rgb(0 0 0 / 0.08)',
none: '0 0 #0000'
},
extend: {
keyframes: {
toasterIn: {
'0.00%': {
transform: 'translateY(100%)'
},
'26.52%': {
transform: 'translateY(-3.90px)'
},
'63.26%': {
transform: 'translateY(1.2px)'
},
'100.00%': {
transform: 'translateY(0px)'
}
},
toasterTopIn: {
'0.00%': {
transform: 'translateY(-82px)'
},
'26.52%': {
transform: 'translateY(5.90px)'
},
'63.26%': {
transform: 'translateY(-1.77px)'
},
'100.00%': {
transform: 'translateY(0px)'
}
},
toasterOut: {
'0%': {
opacity: '1'
},
'100%': {
opacity: '0'
}
},
fadeIn: {
'0%': {
opacity: '0'
},
'100%': {
opacity: '1'
}
},
fadeOut: {
'0%': {
opacity: '1'
},
'100%': {
opacity: '0'
}
},
modalIn: {
'0%': {
transform: 'translateY(32px)'
},
'100%': {
transform: 'translateY(0px)'
}
},
modalInFromRight: {
'0%': {
transform: 'translateX(32px)',
opacity: '0'
},
'100%': {
transform: 'translateX(0px)',
opacity: '1'
}
},
modalInReverse: {
'0%': {
transform: 'translateY(-32px)'
},
'100%': {
transform: 'translateY(0px)'
}
},
spin: {
'0%': {
transform: 'rotate(0deg)'
},
'100%': {
transform: 'rotate(360deg)'
}
}
},
animation: {
'toaster-in': 'toasterIn 0.8s cubic-bezier(0.445, 0.050, 0.550, 0.950)',
'toaster-out': 'toasterOut 0.4s 0s 1 ease forwards',
'toaster-top-in': 'toasterTopIn 0.8s cubic-bezier(0.445, 0.050, 0.550, 0.950)',
'fade-in': 'fadeIn 0.15s ease forwards',
'fade-out': 'fadeOut 0.15s ease forwards',
'setting-highlight-fade-out': 'fadeOut 0.2s 1.4s ease forwards',
'modal-backdrop-in': 'fadeIn 0.15s ease forwards',
'modal-in': 'modalIn 0.25s ease forwards',
'modal-in-from-right': 'modalInFromRight 0.25s ease forwards',
'modal-in-reverse': 'modalInReverse 0.25s ease forwards',
spin: 'spin 1s linear infinite'
},
spacing: {
'0': '0px',
'1': '0.4rem',
'2': '0.8rem',
'3': '1.2rem',
'4': '1.6rem',
'5': '2rem',
'6': '2.4rem',
'7': '2.8rem',
'8': '3.2rem',
'9': '3.6rem',
'10': '4rem',
'11': '4.4rem',
'12': '4.8rem',
'14': '5.6rem',
'16': '6.4rem',
'18': '7.2rem',
'20': '8rem',
'22': '9.2rem',
'24': '9.6rem',
'28': '11.2rem',
'32': '12.8rem',
'36': '14.4rem',
'40': '16rem',
'44': '17.6rem',
'48': '19.2rem',
'52': '20.8rem',
'56': '22.4rem',
'60': '24rem',
'64': '25.6rem',
'72': '28.8rem',
'80': '32rem',
'96': '38.4rem',
px: '1px',
'0.5': '0.2rem',
'1.5': '0.6rem',
'2.5': '1rem',
'3.5': '1.4rem'
},
maxWidth: {
'0': '0rem',
none: 'none',
xs: '32rem',
sm: '38.4rem',
md: '44.8rem',
lg: '51.2rem',
xl: '57.6rem',
'2xl': '67.2rem',
'3xl': '76.8rem',
'4xl': '89.6rem',
'5xl': '102.4rem',
'6xl': '115.2rem',
'7xl': '132rem',
'8xl': '140rem',
'9xl': '156rem',
full: '100%',
min: 'min-content',
max: 'max-content',
fit: 'fit-content',
prose: '65ch'
},
borderRadius: {
sm: '0.3rem',
DEFAULT: '0.4rem',
md: '0.6rem',
lg: '0.8rem',
xl: '1.2rem',
'2xl': '1.6rem',
'3xl': '2.4rem',
full: '9999px'
},
fontSize: {
'2xs': '1.0rem',
base: '1.4rem',
xs: '1.2rem',
sm: '1.3rem',
md: '1.4rem',
lg: '1.65rem',
xl: '2rem',
'2xl': '2.4rem',
'3xl': '3.2rem',
'4xl': '3.6rem',
'5xl': ['4.2rem', '1.15'],
'6xl': ['6rem', '1'],
'7xl': ['7.2rem', '1'],
'8xl': ['9.6rem', '1'],
'9xl': ['12.8rem', '1'],
inherit: 'inherit'
},
lineHeight: {
base: '1.5em',
tight: '1.35em',
tighter: '1.25em',
supertight: '1.1em'
},
colors: {
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))'
}
}
}
}
};

View File

@ -50,7 +50,7 @@
</li>
{{/if}}
<li>
<LinkTo @route="post-analytics-spike" @current-when="post-analytics-spike">{{svg-jar "chart"}}Post analytics spike</LinkTo>
<LinkTo @route="post-analytics-spike" @current-when="post-analytics-spike">{{svg-jar "chart"}}Post analytics</LinkTo>
</li>
</ul>
<ul class="gh-nav-list gh-nav-manage">

View File

@ -4319,6 +4319,26 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a"
integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==
"@radix-ui/react-dialog@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz#d9345575211d6f2d13e209e84aec9a8584b54d6c"
integrity sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==
dependencies:
"@radix-ui/primitive" "1.1.0"
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-context" "1.1.1"
"@radix-ui/react-dismissable-layer" "1.1.1"
"@radix-ui/react-focus-guards" "1.1.1"
"@radix-ui/react-focus-scope" "1.1.0"
"@radix-ui/react-id" "1.1.0"
"@radix-ui/react-portal" "1.1.2"
"@radix-ui/react-presence" "1.1.1"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.1.0"
aria-hidden "^1.1.1"
react-remove-scroll "2.6.0"
"@radix-ui/react-direction@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b"
@ -4666,7 +4686,7 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-separator@1.1.0":
"@radix-ui/react-separator@1.1.0", "@radix-ui/react-separator@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e"
integrity sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==
@ -4785,6 +4805,24 @@
"@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-visually-hidden" "1.1.0"
"@radix-ui/react-tooltip@^1.1.4":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz#152d8485859b80d395d6b3229f676fef3cec56b3"
integrity sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw==
dependencies:
"@radix-ui/primitive" "1.1.0"
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-context" "1.1.1"
"@radix-ui/react-dismissable-layer" "1.1.1"
"@radix-ui/react-id" "1.1.0"
"@radix-ui/react-popper" "1.2.0"
"@radix-ui/react-portal" "1.1.2"
"@radix-ui/react-presence" "1.1.1"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-visually-hidden" "1.1.0"
"@radix-ui/react-use-callback-ref@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"