docs: add reference design
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export function Stamp({
|
||||
children,
|
||||
className,
|
||||
skew = true,
|
||||
variant = 'red',
|
||||
}: {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
skew?: boolean
|
||||
variant?: 'red' | 'ink'
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'stamp px-2 py-1 text-[0.65rem] sm:text-xs',
|
||||
variant === 'ink' && 'stamp-ink',
|
||||
skew && 'stamp-skew',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function OverlayStamp({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
'stamp-overlay pointer-events-none select-none px-4 py-2 font-heading text-2xl sm:text-4xl',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function Redacted({ children }: { children: ReactNode }) {
|
||||
return <span className="redacted px-1">{children}</span>
|
||||
}
|
||||
|
||||
export function FieldRow({
|
||||
label,
|
||||
children,
|
||||
}: {
|
||||
label: string
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-1 border-b border-dashed border-border py-3 sm:grid-cols-[200px_1fr] sm:gap-4">
|
||||
<dt className="font-heading text-xs font-600 uppercase tracking-wider text-muted-foreground">
|
||||
{label}
|
||||
</dt>
|
||||
<dd className="text-sm leading-relaxed text-foreground">{children}</dd>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SectionTitle({
|
||||
index,
|
||||
children,
|
||||
}: {
|
||||
index: string
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<h2 className="mb-4 flex items-baseline gap-3 font-heading text-lg font-700 uppercase tracking-wide">
|
||||
<span className="flex h-7 min-w-7 items-center justify-center bg-primary px-1.5 text-sm text-primary-foreground">
|
||||
{index}
|
||||
</span>
|
||||
<span className="flex-1 border-b border-foreground/30 pb-1">
|
||||
{children}
|
||||
</span>
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
|
||||
export function Perforation({ className }: { className?: string }) {
|
||||
return (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
'flex h-4 w-full items-center justify-between overflow-hidden px-2 opacity-60',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{Array.from({ length: 60 }).map((_, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="h-1.5 w-1.5 shrink-0 rounded-full bg-background shadow-[inset_0_1px_2px_rgba(0,0,0,0.35)]"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import Link from 'next/link'
|
||||
import type { Dossier } from '@/lib/dossiers'
|
||||
import { Stamp } from '@/components/doc-elements'
|
||||
|
||||
const classColor: Record<string, string> = {
|
||||
'СОВ. СЕКРЕТНО': 'text-primary',
|
||||
'СЕКРЕТНО': 'stamp-ink',
|
||||
'ДСП': 'stamp-ink',
|
||||
'РАССЕКРЕЧЕНО': 'stamp-ink opacity-60',
|
||||
}
|
||||
|
||||
export function DossierCard({ d, rotate }: { d: Dossier; rotate?: string }) {
|
||||
return (
|
||||
<Link
|
||||
href={`/dossier/${d.slug}`}
|
||||
style={{ rotate }}
|
||||
className="paper group relative flex flex-col gap-3 p-5 transition-all duration-200 hover:-translate-y-1 hover:rotate-0 hover:shadow-[0_26px_50px_-22px_rgba(40,25,15,0.7)]"
|
||||
>
|
||||
{/* tape pin */}
|
||||
<span className="tape -top-3 left-1/2 -translate-x-1/2 -rotate-2" aria-hidden="true" />
|
||||
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p className="font-mono text-[0.65rem] uppercase tracking-widest text-muted-foreground">
|
||||
Дело № {d.code}
|
||||
</p>
|
||||
<p className="mt-0.5 font-heading text-[0.7rem] uppercase tracking-wider text-primary">
|
||||
{d.category}
|
||||
</p>
|
||||
</div>
|
||||
<Stamp
|
||||
className={`px-1.5 py-0.5 text-[0.55rem] ${classColor[d.classification] ?? ''}`}
|
||||
>
|
||||
{d.classification}
|
||||
</Stamp>
|
||||
</div>
|
||||
|
||||
<h3 className="font-heading text-xl font-700 uppercase leading-none tracking-tight transition-colors group-hover:text-primary">
|
||||
{d.name}
|
||||
</h3>
|
||||
<p className="text-xs italic leading-snug text-muted-foreground">
|
||||
{d.fullName}
|
||||
</p>
|
||||
<p className="text-sm leading-relaxed text-foreground/90">{d.summary}</p>
|
||||
|
||||
<div className="mt-auto flex items-center justify-between border-t border-dashed border-border pt-3">
|
||||
<span className="font-mono text-[0.65rem] uppercase tracking-wider text-muted-foreground">
|
||||
Угроза: <span className="text-foreground">{d.threat}</span>
|
||||
</span>
|
||||
<span className="font-heading text-[0.7rem] font-600 uppercase tracking-wider text-primary opacity-0 transition-opacity group-hover:opacity-100">
|
||||
Открыть дело →
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export { Stamp }
|
||||
@@ -0,0 +1,68 @@
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="sticky top-0 z-30 border-b border-foreground/25 bg-card/85 backdrop-blur-sm shadow-[0_2px_12px_-8px_rgba(40,25,15,0.6)]">
|
||||
<div className="mx-auto flex max-w-5xl flex-col gap-4 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6">
|
||||
<Link href="/" className="flex items-center gap-3">
|
||||
<Image
|
||||
src="/emblem.png"
|
||||
alt="Эмблема Трибунала"
|
||||
width={52}
|
||||
height={52}
|
||||
className="h-12 w-12 mix-blend-multiply"
|
||||
/>
|
||||
<div className="leading-tight">
|
||||
<p className="font-heading text-base font-700 uppercase tracking-wide sm:text-lg">
|
||||
Архив ДНТ-7
|
||||
</p>
|
||||
<p className="text-[0.65rem] uppercase tracking-widest text-muted-foreground">
|
||||
Дачно-Наблюдательный Трибунал
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<nav className="flex flex-wrap items-center gap-x-5 gap-y-1 font-heading text-xs font-600 uppercase tracking-wider">
|
||||
{[
|
||||
{ href: '/', label: 'Картотека' },
|
||||
{ href: '/dossier/directive-kompot', label: 'Уставы' },
|
||||
{ href: '/dossier/lopata-bs-3000', label: 'Инвентарь' },
|
||||
{ href: '/protocol', label: 'Протокол' },
|
||||
].map((l) => (
|
||||
<Link
|
||||
key={l.label}
|
||||
href={l.href}
|
||||
className="relative text-foreground/80 transition-colors hover:text-primary"
|
||||
>
|
||||
{l.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="mt-16 border-t border-foreground/25 bg-card/60">
|
||||
<div className="mx-auto max-w-5xl px-4 py-8 sm:px-6">
|
||||
<div className="flex flex-col justify-between gap-4 text-[0.7rem] uppercase tracking-wider text-muted-foreground sm:flex-row">
|
||||
<p>
|
||||
©{' '}
|
||||
<span className="text-foreground">
|
||||
Дачно-Наблюдательный Трибунал №7
|
||||
</span>{' '}
|
||||
· Кооператив «Заря-9»
|
||||
</p>
|
||||
<p>Форма А-13 · Хранить вечно · Не выносить за периметр</p>
|
||||
</div>
|
||||
<p className="mt-4 max-w-2xl text-[0.7rem] leading-relaxed text-muted-foreground">
|
||||
Все материалы являются художественным вымыслом. Любое совпадение с
|
||||
реальными грядками, соседями и небесными телами случайно. Разглашение
|
||||
преследуется лишением компоста.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Button as ButtonPrimitive } from '@base-ui/react/button'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const buttonVariants = cva(
|
||||
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
|
||||
outline:
|
||||
'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
|
||||
ghost:
|
||||
'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',
|
||||
destructive:
|
||||
'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default:
|
||||
'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
||||
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
||||
lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
|
||||
icon: 'size-8',
|
||||
'icon-xs':
|
||||
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
||||
'icon-sm':
|
||||
'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
|
||||
'icon-lg': 'size-9',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant = 'default',
|
||||
size = 'default',
|
||||
...props
|
||||
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
||||
return (
|
||||
<ButtonPrimitive
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
Reference in New Issue
Block a user