mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-07-07 22:18:39 +00:00
fix
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const THEME_OPTIONS = [
|
||||
{ label: "Taupe", value: "theme-taupe" },
|
||||
@@ -54,21 +54,25 @@ export function ThemeSwitcher() {
|
||||
}, [theme])
|
||||
|
||||
return (
|
||||
<div className="dark fixed inset-x-0 bottom-4 z-50 flex justify-center px-4">
|
||||
<div className="w-full max-w-fit rounded-full border bg-background/80 p-2 shadow-lg backdrop-blur supports-backdrop-filter:bg-background/50">
|
||||
<div className="no-scrollbar flex snap-x snap-mandatory items-center gap-2 overflow-x-auto">
|
||||
<div className="fixed inset-x-0 bottom-4 z-50 flex justify-center px-4">
|
||||
<div className="w-full max-w-fit rounded-full border-0 bg-neutral-950/80 p-1.5 text-neutral-100 shadow-lg ring-1 ring-neutral-950/80 backdrop-blur-xl dark:bg-neutral-800/90 dark:ring-neutral-700/50">
|
||||
<div className="no-scrollbar flex snap-x snap-mandatory items-center gap-1 overflow-x-auto">
|
||||
{THEME_OPTIONS.map((option) => (
|
||||
<Button
|
||||
<button
|
||||
key={option.value}
|
||||
variant={theme === option.value ? "default" : "ghost"}
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setTheme(option.value)
|
||||
}}
|
||||
className="snap-center rounded-full text-white data-[variant=default]:text-black"
|
||||
className={cn(
|
||||
"shrink-0 snap-center rounded-full px-3 py-1.5 text-sm font-medium outline-hidden select-none",
|
||||
theme === option.value
|
||||
? "bg-neutral-600 text-neutral-100 dark:bg-neutral-700/80"
|
||||
: "text-neutral-400 hover:bg-neutral-600 hover:text-neutral-100 dark:hover:bg-neutral-700/80"
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,8 +13,6 @@ import { ThemeSwitcher } from "./components/theme-switcher"
|
||||
import { Preview01 } from "./preview-01"
|
||||
import { Preview02 } from "./preview-02"
|
||||
import { Preview03 } from "./preview-03"
|
||||
import { Preview04 } from "./preview-04"
|
||||
import { Preview05 } from "./preview-05"
|
||||
|
||||
import "./style.css"
|
||||
|
||||
@@ -66,8 +64,6 @@ export default function SeraPage() {
|
||||
<Preview01 />
|
||||
<Preview02 />
|
||||
<Preview03 />
|
||||
<Preview04 />
|
||||
<Preview05 />
|
||||
</div>
|
||||
</div>
|
||||
<ThemeSwitcher />
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
|
||||
import { ChevronLeftIcon, ChevronRightIcon, SearchIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/styles/base-sera/ui/button"
|
||||
import { Badge } from "@/styles/base-sera/ui/badge"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
} from "@/styles/base-sera/ui/card"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
} from "@/styles/base-sera/ui/input-group"
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
} from "@/styles/base-sera/ui/pagination"
|
||||
import { Progress, ProgressValue } from "@/styles/base-sera/ui/progress"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -14,20 +31,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/styles/base-sera/ui/table"
|
||||
|
||||
type ArticleStatus = "in-revision" | "final-edit" | "drafting" | "published"
|
||||
|
||||
type ArticleRow = {
|
||||
title: string
|
||||
wordProgress: string
|
||||
author: string
|
||||
issue: string
|
||||
status: ArticleStatus
|
||||
statusLabel: string
|
||||
progress: number
|
||||
progressClassName: string
|
||||
}
|
||||
|
||||
const ARTICLE_ROWS: ArticleRow[] = [
|
||||
const ARTICLE_ROWS = [
|
||||
{
|
||||
title: "The Future of Sustainable Architecture",
|
||||
wordProgress: "1.4k / 2.6k words",
|
||||
@@ -36,7 +40,6 @@ const ARTICLE_ROWS: ArticleRow[] = [
|
||||
status: "in-revision",
|
||||
statusLabel: "In revision",
|
||||
progress: 45,
|
||||
progressClassName: "w-[45%]",
|
||||
},
|
||||
{
|
||||
title: "Brutalism's Second Act",
|
||||
@@ -46,7 +49,6 @@ const ARTICLE_ROWS: ArticleRow[] = [
|
||||
status: "final-edit",
|
||||
statusLabel: "Final edit",
|
||||
progress: 90,
|
||||
progressClassName: "w-[90%]",
|
||||
},
|
||||
{
|
||||
title: "The Typography of Public Spaces",
|
||||
@@ -56,7 +58,6 @@ const ARTICLE_ROWS: ArticleRow[] = [
|
||||
status: "drafting",
|
||||
statusLabel: "Drafting",
|
||||
progress: 20,
|
||||
progressClassName: "w-[20%]",
|
||||
},
|
||||
{
|
||||
title: "Rethinking Urban Canopies",
|
||||
@@ -66,231 +67,150 @@ const ARTICLE_ROWS: ArticleRow[] = [
|
||||
status: "published",
|
||||
statusLabel: "Published",
|
||||
progress: 100,
|
||||
progressClassName: "w-full",
|
||||
},
|
||||
]
|
||||
{
|
||||
title: "Light, Glass, and the Modern Museum",
|
||||
wordProgress: "1.2k / 2.0k words",
|
||||
author: "Amara Osei",
|
||||
issue: "Autumn 2024",
|
||||
status: "in-revision",
|
||||
statusLabel: "In revision",
|
||||
progress: 55,
|
||||
},
|
||||
{
|
||||
title: "Concrete Utopias: Housing in the 21st Century",
|
||||
wordProgress: "3.0k / 3.0k words",
|
||||
author: "Tomás Herrera",
|
||||
issue: "Summer 2024",
|
||||
status: "published",
|
||||
statusLabel: "Published",
|
||||
progress: 100,
|
||||
},
|
||||
{
|
||||
title: "Designing for Silence",
|
||||
wordProgress: "0.8k / 2.2k words",
|
||||
author: "Ingrid Solberg",
|
||||
issue: "Winter 2024",
|
||||
status: "drafting",
|
||||
statusLabel: "Drafting",
|
||||
progress: 30,
|
||||
},
|
||||
{
|
||||
title: "The Invisible Infrastructure of Cities",
|
||||
wordProgress: "2.4k / 2.8k words",
|
||||
author: "James Whitfield",
|
||||
issue: "Autumn 2024",
|
||||
status: "final-edit",
|
||||
statusLabel: "Final edit",
|
||||
progress: 85,
|
||||
},
|
||||
] as const
|
||||
|
||||
function getStatusClasses(status: ArticleStatus): {
|
||||
dotClassName: string
|
||||
textClassName: string
|
||||
} {
|
||||
if (status === "in-revision") {
|
||||
return {
|
||||
dotClassName: "bg-amber-600/75",
|
||||
textClassName: "text-amber-700 dark:text-amber-300",
|
||||
}
|
||||
}
|
||||
const STATUS_BADGE_VARIANT = {
|
||||
"in-revision": "outline",
|
||||
"final-edit": "default",
|
||||
drafting: "ghost",
|
||||
published: "secondary",
|
||||
} as const
|
||||
|
||||
if (status === "final-edit") {
|
||||
return {
|
||||
dotClassName: "bg-foreground/90",
|
||||
textClassName: "text-foreground",
|
||||
}
|
||||
}
|
||||
|
||||
if (status === "drafting") {
|
||||
return {
|
||||
dotClassName: "bg-muted-foreground/70",
|
||||
textClassName: "text-muted-foreground",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dotClassName: "bg-muted-foreground/80",
|
||||
textClassName: "text-muted-foreground",
|
||||
}
|
||||
}
|
||||
|
||||
function PaginationButton({
|
||||
children,
|
||||
active = false,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
active?: boolean
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
size="icon-sm"
|
||||
variant={active ? "default" : "ghost"}
|
||||
className={cn(
|
||||
"size-8 border text-[11px] tracking-[0.12em]",
|
||||
active
|
||||
? "border-foreground bg-foreground text-background hover:bg-foreground/90"
|
||||
: "border-transparent text-muted-foreground hover:border-border hover:bg-transparent hover:text-foreground"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
const STATUS_DOT_CLASSNAME = {
|
||||
"in-revision": "bg-amber-600/80",
|
||||
"final-edit": "bg-foreground/90",
|
||||
drafting: "bg-muted-foreground/60",
|
||||
published: "bg-emerald-600/80",
|
||||
}
|
||||
|
||||
export function ArticleDirectory() {
|
||||
return (
|
||||
<section className="flex min-h-[860px] flex-col border border-border/70 bg-background">
|
||||
<div className="flex flex-col gap-3 p-4 md:hidden">
|
||||
{ARTICLE_ROWS.map((row) => {
|
||||
const statusClasses = getStatusClasses(row.status)
|
||||
|
||||
return (
|
||||
<article key={row.title} className="flex flex-col gap-3 border border-border/70 p-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h2 className="font-heading text-xl leading-tight tracking-tight">{row.title}</h2>
|
||||
<p className="text-xs text-muted-foreground">{row.wordProgress}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2 text-xs">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Author
|
||||
</span>
|
||||
<span>{row.author}</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Issue
|
||||
</span>
|
||||
<span>{row.issue}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 text-[10px] font-semibold tracking-[0.16em] uppercase",
|
||||
statusClasses.textClassName
|
||||
)}
|
||||
>
|
||||
<span className={cn("size-1.5 rounded-full", statusClasses.dotClassName)} />
|
||||
<span>{row.statusLabel}</span>
|
||||
</div>
|
||||
<span className="text-xs font-medium">{row.progress}%</span>
|
||||
</div>
|
||||
|
||||
<div className="relative h-px w-full bg-border">
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-y-0 left-0 bg-foreground",
|
||||
row.progressClassName
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
|
||||
<footer className="flex items-center justify-between gap-4 border border-border/70 p-4">
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Showing 4 of 24
|
||||
</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<PaginationButton>
|
||||
<ChevronLeftIcon className="size-3.5" />
|
||||
</PaginationButton>
|
||||
<PaginationButton active>1</PaginationButton>
|
||||
<PaginationButton>2</PaginationButton>
|
||||
<PaginationButton>3</PaginationButton>
|
||||
<PaginationButton>
|
||||
<ChevronRightIcon className="size-3.5" />
|
||||
</PaginationButton>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div className="hidden min-h-[860px] md:flex md:flex-col">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<InputGroup>
|
||||
<InputGroupAddon>
|
||||
<SearchIcon />
|
||||
</InputGroupAddon>
|
||||
<InputGroupInput type="search" placeholder="Search articles..." />
|
||||
</InputGroup>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader className="[&_tr]:border-border/70">
|
||||
<TableRow className="h-12 hover:bg-transparent">
|
||||
<TableHead className="px-6 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
|
||||
Title
|
||||
</TableHead>
|
||||
<TableHead className="w-[170px] px-3 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
|
||||
Author
|
||||
</TableHead>
|
||||
<TableHead className="w-[150px] px-3 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
|
||||
Issue
|
||||
</TableHead>
|
||||
<TableHead className="w-[180px] px-3 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
|
||||
Status
|
||||
</TableHead>
|
||||
<TableHead className="w-[200px] px-6 text-right text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
|
||||
Progress
|
||||
</TableHead>
|
||||
<TableHeader>
|
||||
<TableRow className="hover:bg-transparent">
|
||||
<TableHead>Title</TableHead>
|
||||
<TableHead className="w-[170px]">Author</TableHead>
|
||||
<TableHead className="w-[150px]">Issue</TableHead>
|
||||
<TableHead className="w-[180px]">Status</TableHead>
|
||||
<TableHead className="w-[140px]">Progress</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{ARTICLE_ROWS.map((row) => {
|
||||
const statusClasses = getStatusClasses(row.status)
|
||||
|
||||
return (
|
||||
<TableRow key={row.title} className="h-20 border-border/60 hover:bg-transparent">
|
||||
<TableCell className="px-6 py-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="font-heading text-3xl leading-none tracking-tight text-foreground">
|
||||
{row.title}
|
||||
</p>
|
||||
<p className="text-[11px] text-muted-foreground">{row.wordProgress}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="px-3 py-4 text-sm text-foreground">
|
||||
{row.author}
|
||||
</TableCell>
|
||||
<TableCell className="px-3 py-4 text-sm text-foreground">
|
||||
{row.issue}
|
||||
</TableCell>
|
||||
<TableCell className="px-3 py-4">
|
||||
<div
|
||||
{ARTICLE_ROWS.map((row) => (
|
||||
<TableRow key={row.title}>
|
||||
<TableCell>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="font-heading text-xl tracking-tight text-foreground">
|
||||
{row.title}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{row.wordProgress}
|
||||
</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{row.author}</TableCell>
|
||||
<TableCell>{row.issue}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={STATUS_BADGE_VARIANT[row.status]}>
|
||||
<span
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 text-[10px] font-semibold tracking-[0.16em] uppercase",
|
||||
statusClasses.textClassName
|
||||
"size-1.5 rounded-full",
|
||||
STATUS_DOT_CLASSNAME[row.status]
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn("size-1.5 rounded-full", statusClasses.dotClassName)}
|
||||
/>
|
||||
<span>{row.statusLabel}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="px-6 py-4">
|
||||
<div className="flex items-center justify-end gap-3">
|
||||
<div className="relative h-px w-16 bg-border">
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-y-0 left-0 bg-foreground",
|
||||
row.progressClassName
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<span className="w-9 text-right text-[11px] font-medium">
|
||||
{row.progress}%
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
/>
|
||||
{row.statusLabel}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Progress
|
||||
value={row.progress}
|
||||
aria-label={`${row.progress}% complete`}
|
||||
className="flex flex-row-reverse items-center **:data-[slot=progress-track]:w-16"
|
||||
>
|
||||
<ProgressValue>
|
||||
{(formattedValue) => `${formattedValue}`}
|
||||
</ProgressValue>
|
||||
</Progress>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<div className="flex-1 border-t border-border/60" />
|
||||
|
||||
<footer className="flex items-center justify-between gap-4 border-t border-border/70 px-6 py-4">
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Showing 4 of 24 articles
|
||||
</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<PaginationButton>
|
||||
<ChevronLeftIcon className="size-3.5" />
|
||||
</PaginationButton>
|
||||
<PaginationButton active>1</PaginationButton>
|
||||
<PaginationButton>2</PaginationButton>
|
||||
<PaginationButton>3</PaginationButton>
|
||||
<PaginationButton>
|
||||
<ChevronRightIcon className="size-3.5" />
|
||||
</PaginationButton>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationLink
|
||||
href="#"
|
||||
size="icon-sm"
|
||||
aria-label="Previous page"
|
||||
>
|
||||
<ChevronLeftIcon className="cn-rtl-flip" />
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
{[1, 2, 3].map((page) => (
|
||||
<PaginationItem key={page}>
|
||||
<PaginationLink href="#" size="icon-sm" isActive={page === 1}>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
<PaginationItem>
|
||||
<PaginationLink href="#" size="icon-sm" aria-label="Next page">
|
||||
<ChevronRightIcon className="cn-rtl-flip" />
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,56 +1,46 @@
|
||||
"use client"
|
||||
import { ArrowLeftIcon, PlusIcon } from "lucide-react"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronLeftIcon, SearchIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-sera/ui/button"
|
||||
import { Input } from "@/styles/base-sera/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/styles/base-sera/ui/select"
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/styles/base-sera/ui/breadcrumb"
|
||||
import { Button } from "@/styles/base-sera/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-sera/ui/button-group"
|
||||
|
||||
export function PreviewHeader() {
|
||||
return (
|
||||
<header className="container flex flex-col gap-8 py-8 md:py-10">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex items-center gap-1.5 text-[11px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
<ChevronLeftIcon className="size-3.5" />
|
||||
<span>Editorial dashboard</span>
|
||||
<header>
|
||||
<div className="container flex flex-col items-center justify-center gap-(--gap) py-(--gap) sm:flex-row sm:justify-between">
|
||||
<div className="flex flex-col gap-2 text-center sm:text-left">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList className="justify-center md:justify-start">
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
href="#"
|
||||
className="inline-flex items-center gap-1.5"
|
||||
>
|
||||
<ArrowLeftIcon className="size-3" />
|
||||
Editorial Dashboard
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<h1 className="line-clamp-1 font-heading text-3xl tracking-wide uppercase md:text-3xl lg:text-4xl">
|
||||
Article Directory
|
||||
</h1>
|
||||
</div>
|
||||
<h1 className="font-heading text-4xl leading-none tracking-tight uppercase md:text-5xl">
|
||||
Article Directory
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-end">
|
||||
<div className="relative w-full max-w-[320px]">
|
||||
<SearchIcon className="pointer-events-none absolute top-1/2 left-3 size-3.5 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search articles..."
|
||||
className="h-10 border border-border/80 bg-background py-2 pr-3 pl-9 text-[11px] font-medium tracking-[0.14em] uppercase placeholder:tracking-[0.14em]"
|
||||
/>
|
||||
<div>
|
||||
<ButtonGroup className="gap-2 sm:ml-auto md:gap-4">
|
||||
<Button>
|
||||
<PlusIcon data-icon="inline-start" />
|
||||
New Article
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<Select defaultValue="all-issues">
|
||||
<SelectTrigger className="h-10 min-w-[132px] border border-border/80 bg-background px-4 text-[11px] font-semibold tracking-[0.14em] uppercase">
|
||||
<SelectValue placeholder="All issues" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all-issues">All issues</SelectItem>
|
||||
<SelectItem value="summer-2024">Summer 2024</SelectItem>
|
||||
<SelectItem value="autumn-2024">Autumn 2024</SelectItem>
|
||||
<SelectItem value="winter-2024">Winter 2024</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Button className="h-10 px-5 text-[11px] tracking-[0.16em]">
|
||||
New Article
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
||||
|
||||
import { ArticleDirectory } from "./components/article-directory"
|
||||
import { PreviewHeader } from "./components/preview-header"
|
||||
|
||||
export function Preview02() {
|
||||
return (
|
||||
<div className="preview theme-taupe w-full flex-1 bg-muted font-sans ring-1 ring-foreground/5">
|
||||
<div className="preview theme-taupe @container/preview w-full flex-1 bg-muted pt-4 font-sans ring-1 ring-foreground/5 [--gap:--spacing(4)] sm:pt-0 md:[--gap:--spacing(6)] xl:[--gap:--spacing(8)] **:[.container]:px-(--gap)">
|
||||
<PreviewHeader />
|
||||
<div className="container pb-8">
|
||||
<Separator className="hidden sm:block" />
|
||||
<div className="container py-(--gap)">
|
||||
<ArticleDirectory />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,67 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { BoldIcon, Code2Icon, LinkIcon } from "lucide-react"
|
||||
import {
|
||||
AlignCenterIcon,
|
||||
AlignLeftIcon,
|
||||
AlignRightIcon,
|
||||
BoldIcon,
|
||||
ChevronDownIcon,
|
||||
Code2Icon,
|
||||
Heading1Icon,
|
||||
Heading2Icon,
|
||||
Heading3Icon,
|
||||
ImageIcon,
|
||||
ItalicIcon,
|
||||
LinkIcon,
|
||||
ListIcon,
|
||||
ListOrderedIcon,
|
||||
RedoIcon,
|
||||
StrikethroughIcon,
|
||||
TypeIcon,
|
||||
UnderlineIcon,
|
||||
UndoIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-sera/ui/button"
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupSeparator,
|
||||
} from "@/styles/base-sera/ui/button-group"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/styles/base-sera/ui/card"
|
||||
import { Checkbox } from "@/styles/base-sera/ui/checkbox"
|
||||
import { Progress, ProgressValue } from "@/styles/base-sera/ui/progress"
|
||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-sera/ui/dropdown-menu"
|
||||
import {
|
||||
Field,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSet,
|
||||
} from "@/styles/base-sera/ui/field"
|
||||
import { Input } from "@/styles/base-sera/ui/input"
|
||||
import {
|
||||
Progress,
|
||||
ProgressLabel,
|
||||
ProgressValue,
|
||||
} from "@/styles/base-sera/ui/progress"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/styles/base-sera/ui/select"
|
||||
import { Textarea } from "@/styles/base-sera/ui/textarea"
|
||||
|
||||
type Milestone = {
|
||||
name: string
|
||||
@@ -45,160 +93,222 @@ const MILESTONES: Milestone[] = [
|
||||
},
|
||||
]
|
||||
|
||||
const ISSUES = [
|
||||
{ label: "Spring Issue 2024", value: "spring-2024" },
|
||||
{ label: "Summer Issue 2024", value: "summer-2024" },
|
||||
{ label: "Autumn Issue 2024", value: "autumn-2024" },
|
||||
{ label: "Winter Issue 2024", value: "winter-2024" },
|
||||
]
|
||||
|
||||
export function EditorWorkspace() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 items-start gap-6 xl:grid-cols-[minmax(0,1fr)_300px]">
|
||||
<section className="flex min-h-[980px] flex-col border border-border/70 bg-background">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-border/70 px-4 py-3">
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 px-2 text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase"
|
||||
>
|
||||
Normal Text
|
||||
<section className="flex flex-col border border-border/70 bg-background">
|
||||
<div className="flex border-b p-2">
|
||||
<ButtonGroup>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button variant="ghost" size="sm">
|
||||
Normal Text
|
||||
<ChevronDownIcon data-icon="inline-end" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>
|
||||
<TypeIcon />
|
||||
Normal Text
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Heading1Icon />
|
||||
Heading 1
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Heading2Icon />
|
||||
Heading 2
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Heading3Icon />
|
||||
Heading 3
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ListIcon />
|
||||
Bullet List
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ListOrderedIcon />
|
||||
Numbered List
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<ButtonGroupSeparator className="mx-2 data-vertical:h-4 data-vertical:self-center" />
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Bold">
|
||||
<BoldIcon />
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<Button variant="ghost" size="icon-sm" className="size-7">
|
||||
<BoldIcon className="size-3.5" />
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Italic">
|
||||
<ItalicIcon />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon-sm" className="size-7">
|
||||
<Code2Icon className="size-3.5" />
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Underline">
|
||||
<UnderlineIcon />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon-sm" className="size-7">
|
||||
<LinkIcon className="size-3.5" />
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Strikethrough">
|
||||
<StrikethroughIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
1,402 words
|
||||
</p>
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Code">
|
||||
<Code2Icon />
|
||||
</Button>
|
||||
<ButtonGroupSeparator className="mx-2 data-vertical:h-4 data-vertical:self-center" />
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Align Left">
|
||||
<AlignLeftIcon />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Align Center">
|
||||
<AlignCenterIcon />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Align Right">
|
||||
<AlignRightIcon />
|
||||
</Button>
|
||||
<ButtonGroupSeparator className="mx-2 data-vertical:h-4 data-vertical:self-center" />
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Link">
|
||||
<LinkIcon />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Image">
|
||||
<ImageIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className="ml-auto">
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Undo">
|
||||
<UndoIcon />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Redo">
|
||||
<RedoIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col gap-8 px-10 py-10 md:px-14">
|
||||
<p className="max-w-[72ch] text-[29px] leading-[1.65] text-foreground/95">
|
||||
As cities continue to expand at an unprecedented rate, the architectural
|
||||
paradigm is shifting from mere expansion to sustainable integration. The
|
||||
concrete jungles of the 20th century are making way for structures that
|
||||
breathe, adapt, and give back to their environments.
|
||||
<div className="mx-auto flex max-w-2xl flex-1 flex-col gap-8 px-10 py-10 md:px-14">
|
||||
<h1 className="font-heading text-4xl tracking-wide uppercase">
|
||||
The Future of Sustainable Architecture
|
||||
</h1>
|
||||
<p>
|
||||
As cities continue to expand at an unprecedented rate, the
|
||||
architectural paradigm is shifting from mere expansion to
|
||||
sustainable integration. The concrete jungles of the 20th century
|
||||
are making way for structures that breathe, adapt, and give back to
|
||||
their environments.
|
||||
</p>
|
||||
|
||||
<p className="max-w-[72ch] text-[29px] leading-[1.65] text-foreground/95">
|
||||
Historically, urban development has been a zero-sum game with nature. We
|
||||
paved over wetlands, redirected rivers, and constructed imposing glass towers
|
||||
that guzzled energy. However, a new generation of architects, like Alistair
|
||||
Sterling, are proposing a radical rethink of our built environment.
|
||||
<p>
|
||||
Historically, urban development has been a zero-sum game with
|
||||
nature. We paved over wetlands, redirected rivers, and constructed
|
||||
imposing glass towers that guzzled energy. However, a new generation
|
||||
of architects, like Alistair Sterling, are proposing a radical
|
||||
rethink of our built environment.
|
||||
</p>
|
||||
|
||||
<h2 className="font-heading text-4xl tracking-tight uppercase">
|
||||
<h2 className="font-heading text-2xl tracking-wide uppercase">
|
||||
The Living Building Challenge
|
||||
</h2>
|
||||
|
||||
<p className="max-w-[72ch] text-[29px] leading-[1.65] text-foreground/95">
|
||||
Sterling's latest project in downtown Seattle is a testament to this new
|
||||
philosophy. "We are no longer designing static structures," Sterling
|
||||
explained during a recent site visit. "We are engineering localized
|
||||
ecosystems."
|
||||
<p>
|
||||
Sterling's latest project in downtown Seattle is a testament to
|
||||
this new philosophy. "We are no longer designing static
|
||||
structures," Sterling explained during a recent site visit.
|
||||
"We are engineering localized ecosystems."
|
||||
</p>
|
||||
|
||||
<p className="max-w-[72ch] text-[29px] leading-[1.65] text-foreground/95">
|
||||
The building features a facade of responsive biomaterials that adjust their
|
||||
porosity based on humidity and temperature, significantly reducing the need
|
||||
for artificial climate control. Rainwater is not merely channeled away but
|
||||
captured, filtered through a series of integrated rooftop wetlands, and
|
||||
reused within the building's greywater system.
|
||||
<p>
|
||||
The building features a facade of responsive biomaterials that
|
||||
adjust their porosity based on humidity and temperature,
|
||||
significantly reducing the need for artificial climate control.
|
||||
Rainwater is not merely channeled away but captured, filtered
|
||||
through a series of integrated rooftop wetlands, and reused within
|
||||
the building's greywater system.
|
||||
</p>
|
||||
|
||||
<p className="max-w-[72ch] text-[29px] leading-[1.65] text-foreground/95">
|
||||
This shift requires more than just innovative materials; it demands a
|
||||
fundamental change in how we value space. The metric of success is no longer
|
||||
simply cost per square foot, but the building's overall environmental impact
|
||||
over its lifecycle.
|
||||
<p>
|
||||
This shift requires more than just innovative materials; it demands
|
||||
a fundamental change in how we value space. The metric of success is
|
||||
no longer simply cost per square foot, but the building's
|
||||
overall environmental impact over its lifecycle.
|
||||
</p>
|
||||
|
||||
<p className="max-w-[72ch] text-[25px] italic leading-[1.6] text-muted-foreground">
|
||||
Integration of solar passive design elements needs more elaboration here.
|
||||
Check with engineering team for specific stats.
|
||||
<p>
|
||||
Integration of solar passive design elements needs more elaboration
|
||||
here. Check with engineering team for specific stats.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
This shift requires more than just innovative materials; it demands
|
||||
a fundamental change in how we value space. Check with engineering
|
||||
team for specific stats.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside className="flex min-h-[980px] flex-col gap-6">
|
||||
<Card className="gap-0 py-0">
|
||||
<CardHeader className="border-b border-border/70 py-5">
|
||||
<aside className="flex flex-col gap-(--gap)">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Article Details</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-5 py-5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Issue
|
||||
</p>
|
||||
<p className="font-medium">Summer Issue 2024</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Author
|
||||
</p>
|
||||
<p className="font-medium">Elena Rostova</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Target Word Count
|
||||
</p>
|
||||
<Progress value={70}>
|
||||
<ProgressValue className="w-full text-right text-[10px] font-medium tracking-[0.12em] text-muted-foreground uppercase">
|
||||
1.4k / 2.0k
|
||||
</ProgressValue>
|
||||
</Progress>
|
||||
</div>
|
||||
<CardContent>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel>Issue</FieldLabel>
|
||||
<Select items={ISSUES} defaultValue="summer-2024">
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{ISSUES.map((issue) => (
|
||||
<SelectItem key={issue.value} value={issue.value}>
|
||||
{issue.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>Author</FieldLabel>
|
||||
<Input defaultValue="Elena Rostova" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="gap-0 py-0">
|
||||
<CardHeader className="border-b border-border/70 py-5">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Publication Flow</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-5 py-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Editorial Progress
|
||||
</p>
|
||||
<Progress value={45}>
|
||||
<ProgressValue className="w-full text-right text-[10px] font-medium tracking-[0.12em] text-muted-foreground uppercase">
|
||||
45%
|
||||
</ProgressValue>
|
||||
</Progress>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
Required Milestones
|
||||
</p>
|
||||
{MILESTONES.map((milestone) => (
|
||||
<label
|
||||
key={milestone.name}
|
||||
className="grid grid-cols-[18px_1fr] items-start gap-2.5"
|
||||
>
|
||||
<Checkbox checked={milestone.complete} />
|
||||
<span className="flex flex-col gap-0.5">
|
||||
<span className="text-sm text-foreground">{milestone.name}</span>
|
||||
<span className="text-[10px] tracking-[0.12em] text-muted-foreground uppercase">
|
||||
{milestone.note ?? ""}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<CardContent>
|
||||
<FieldGroup>
|
||||
<FieldSet>
|
||||
<FieldLegend>Required Milestones</FieldLegend>
|
||||
<Field>
|
||||
{MILESTONES.map((milestone) => (
|
||||
<Field key={milestone.name} orientation="horizontal">
|
||||
<Checkbox
|
||||
defaultChecked={milestone.complete}
|
||||
name={milestone.name}
|
||||
id={milestone.name}
|
||||
/>
|
||||
<FieldLabel htmlFor={milestone.name}>
|
||||
{milestone.name}
|
||||
</FieldLabel>
|
||||
</Field>
|
||||
))}
|
||||
</Field>
|
||||
</FieldSet>
|
||||
<Field>
|
||||
<FieldLabel>Add note for editor</FieldLabel>
|
||||
<Textarea placeholder="This article needs to be revised for clarity and accuracy." />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Word Count</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Progress value={70}>
|
||||
<ProgressLabel>1,402 / 2,000 words</ProgressLabel>
|
||||
<ProgressValue />
|
||||
</Progress>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<CardFooter className="mt-auto border border-border/70 bg-background py-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full text-[10px] font-semibold tracking-[0.16em]"
|
||||
>
|
||||
Add Note for Editor
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,42 +1,44 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ArrowLeftIcon } from "lucide-react"
|
||||
import { ArrowLeftIcon, ExternalLinkIcon } from "lucide-react"
|
||||
|
||||
import { Badge } from "@/styles/base-sera/ui/badge"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
} from "@/styles/base-sera/ui/breadcrumb"
|
||||
import { Button } from "@/styles/base-sera/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-sera/ui/button-group"
|
||||
|
||||
export function PreviewHeader() {
|
||||
return (
|
||||
<header className="container flex flex-col gap-6 py-8 md:py-10">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-auto w-fit px-0 text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase hover:bg-transparent hover:text-foreground"
|
||||
>
|
||||
<ArrowLeftIcon className="size-3.5" />
|
||||
Back to articles
|
||||
</Button>
|
||||
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<h1 className="font-heading text-4xl leading-none tracking-tight uppercase md:text-5xl">
|
||||
The Future of Sustainable Architecture
|
||||
</h1>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2 lg:justify-end">
|
||||
<div className="inline-flex h-9 items-center gap-2 border border-border/70 px-3 text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
<span className="size-1 rounded-full bg-foreground/80" />
|
||||
Autosaved 2 mins ago
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-9 px-4 text-[10px] font-semibold tracking-[0.16em]"
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
<Button size="sm" className="h-9 px-4 text-[10px] font-semibold tracking-[0.16em]">
|
||||
Submit Draft
|
||||
</Button>
|
||||
<header>
|
||||
<div className="container flex flex-col items-center justify-center gap-(--gap) py-(--gap) sm:flex-row sm:justify-between">
|
||||
<div className="flex flex-col gap-2 text-center sm:text-left">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#" className="flex items-center gap-1.5">
|
||||
<ArrowLeftIcon className="size-3.5" />
|
||||
Back to articles
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<h1 className="line-clamp-1 font-heading text-3xl tracking-wide uppercase md:text-3xl lg:text-4xl">
|
||||
EDIT ARTICLE
|
||||
</h1>
|
||||
</div>
|
||||
<ButtonGroup className="gap-2 md:gap-4">
|
||||
<Badge title="2 minutes ago">Autosaved</Badge>
|
||||
<ButtonGroup className="gap-2 md:gap-4">
|
||||
<Button variant="link">
|
||||
Preview
|
||||
<ExternalLinkIcon data-icon="inline-end" />
|
||||
</Button>
|
||||
<Button>Submit Draft</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
||||
|
||||
import { EditorWorkspace } from "./components/editor-workspace"
|
||||
import { PreviewHeader } from "./components/preview-header"
|
||||
|
||||
export function Preview03() {
|
||||
return (
|
||||
<div className="preview theme-taupe w-full flex-1 bg-muted font-sans ring-1 ring-foreground/5">
|
||||
<div className="preview theme-taupe @container/preview w-full flex-1 bg-muted pt-4 font-sans ring-1 ring-foreground/5 [--gap:--spacing(4)] sm:pt-0 md:[--gap:--spacing(6)] xl:[--gap:--spacing(8)] **:[.container]:px-(--gap)">
|
||||
<PreviewHeader />
|
||||
<div className="container pb-8">
|
||||
<Separator className="hidden sm:block" />
|
||||
<div className="container py-(--gap)">
|
||||
<EditorWorkspace />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { AlertCircleIcon, CheckIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Badge } from "@/styles/base-sera/ui/badge"
|
||||
import { Button } from "@/styles/base-sera/ui/button"
|
||||
import {
|
||||
Progress,
|
||||
ProgressTrack,
|
||||
ProgressValue,
|
||||
} from "@/styles/base-sera/ui/progress"
|
||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
||||
|
||||
type IssueStatus = "active" | "draft" | "archived"
|
||||
type IssueHealth = "overdue" | "on-track" | "completed"
|
||||
|
||||
type PublicationIssue = {
|
||||
name: string
|
||||
dueLabel: string
|
||||
status: IssueStatus
|
||||
progress: number
|
||||
health: IssueHealth
|
||||
healthLabel: string
|
||||
primaryAction: string
|
||||
secondaryAction?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const PUBLICATION_ISSUES: PublicationIssue[] = [
|
||||
{
|
||||
name: "Summer Issue",
|
||||
dueLabel: "Due Aug 15, 2024",
|
||||
status: "active",
|
||||
progress: 45,
|
||||
health: "overdue",
|
||||
healthLabel: "1 block overdue",
|
||||
primaryAction: "View Board",
|
||||
secondaryAction: "Manage",
|
||||
},
|
||||
{
|
||||
name: "Autumn Issue",
|
||||
dueLabel: "Due Nov 01, 2024",
|
||||
status: "draft",
|
||||
progress: 10,
|
||||
health: "on-track",
|
||||
healthLabel: "On track",
|
||||
primaryAction: "Resume Draft",
|
||||
secondaryAction: "Manage",
|
||||
},
|
||||
{
|
||||
name: "Spring Issue",
|
||||
dueLabel: "Published May 10, 2024",
|
||||
status: "archived",
|
||||
progress: 100,
|
||||
health: "completed",
|
||||
healthLabel: "Completed",
|
||||
primaryAction: "View Archive",
|
||||
disabled: true,
|
||||
},
|
||||
]
|
||||
|
||||
function getStatusVariant(
|
||||
status: IssueStatus
|
||||
): React.ComponentProps<typeof Badge>["variant"] {
|
||||
if (status === "active") {
|
||||
return "outline"
|
||||
}
|
||||
|
||||
if (status === "draft") {
|
||||
return "secondary"
|
||||
}
|
||||
|
||||
return "ghost"
|
||||
}
|
||||
|
||||
function getHealthStyles(health: IssueHealth): {
|
||||
icon: React.JSX.Element
|
||||
className: string
|
||||
} {
|
||||
if (health === "overdue") {
|
||||
return {
|
||||
icon: <AlertCircleIcon className="size-3.5" />,
|
||||
className: "text-foreground",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
icon: <CheckIcon className="size-3.5" />,
|
||||
className: "text-muted-foreground",
|
||||
}
|
||||
}
|
||||
|
||||
export function PublicationIssues() {
|
||||
return (
|
||||
<section className="flex flex-col gap-6 border border-border/70 bg-background p-6 md:p-8">
|
||||
<header className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h2 className="font-heading text-5xl leading-none tracking-tight uppercase">
|
||||
Publication Issues
|
||||
</h2>
|
||||
<p className="text-[11px] font-semibold tracking-[0.22em] text-muted-foreground uppercase">
|
||||
Editorial Pipeline Management
|
||||
</p>
|
||||
</div>
|
||||
<Button className="h-10 w-full px-5 text-[10px] font-semibold tracking-[0.2em] lg:w-auto">
|
||||
Create New Issue
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
{PUBLICATION_ISSUES.map((issue) => {
|
||||
const statusVariant = getStatusVariant(issue.status)
|
||||
const healthStyles = getHealthStyles(issue.health)
|
||||
|
||||
return (
|
||||
<article
|
||||
key={issue.name}
|
||||
className="grid grid-cols-1 gap-5 border border-border/60 bg-card px-5 py-6 md:grid-cols-[minmax(0,1fr)_minmax(260px,1fr)_auto] md:items-center md:gap-6 md:px-7"
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-2 md:grid-cols-[minmax(0,1fr)_auto] md:items-start md:gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3 className="font-heading text-4xl leading-none tracking-tight">
|
||||
{issue.name}
|
||||
</h3>
|
||||
<p className="text-[11px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
|
||||
{issue.dueLabel}
|
||||
</p>
|
||||
</div>
|
||||
<Badge
|
||||
variant={statusVariant}
|
||||
className="w-fit px-3 py-1 text-[10px] font-semibold tracking-[0.18em] uppercase"
|
||||
>
|
||||
{issue.status}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 border-l-0 border-border/60 md:border-l md:pl-6">
|
||||
<Progress value={issue.progress} className="gap-2">
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<span className="text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase">
|
||||
Editorial Progress
|
||||
</span>
|
||||
<ProgressValue className="m-0 text-[11px] font-semibold text-foreground">
|
||||
{(value) => `${value}%`}
|
||||
</ProgressValue>
|
||||
</div>
|
||||
<ProgressTrack className="bg-border/90" />
|
||||
</Progress>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-[12px] font-medium",
|
||||
healthStyles.className
|
||||
)}
|
||||
>
|
||||
{healthStyles.icon}
|
||||
<span>{issue.healthLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-start gap-2 border-l-0 border-border/60 md:justify-end md:border-l md:pl-6">
|
||||
{issue.secondaryAction ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
disabled={issue.disabled}
|
||||
className="h-9 px-4 text-[10px] font-semibold tracking-[0.16em]"
|
||||
>
|
||||
{issue.secondaryAction}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="button"
|
||||
variant={issue.status === "active" ? "default" : "outline"}
|
||||
disabled={issue.disabled}
|
||||
className="h-9 px-4 text-[10px] font-semibold tracking-[0.16em]"
|
||||
>
|
||||
{issue.primaryAction}
|
||||
</Button>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { PublicationIssues } from "./components/publication-issues"
|
||||
|
||||
export function Preview04() {
|
||||
return (
|
||||
<div className="preview theme-taupe w-full flex-1 bg-muted font-sans ring-1 ring-foreground/5">
|
||||
<div className="container py-8">
|
||||
<PublicationIssues />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
CalendarIcon,
|
||||
CheckIcon,
|
||||
ChevronRightIcon,
|
||||
CircleAlertIcon,
|
||||
FileTextIcon,
|
||||
FilterIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
} from "@/styles/base-sera/ui/avatar"
|
||||
import { Badge } from "@/styles/base-sera/ui/badge"
|
||||
import { Button } from "@/styles/base-sera/ui/button"
|
||||
import { Progress, ProgressTrack, ProgressValue } from "@/styles/base-sera/ui/progress"
|
||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
||||
|
||||
type PipelineStatus = "draft" | "active" | "archived"
|
||||
type AlertTone = "neutral" | "warning"
|
||||
type ColumnKey = "planning" | "in-progress" | "published"
|
||||
|
||||
type IssueCard = {
|
||||
title: string
|
||||
volume: string
|
||||
status: PipelineStatus
|
||||
dueLabel: string
|
||||
articleCount: number
|
||||
progress: number
|
||||
assignees: string[]
|
||||
alert?: {
|
||||
title: string
|
||||
message: string
|
||||
tone: AlertTone
|
||||
}
|
||||
}
|
||||
|
||||
type PipelineColumn = {
|
||||
key: ColumnKey
|
||||
title: string
|
||||
count: number
|
||||
cards: IssueCard[]
|
||||
}
|
||||
|
||||
const PIPELINE_COLUMNS: PipelineColumn[] = [
|
||||
{
|
||||
key: "planning",
|
||||
title: "Planning",
|
||||
count: 1,
|
||||
cards: [
|
||||
{
|
||||
title: "Winter Issue",
|
||||
volume: "Vol 4. / Issue 12",
|
||||
status: "draft",
|
||||
dueLabel: "Due Nov 15",
|
||||
articleCount: 12,
|
||||
progress: 10,
|
||||
assignees: ["MS", "LI"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "in-progress",
|
||||
title: "In Progress",
|
||||
count: 2,
|
||||
cards: [
|
||||
{
|
||||
title: "Fall Issue",
|
||||
volume: "Vol 4. / Issue 11",
|
||||
status: "active",
|
||||
dueLabel: "Due Oct 01",
|
||||
articleCount: 14,
|
||||
progress: 85,
|
||||
assignees: ["AS", "EP"],
|
||||
alert: {
|
||||
title: "Layout Review",
|
||||
message: "Awaiting final sign-off from art director.",
|
||||
tone: "neutral",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Summer Issue",
|
||||
volume: "Vol 4. / Issue 10",
|
||||
status: "active",
|
||||
dueLabel: "Due Jul 15",
|
||||
articleCount: 15,
|
||||
progress: 45,
|
||||
assignees: ["AS", "KL"],
|
||||
alert: {
|
||||
title: "1 block overdue",
|
||||
message: "Photography needs final sign-off.",
|
||||
tone: "warning",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "published",
|
||||
title: "Published",
|
||||
count: 1,
|
||||
cards: [
|
||||
{
|
||||
title: "Spring Issue",
|
||||
volume: "Vol 4. / Issue 09",
|
||||
status: "archived",
|
||||
dueLabel: "Pub Apr 15",
|
||||
articleCount: 12,
|
||||
progress: 100,
|
||||
assignees: ["AL", "JI"],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function getBadgeVariant(
|
||||
status: PipelineStatus
|
||||
): React.ComponentProps<typeof Badge>["variant"] {
|
||||
if (status === "active") {
|
||||
return "outline"
|
||||
}
|
||||
|
||||
if (status === "draft") {
|
||||
return "secondary"
|
||||
}
|
||||
|
||||
return "ghost"
|
||||
}
|
||||
|
||||
function getAlertStyles(tone: AlertTone): { className: string; icon: React.JSX.Element } {
|
||||
if (tone === "warning") {
|
||||
return {
|
||||
className: "border-l border-l-destructive/80 bg-muted/30",
|
||||
icon: <CircleAlertIcon className="size-3.5 text-destructive" />,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
className: "border-l border-l-foreground/80 bg-muted/30",
|
||||
icon: <CircleAlertIcon className="size-3.5 text-foreground" />,
|
||||
}
|
||||
}
|
||||
|
||||
export function EditorialPipelineBoard() {
|
||||
return (
|
||||
<section className="flex flex-col gap-6 border border-border/70 bg-background p-6 md:p-8">
|
||||
<header className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h2 className="font-heading text-5xl leading-none tracking-tight uppercase">
|
||||
Publication Issues
|
||||
</h2>
|
||||
<p className="text-[11px] font-semibold tracking-[0.22em] text-muted-foreground uppercase">
|
||||
Editorial Pipeline Board
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon-sm">
|
||||
<FilterIcon className="size-4" />
|
||||
</Button>
|
||||
<Button size="sm" className="px-4 text-[10px] font-semibold tracking-[0.18em]">
|
||||
New Issue
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 xl:grid-cols-3">
|
||||
{PIPELINE_COLUMNS.map((column) => (
|
||||
<section key={column.key} className="flex flex-col gap-3">
|
||||
<div className="grid grid-cols-[1fr_auto] items-end gap-3 border-b border-border/80 pb-2">
|
||||
<h3 className="text-[10px] font-semibold tracking-[0.2em] text-foreground uppercase">
|
||||
{column.title}
|
||||
</h3>
|
||||
<span className="inline-flex h-4 min-w-4 items-center justify-center bg-muted px-1 text-[9px] font-semibold text-muted-foreground">
|
||||
{column.count}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
{column.cards.map((card) => {
|
||||
const statusVariant = getBadgeVariant(card.status)
|
||||
|
||||
return (
|
||||
<article
|
||||
key={card.title}
|
||||
className="flex flex-col gap-4 border border-border/60 bg-card p-4"
|
||||
>
|
||||
<div className="grid grid-cols-[1fr_auto] items-start gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="font-heading text-4xl leading-none tracking-tight">
|
||||
{card.title}
|
||||
</h4>
|
||||
<p className="text-[10px] font-semibold tracking-[0.16em] text-muted-foreground uppercase">
|
||||
{card.volume}
|
||||
</p>
|
||||
</div>
|
||||
<Badge
|
||||
variant={statusVariant}
|
||||
className="px-2 py-0.5 text-[9px] tracking-[0.18em] uppercase"
|
||||
>
|
||||
{card.status}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{card.alert ? (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-1 px-2.5 py-2",
|
||||
getAlertStyles(card.alert.tone).className
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-semibold tracking-[0.14em] uppercase">
|
||||
{getAlertStyles(card.alert.tone).icon}
|
||||
<span>{card.alert.title}</span>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground">{card.alert.message}</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid grid-cols-2 items-center gap-3 text-[10px] text-muted-foreground">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CalendarIcon className="size-3.5" />
|
||||
<span>{card.dueLabel}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-1.5">
|
||||
<FileTextIcon className="size-3.5" />
|
||||
<span>{card.articleCount} Articles</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Progress value={card.progress} className="gap-1.5">
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<span className="text-[10px] font-semibold tracking-[0.18em] text-foreground uppercase">
|
||||
Editorial Progress
|
||||
</span>
|
||||
<ProgressValue className="m-0 text-[10px] font-semibold text-foreground">
|
||||
{(value) => `${value}%`}
|
||||
</ProgressValue>
|
||||
</div>
|
||||
<ProgressTrack className="bg-border/90" />
|
||||
</Progress>
|
||||
|
||||
<footer className="grid grid-cols-[1fr_auto] items-center gap-3">
|
||||
<AvatarGroup className="space-x-0">
|
||||
{card.assignees.map((assignee) => (
|
||||
<Avatar
|
||||
key={assignee}
|
||||
size="sm"
|
||||
className="ring-0 after:border-border/70 [&+[data-slot=avatar]]:-ml-2"
|
||||
>
|
||||
<AvatarFallback className="bg-muted text-[9px] font-semibold tracking-wide text-muted-foreground">
|
||||
{assignee}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
))}
|
||||
</AvatarGroup>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="h-auto px-0 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground hover:bg-transparent hover:text-foreground"
|
||||
>
|
||||
View <ChevronRightIcon className="size-3.5" />
|
||||
</Button>
|
||||
</footer>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { EditorialPipelineBoard } from "./components/editorial-pipeline-board"
|
||||
|
||||
export function Preview05() {
|
||||
return (
|
||||
<div className="preview theme-taupe w-full flex-1 bg-muted font-sans ring-1 ring-foreground/5">
|
||||
<div className="container py-8">
|
||||
<EditorialPipelineBoard />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user