feat(design-preview): каркас на блоке app-shell-12 из ReUI PRO
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f97604c23e
commit
609bea6ad7
2 files changed
+176
-101
No files matched your search
@@ -3,9 +3,8 @@ import { Link, useNavigate, useRouterState } from '@tanstack/react-router'
|
|||||||
import {
|
import {
|
||||||
ActivityIcon,
|
ActivityIcon,
|
||||||
ArrowRightIcon,
|
ArrowRightIcon,
|
||||||
CheckIcon,
|
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronsUpDownIcon,
|
LayoutGridIcon,
|
||||||
CloudIcon,
|
CloudIcon,
|
||||||
FingerprintIcon,
|
FingerprintIcon,
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
@@ -29,8 +28,10 @@ import { Badge } from '@/components/reui/badge'
|
|||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
} from '@authportal/ui/components/breadcrumb'
|
} from '@authportal/ui/components/breadcrumb'
|
||||||
import { Button } from '@authportal/ui/components/button'
|
import { Button } from '@authportal/ui/components/button'
|
||||||
import {
|
import {
|
||||||
@@ -69,7 +70,7 @@ import {
|
|||||||
} from '@authportal/ui/components/sidebar'
|
} from '@authportal/ui/components/sidebar'
|
||||||
import { cn } from '@authportal/ui/lib/utils'
|
import { cn } from '@authportal/ui/lib/utils'
|
||||||
import { Segmented } from './bits'
|
import { Segmented } from './bits'
|
||||||
import { FRESH_BY_PAGE, MONITOR_ITEMS, NAV_GROUPS, NAV_ITEMS, type NavItem } from './mock'
|
import { APP_SHORT, FRESH_BY_PAGE, MONITOR_ITEMS, NAV_GROUPS, NAV_ITEMS, type NavItem } from './mock'
|
||||||
import { usePreviewStore } from './preview-store'
|
import { usePreviewStore } from './preview-store'
|
||||||
|
|
||||||
const APP_ICON: Record<AppId, typeof CloudIcon> = {
|
const APP_ICON: Record<AppId, typeof CloudIcon> = {
|
||||||
@@ -82,90 +83,76 @@ const APP_ICON: Record<AppId, typeof CloudIcon> = {
|
|||||||
mm: LayoutDashboardIcon,
|
mm: LayoutDashboardIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
function PreviewAppSwitcher() {
|
/** Бренд как в app-shell-12: логотип, название и подпись; переключатель приложений семейства — в шапке. */
|
||||||
const { isMobile } = useSidebar()
|
function PreviewBrand() {
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<DropdownMenu>
|
<SidebarMenuButton size="lg" aria-label="Auth Portal — Приложения" className="gap-2.5" render={<Link to="/design-preview/apps" />}>
|
||||||
<DropdownMenuTrigger
|
<span className="bg-primary text-primary-foreground flex size-8 shrink-0 items-center justify-center rounded-md" aria-hidden>
|
||||||
render={<SidebarMenuButton size="lg" aria-label="Переключатель приложений: Auth Portal" className="aria-expanded:bg-muted" />}
|
<ShieldCheckIcon className="size-4" />
|
||||||
>
|
</span>
|
||||||
<div className="bg-primary text-primary-foreground flex aspect-square size-8 items-center justify-center rounded-md" aria-hidden>
|
<span className="grid min-w-0 flex-1 text-left leading-tight">
|
||||||
<ShieldCheckIcon className="size-4" />
|
<span className="truncate text-sm font-semibold">Auth Portal</span>
|
||||||
</div>
|
<span className="text-muted-foreground truncate text-xs">Единый вход · SSO</span>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
</span>
|
||||||
<span className="truncate font-semibold">Auth Portal</span>
|
</SidebarMenuButton>
|
||||||
<span className="text-muted-foreground truncate text-xs">Единый вход · SSO</span>
|
|
||||||
</div>
|
|
||||||
<ChevronsUpDownIcon className="ml-auto size-4" />
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
className="min-w-56 rounded-lg"
|
|
||||||
side={isMobile ? 'bottom' : 'right'}
|
|
||||||
align="start"
|
|
||||||
sideOffset={4}
|
|
||||||
>
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuLabel>Приложения</DropdownMenuLabel>
|
|
||||||
<DropdownMenuItem disabled>
|
|
||||||
<KeyRoundIcon />
|
|
||||||
Auth Portal
|
|
||||||
<CheckIcon className="ml-auto size-4" />
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{APPS.map((app) => {
|
|
||||||
const Icon = APP_ICON[app.id]
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={app.id}
|
|
||||||
onClick={() => toast(`Открыть ${app.title}`, { description: `${app.url} · SSO handoff (макет)` })}
|
|
||||||
>
|
|
||||||
<Icon />
|
|
||||||
{app.title}
|
|
||||||
<span className="text-muted-foreground ml-auto font-mono text-[11px]">{app.id}</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Сетка приложений семейства в шапке (app-shell-12: Apps menu). */
|
||||||
|
function AppsMenu() {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger render={<Button variant="ghost" size="icon" aria-label="Приложения семейства" />}>
|
||||||
|
<LayoutGridIcon className="size-[18px]" aria-hidden />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent side="bottom" align="end" sideOffset={8} className="w-72">
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuLabel>Приложения</DropdownMenuLabel>
|
||||||
|
<div className="grid grid-cols-3 gap-1 p-1">
|
||||||
|
<DropdownMenuItem disabled className="h-auto flex-col gap-1.5 py-3 text-center [&_svg]:size-5">
|
||||||
|
<KeyRoundIcon className="text-muted-foreground" aria-hidden />
|
||||||
|
<span className="text-xs font-medium">Auth Portal</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
{APPS.map((app) => {
|
||||||
|
const Icon = APP_ICON[app.id]
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={app.id}
|
||||||
|
onClick={() => toast(`Открыть ${app.title}`, { description: `${app.url} · SSO handoff (макет)` })}
|
||||||
|
className="h-auto flex-col gap-1.5 py-3 text-center [&_svg]:size-5"
|
||||||
|
>
|
||||||
|
<Icon className="text-muted-foreground" aria-hidden />
|
||||||
|
<span className="text-xs font-medium">{APP_SHORT[app.id]}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const LABEL_CLASS = 'text-muted-foreground text-[11px] font-medium uppercase'
|
const LABEL_CLASS = 'text-muted-foreground text-[11px] font-medium uppercase'
|
||||||
|
|
||||||
function PreviewNavUser() {
|
function PreviewUserMenu() {
|
||||||
const { isMobile } = useSidebar()
|
|
||||||
const { theme, setTheme, resolvedTheme } = useTheme()
|
const { theme, setTheme, resolvedTheme } = useTheme()
|
||||||
const { density, setDensity, contrast, setContrast } = usePreviewStore()
|
const { density, setDensity, contrast, setContrast } = usePreviewStore()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const current = theme === 'light' || theme === 'dark' ? theme : resolvedTheme === 'light' ? 'light' : 'dark'
|
const current = theme === 'light' || theme === 'dark' ? theme : resolvedTheme === 'light' ? 'light' : 'dark'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<Popover>
|
||||||
<SidebarMenuItem>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger
|
<PopoverTrigger
|
||||||
render={
|
render={<Button variant="ghost" size="icon" aria-label="Профиль и настройки интерфейса: Admin, admin@shnt.top" className="rounded-full" />}
|
||||||
<SidebarMenuButton
|
>
|
||||||
size="lg"
|
<span className="bg-background text-foreground flex size-7 items-center justify-center rounded-full border text-[11px] font-medium">AD</span>
|
||||||
aria-label="Профиль и настройки интерфейса: Admin, admin@shnt.top"
|
</PopoverTrigger>
|
||||||
className="bg-muted/40 aria-expanded:bg-muted"
|
<PopoverContent side="bottom" align="end" sideOffset={8} className="w-72 gap-3">
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="bg-background text-foreground flex size-8 shrink-0 items-center justify-center rounded-full border text-xs font-medium">
|
|
||||||
AD
|
|
||||||
</div>
|
|
||||||
<div className="grid min-w-0 flex-1 text-left leading-tight">
|
|
||||||
<span className="truncate text-sm font-semibold">Admin</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">admin@shnt.top</span>
|
|
||||||
</div>
|
|
||||||
<ChevronsUpDownIcon className="text-muted-foreground ml-auto size-4" aria-hidden />
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent side={isMobile ? 'top' : 'right'} align="end" sideOffset={8} className="w-72 gap-3">
|
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<div className="bg-background flex size-8 shrink-0 items-center justify-center rounded-full border text-xs font-medium">
|
<div className="bg-background flex size-8 shrink-0 items-center justify-center rounded-full border text-xs font-medium">
|
||||||
AD
|
AD
|
||||||
@@ -217,9 +204,7 @@ function PreviewNavUser() {
|
|||||||
Выйти
|
Выйти
|
||||||
</button>
|
</button>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +246,64 @@ function NavEntry({ item, pathname, pinned, onTogglePin }: { item: NavItem; path
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PreviewSecondaryNav() {
|
||||||
|
const { setPaletteOpen } = usePreviewStore()
|
||||||
|
return (
|
||||||
|
<SidebarGroup className="py-1">
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton size="sm" tooltip="Командная палитра" onClick={() => setPaletteOpen(true)} className="h-8! [&_svg]:size-3.5">
|
||||||
|
<SearchIcon aria-hidden />
|
||||||
|
<span>Поиск и команды</span>
|
||||||
|
<Kbd className="ml-auto group-data-[collapsible=icon]:hidden">⌘K</Kbd>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton size="sm" tooltip="Журнал входов" render={<Link to="/design-preview/logins" />} className="h-8! [&_svg]:size-3.5">
|
||||||
|
<ActivityIcon aria-hidden />
|
||||||
|
<span>Журнал входов</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Кнопка на кромке меню (app-shell-12): свернуть или развернуть. */
|
||||||
|
function SidebarRailToggle() {
|
||||||
|
const { state, toggleSidebar } = useSidebar()
|
||||||
|
const expanded = state === 'expanded'
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={expanded ? 'Свернуть меню' : 'Развернуть меню'}
|
||||||
|
onClick={toggleSidebar}
|
||||||
|
style={{ left: expanded ? 'var(--sidebar-width)' : 'var(--sidebar-width-icon)' }}
|
||||||
|
className="group/rail fixed top-1/2 z-30 hidden h-12 w-7 -translate-y-1/2 cursor-pointer items-center pl-2 outline-none transition-[left] duration-200 ease-linear md:flex"
|
||||||
|
>
|
||||||
|
<span className="flex flex-col items-center">
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn(
|
||||||
|
'bg-foreground/40 group-hover/rail:bg-foreground/60 block h-2 w-0.5 origin-bottom rounded-t-full transition-all duration-100 ease-linear',
|
||||||
|
expanded ? 'group-hover/rail:rotate-40' : 'group-hover/rail:-rotate-40',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn(
|
||||||
|
'bg-foreground/40 group-hover/rail:bg-foreground/60 block h-2 w-0.5 origin-top rounded-b-full transition-all duration-100 ease-linear',
|
||||||
|
expanded ? 'group-hover/rail:-rotate-40' : 'group-hover/rail:rotate-40',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="border-border bg-foreground text-background pointer-events-none absolute left-full -ml-2 -translate-x-0.5 rounded-md border px-2 py-0.5 text-[11px] font-medium whitespace-nowrap opacity-0 shadow-xs transition-all duration-200 ease-out group-hover/rail:translate-x-0 group-hover/rail:opacity-100">
|
||||||
|
{expanded ? 'Свернуть' : 'Развернуть'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function PreviewSidebar() {
|
export function PreviewSidebar() {
|
||||||
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
||||||
const { pins, togglePin } = usePreviewStore()
|
const { pins, togglePin } = usePreviewStore()
|
||||||
@@ -268,8 +311,8 @@ export function PreviewSidebar() {
|
|||||||
const pinned = NAV_ITEMS.filter((i) => pins.includes(i.id))
|
const pinned = NAV_ITEMS.filter((i) => pins.includes(i.id))
|
||||||
return (
|
return (
|
||||||
<Sidebar collapsible="icon">
|
<Sidebar collapsible="icon">
|
||||||
<SidebarHeader className="pb-1.5">
|
<SidebarHeader className="pb-0">
|
||||||
<PreviewAppSwitcher />
|
<PreviewBrand />
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
{pinned.length > 0 ? (
|
{pinned.length > 0 ? (
|
||||||
@@ -318,9 +361,13 @@ export function PreviewSidebar() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter className="px-1!">
|
||||||
<PreviewNavUser />
|
<div className="px-2">
|
||||||
|
<Separator />
|
||||||
|
</div>
|
||||||
|
<PreviewSecondaryNav />
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
|
<SidebarRailToggle />
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -462,35 +509,51 @@ export function PreviewHeader() {
|
|||||||
const current = NAV_ITEMS.find((i) => pathname === i.to || pathname.startsWith(`${i.to}/`))
|
const current = NAV_ITEMS.find((i) => pathname === i.to || pathname.startsWith(`${i.to}/`))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-background/80 sticky top-0 z-20 flex min-h-12 shrink-0 items-center gap-2 border-b px-4 py-1.5 backdrop-blur md:px-6">
|
<header className="bg-background sticky top-0 z-20 flex h-12 shrink-0 items-center gap-2 border-b px-4 md:px-6">
|
||||||
<SidebarTrigger className="-ml-1" />
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
|
<SidebarTrigger className="-ml-1 md:hidden" />
|
||||||
<Breadcrumb className="min-w-0">
|
<Breadcrumb className="min-w-0">
|
||||||
<BreadcrumbList className="flex-nowrap">
|
<BreadcrumbList className="flex-nowrap">
|
||||||
<BreadcrumbItem className="min-w-0">
|
<BreadcrumbItem className="hidden md:block">
|
||||||
<BreadcrumbPage className="truncate">{current?.label ?? 'Макет'}</BreadcrumbPage>
|
<BreadcrumbLink render={<Link to="/design-preview/apps" />}>{current?.group ?? 'Портал'}</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
</BreadcrumbList>
|
<BreadcrumbSeparator className="hidden md:block" />
|
||||||
</Breadcrumb>
|
<BreadcrumbItem className="min-w-0">
|
||||||
<div className="ml-auto flex shrink-0 items-center gap-2">
|
<BreadcrumbPage className="truncate">{current?.label ?? 'Макет'}</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground ml-auto flex shrink-0 items-center gap-1.5">
|
||||||
<FreshPill />
|
<FreshPill />
|
||||||
<FreshnessPopover />
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
aria-label="Командная палитра (⌘K)"
|
|
||||||
className="md:hidden"
|
|
||||||
onClick={() => setPaletteOpen(true)}
|
|
||||||
>
|
|
||||||
<SearchIcon aria-hidden />
|
|
||||||
</Button>
|
|
||||||
<MonitorPopover />
|
<MonitorPopover />
|
||||||
|
<Button type="button" variant="ghost" size="icon" aria-label="Командная палитра (⌘K)" onClick={() => setPaletteOpen(true)}>
|
||||||
|
<SearchIcon className="size-[18px]" aria-hidden />
|
||||||
|
</Button>
|
||||||
|
<FreshnessPopover />
|
||||||
|
<AppsMenu />
|
||||||
|
<PreviewUserMenu />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Подвал страницы как в app-shell-12. */
|
||||||
|
export function PreviewFooter() {
|
||||||
|
return (
|
||||||
|
<footer className="border-t px-4 py-2.5 md:px-6">
|
||||||
|
<div className="flex flex-col items-center justify-between gap-2 text-xs md:flex-row">
|
||||||
|
<span className="text-muted-foreground">2026 © Auth Portal · shnt.top</span>
|
||||||
|
<nav className="text-muted-foreground flex flex-wrap items-center justify-center gap-x-3 gap-y-1" aria-label="Служебные ссылки">
|
||||||
|
<Link to="/design-preview/logins" className="hover:text-foreground">Журнал входов</Link>
|
||||||
|
<Link to="/design-preview/audit" className="hover:text-foreground">Изменения</Link>
|
||||||
|
<Link to="/design-preview/account" className="hover:text-foreground">Безопасность</Link>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type PaletteEntry = {
|
type PaletteEntry = {
|
||||||
id: string
|
id: string
|
||||||
group: string
|
group: string
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useState, type CSSProperties, type ReactNode } from 'react'
|
import { useEffect, useState, type CSSProperties, type ReactNode } from 'react'
|
||||||
import { Skeleton } from '@authportal/ui/components/skeleton'
|
import { Skeleton } from '@authportal/ui/components/skeleton'
|
||||||
import { SidebarInset, SidebarProvider } from '@authportal/ui/components/sidebar'
|
import { SidebarInset, SidebarProvider } from '@authportal/ui/components/sidebar'
|
||||||
|
import { cn } from '@authportal/ui/lib/utils'
|
||||||
import { TooltipProvider } from '@authportal/ui/components/tooltip'
|
import { TooltipProvider } from '@authportal/ui/components/tooltip'
|
||||||
import { CommandPalette, PreviewHeader, PreviewSidebar } from './preview-chrome'
|
import { CommandPalette, PreviewFooter, PreviewHeader, PreviewSidebar } from './preview-chrome'
|
||||||
import { UnsavedBar } from './unsaved-bar'
|
import { UnsavedBar } from './unsaved-bar'
|
||||||
import './design-preview.css'
|
import './design-preview.css'
|
||||||
|
|
||||||
@@ -31,7 +32,17 @@ export function PreviewShell({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delay={0}>
|
<TooltipProvider delay={0}>
|
||||||
<SidebarProvider open={open} onOpenChange={setOpen} style={{ '--sidebar-width': '240px' } as CSSProperties}>
|
<SidebarProvider
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
className={cn(
|
||||||
|
'[--sidebar:color-mix(in_oklab,var(--color-sidebar)_60%,transparent)]',
|
||||||
|
'[--sidebar-border:transparent]',
|
||||||
|
'[--sidebar-accent:color-mix(in_oklab,var(--color-primary)_5%,transparent)]',
|
||||||
|
'[--sidebar-accent-foreground:var(--color-primary)]',
|
||||||
|
)}
|
||||||
|
style={{ '--sidebar-width': '240px' } as CSSProperties}
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href="#main-content"
|
href="#main-content"
|
||||||
className="bg-background text-foreground ring-ring sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:rounded-md focus:px-3 focus:py-2 focus:text-sm focus:font-medium focus:ring-2"
|
className="bg-background text-foreground ring-ring sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:rounded-md focus:px-3 focus:py-2 focus:text-sm focus:font-medium focus:ring-2"
|
||||||
@@ -44,6 +55,7 @@ export function PreviewShell({ children }: { children: ReactNode }) {
|
|||||||
<div className="mx-auto flex w-full min-w-0 max-w-[1480px] flex-1 flex-col gap-4 px-4 py-4 pb-24 md:gap-6 md:px-6 md:py-5 md:pb-24">
|
<div className="mx-auto flex w-full min-w-0 max-w-[1480px] flex-1 flex-col gap-4 px-4 py-4 pb-24 md:gap-6 md:px-6 md:py-5 md:pb-24">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
<PreviewFooter />
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
<UnsavedBar />
|
<UnsavedBar />
|
||||||
|
|||||||
Reference in new issue
Block a user