mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-22 11:16:01 +03:00
Added tabs variant
This commit is contained in:
parent
53a0ccfa1c
commit
85a4f72545
File diff suppressed because one or more lines are too long
1
apps/post-analytics-spike/dist/index-0846be11.mjs.map
vendored
Normal file
1
apps/post-analytics-spike/dist/index-0846be11.mjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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
|
@ -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
@ -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>
|
||||
|
@ -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
|
||||
|
@ -4,7 +4,7 @@
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"config": "tailwind.config.cjs",
|
||||
"css": "styles.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": false,
|
||||
|
@ -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",
|
||||
|
36
apps/shade/src/components/ui/badge.tsx
Normal file
36
apps/shade/src/components/ui/badge.tsx
Normal 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 }
|
22
apps/shade/src/components/ui/input.tsx
Normal file
22
apps/shade/src/components/ui/input.tsx
Normal 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};
|
31
apps/shade/src/components/ui/separator.tsx
Normal file
31
apps/shade/src/components/ui/separator.tsx
Normal 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};
|
138
apps/shade/src/components/ui/sheet.tsx
Normal file
138
apps/shade/src/components/ui/sheet.tsx
Normal 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
|
||||
};
|
762
apps/shade/src/components/ui/sidebar.tsx
Normal file
762
apps/shade/src/components/ui/sidebar.tsx
Normal 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
|
||||
};
|
15
apps/shade/src/components/ui/skeleton.tsx
Normal file
15
apps/shade/src/components/ui/skeleton.tsx
Normal 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};
|
@ -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}
|
||||
|
30
apps/shade/src/components/ui/tooltip.tsx
Normal file
30
apps/shade/src/components/ui/tooltip.tsx
Normal 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};
|
19
apps/shade/src/hooks/use-mobile.tsx
Normal file
19
apps/shade/src/hooks/use-mobile.tsx
Normal 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
|
||||
}
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -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))'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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">
|
||||
|
40
yarn.lock
40
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user