Compare commits
1 Commits
shadcn@4.7
...
shadcn/pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecbace99d9 |
@@ -9,6 +9,5 @@
|
|||||||
"WebFetch(domain:github.com)"
|
"WebFetch(domain:github.com)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
},
|
}
|
||||||
"outputStyle": "Explanatory"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "shadcn",
|
|
||||||
"displayName": "shadcn/ui",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "UI component and design system framework. Search registries, install components as source code, and audit your project.",
|
|
||||||
"author": {
|
|
||||||
"name": "shadcn"
|
|
||||||
},
|
|
||||||
"homepage": "https://ui.shadcn.com",
|
|
||||||
"repository": "https://github.com/shadcn-ui/ui",
|
|
||||||
"license": "MIT",
|
|
||||||
"logo": "skills/shadcn/assets/shadcn.png",
|
|
||||||
"keywords": [
|
|
||||||
"shadcn",
|
|
||||||
"shadcn-ui",
|
|
||||||
"ui",
|
|
||||||
"components",
|
|
||||||
"tailwind",
|
|
||||||
"tailwindcss",
|
|
||||||
"radix",
|
|
||||||
"react",
|
|
||||||
"design-system",
|
|
||||||
"registry",
|
|
||||||
"mcp"
|
|
||||||
],
|
|
||||||
"category": "developer-tools",
|
|
||||||
"tags": [
|
|
||||||
"ui",
|
|
||||||
"components",
|
|
||||||
"design-system",
|
|
||||||
"react",
|
|
||||||
"tailwind"
|
|
||||||
],
|
|
||||||
"skills": "./skills/",
|
|
||||||
"mcpServers": {
|
|
||||||
"shadcn": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["shadcn@latest", "mcp"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9
.github/workflows/prerelease-comment.yml
vendored
@@ -3,7 +3,7 @@ name: Write Beta Release comment
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: ["Release"]
|
workflows: ["Release - Beta"]
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
|
||||||
@@ -11,13 +11,12 @@ jobs:
|
|||||||
comment:
|
comment:
|
||||||
if: |
|
if: |
|
||||||
github.repository_owner == 'shadcn-ui' &&
|
github.repository_owner == 'shadcn-ui' &&
|
||||||
github.event.workflow_run.event == 'pull_request' &&
|
${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
github.event.workflow_run.conclusion == 'success'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Write comment to the PR
|
name: Write comment to the PR
|
||||||
steps:
|
steps:
|
||||||
- name: "Comment on PR"
|
- name: "Comment on PR"
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
@@ -54,7 +53,7 @@ jobs:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- name: "Remove the autorelease label once published"
|
- name: "Remove the autorelease label once published"
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
64
.github/workflows/prerelease.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Adapted from create-t3-app.
|
||||||
|
|
||||||
|
name: Release - Beta
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prerelease:
|
||||||
|
if: |
|
||||||
|
github.repository_owner == 'shadcn-ui' &&
|
||||||
|
contains(github.event.pull_request.labels.*.name, '🚀 autorelease')
|
||||||
|
name: Build & Publish a beta release to NPM
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: Preview
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Use PNPM
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9.0.6
|
||||||
|
|
||||||
|
- name: Use Node.js 20
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Update npm for OIDC support
|
||||||
|
run: npm install -g npm@latest
|
||||||
|
|
||||||
|
- name: Install NPM Dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Modify package.json version
|
||||||
|
run: node .github/version-script-beta.js
|
||||||
|
|
||||||
|
- name: Publish Beta to NPM
|
||||||
|
run: pnpm pub:beta
|
||||||
|
|
||||||
|
- name: get-npm-version
|
||||||
|
id: package-version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@main
|
||||||
|
with:
|
||||||
|
path: packages/shadcn
|
||||||
|
|
||||||
|
- name: Upload packaged artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||||
|
path: packages/shadcn/dist/index.js
|
||||||
82
.github/workflows/release.yml
vendored
@@ -2,81 +2,24 @@
|
|||||||
|
|
||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
run-name: ${{ github.event_name == 'pull_request' && format('Release Beta - PR {0}', github.event.number) || 'Release Stable' }}
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
|
||||||
types: [labeled]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prerelease:
|
|
||||||
if: ${{ github.event_name == 'pull_request' && github.repository_owner == 'shadcn-ui' && contains(github.event.pull_request.labels.*.name, '🚀 autorelease') }}
|
|
||||||
name: Publish Beta to NPM
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: Preview
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Use PNPM
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 9.0.6
|
|
||||||
|
|
||||||
- name: Use Node.js 20
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
registry-url: "https://registry.npmjs.org"
|
|
||||||
cache: "pnpm"
|
|
||||||
|
|
||||||
- name: Update npm for OIDC support
|
|
||||||
run: npm install -g npm@latest
|
|
||||||
|
|
||||||
- name: Install NPM Dependencies
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Modify package.json version
|
|
||||||
run: node .github/version-script-beta.js
|
|
||||||
|
|
||||||
- name: Publish Beta to NPM
|
|
||||||
run: pnpm pub:beta
|
|
||||||
|
|
||||||
- name: get-npm-version
|
|
||||||
id: package-version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@main
|
|
||||||
with:
|
|
||||||
path: packages/shadcn
|
|
||||||
|
|
||||||
- name: Upload packaged artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
|
||||||
path: packages/shadcn/dist/index.js
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
if: ${{ github.event_name == 'push' && github.repository_owner == 'shadcn-ui' }}
|
if: ${{ github.repository_owner == 'shadcn-ui' }}
|
||||||
name: Create Version PR or Publish Stable Release
|
name: Create a PR for release workflow
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -104,19 +47,10 @@ jobs:
|
|||||||
- name: Build the package
|
- name: Build the package
|
||||||
run: pnpm shadcn:build
|
run: pnpm shadcn:build
|
||||||
|
|
||||||
- name: Import GPG key
|
|
||||||
uses: crazy-max/ghaction-import-gpg@v6
|
|
||||||
with:
|
|
||||||
gpg_private_key: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }}
|
|
||||||
git_user_signingkey: true
|
|
||||||
git_commit_gpgsign: true
|
|
||||||
git_tag_gpgsign: true
|
|
||||||
|
|
||||||
- name: Create Version PR or Publish to NPM
|
- name: Create Version PR or Publish to NPM
|
||||||
id: changesets
|
id: changesets
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@v1
|
||||||
with:
|
with:
|
||||||
setupGitUser: false
|
|
||||||
commit: "chore(release): version packages"
|
commit: "chore(release): version packages"
|
||||||
title: "chore(release): version packages"
|
title: "chore(release): version packages"
|
||||||
version: node .github/changeset-version.js
|
version: node .github/changeset-version.js
|
||||||
|
|||||||
75
.github/workflows/signed-commits.yml
vendored
@@ -1,75 +0,0 @@
|
|||||||
name: Signed commits
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- reopened
|
|
||||||
- synchronize
|
|
||||||
- ready_for_review
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
signed-commits:
|
|
||||||
if: github.repository_owner == 'shadcn-ui'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Signed commits
|
|
||||||
steps:
|
|
||||||
- name: Check PR commits
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
const body = "Can you sign the commits please? See https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits. Thank you."
|
|
||||||
|
|
||||||
const { owner, repo } = context.repo
|
|
||||||
const pullNumber = context.payload.pull_request.number
|
|
||||||
|
|
||||||
const commits = await github.paginate(github.rest.pulls.listCommits, {
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pullNumber,
|
|
||||||
per_page: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
const unsignedCommits = commits.filter((commit) => {
|
|
||||||
return commit.commit.verification?.reason === "unsigned"
|
|
||||||
})
|
|
||||||
|
|
||||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pullNumber,
|
|
||||||
per_page: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
const existingComments = comments.filter((comment) => {
|
|
||||||
return comment.user.type === "Bot" && comment.body.trim() === body
|
|
||||||
})
|
|
||||||
|
|
||||||
if (unsignedCommits.length > 0) {
|
|
||||||
core.info(`Found ${unsignedCommits.length} unsigned commits.`)
|
|
||||||
|
|
||||||
if (existingComments.length === 0) {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pullNumber,
|
|
||||||
body,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
core.info("All commits are signed.")
|
|
||||||
|
|
||||||
for (const comment of existingComments) {
|
|
||||||
await github.rest.issues.deleteComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: comment.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
3
.github/workflows/test.yml
vendored
@@ -39,9 +39,6 @@ jobs:
|
|||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
- name: Install Bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ import Image from "next/image"
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
import { Announcement } from "@/components/announcement"
|
import { Announcement } from "@/components/announcement"
|
||||||
|
import { ExamplesNav } from "@/components/examples-nav"
|
||||||
import {
|
import {
|
||||||
PageActions,
|
PageActions,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
PageHeaderDescription,
|
PageHeaderDescription,
|
||||||
PageHeaderHeading,
|
PageHeaderHeading,
|
||||||
} from "@/components/page-header"
|
} from "@/components/page-header"
|
||||||
|
import { PageNav } from "@/components/page-nav"
|
||||||
|
import { ThemeSelector } from "@/components/theme-selector"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
|
|
||||||
import { RootComponents } from "./components"
|
import { RootComponents } from "./components"
|
||||||
|
|||||||
@@ -1,216 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { ChevronLeftIcon, ChevronRightIcon, SearchIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
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,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/styles/base-sera/ui/table"
|
|
||||||
|
|
||||||
const ARTICLE_ROWS = [
|
|
||||||
{
|
|
||||||
title: "The Future of Sustainable Architecture",
|
|
||||||
wordProgress: "1.4k / 2.6k words",
|
|
||||||
author: "Elena Rostova",
|
|
||||||
issue: "Summer 2024",
|
|
||||||
status: "in-revision",
|
|
||||||
statusLabel: "In revision",
|
|
||||||
progress: 45,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Brutalism's Second Act",
|
|
||||||
wordProgress: "2.1k / 2.5k words",
|
|
||||||
author: "Marcus Chen",
|
|
||||||
issue: "Summer 2024",
|
|
||||||
status: "final-edit",
|
|
||||||
statusLabel: "Final edit",
|
|
||||||
progress: 90,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "The Typography of Public Spaces",
|
|
||||||
wordProgress: "0.5k / 1.5k words",
|
|
||||||
author: "Sarah Jenkins",
|
|
||||||
issue: "Autumn 2024",
|
|
||||||
status: "drafting",
|
|
||||||
statusLabel: "Drafting",
|
|
||||||
progress: 20,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Rethinking Urban Canopies",
|
|
||||||
wordProgress: "1.8k / 1.8k words",
|
|
||||||
author: "David O'Connor",
|
|
||||||
issue: "Summer 2024",
|
|
||||||
status: "published",
|
|
||||||
statusLabel: "Published",
|
|
||||||
progress: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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
|
|
||||||
|
|
||||||
const STATUS_BADGE_VARIANT = {
|
|
||||||
"in-revision": "outline",
|
|
||||||
"final-edit": "default",
|
|
||||||
drafting: "ghost",
|
|
||||||
published: "secondary",
|
|
||||||
} as const
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<InputGroup>
|
|
||||||
<InputGroupAddon>
|
|
||||||
<SearchIcon />
|
|
||||||
</InputGroupAddon>
|
|
||||||
<InputGroupInput type="search" placeholder="Search articles..." />
|
|
||||||
</InputGroup>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Table>
|
|
||||||
<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) => (
|
|
||||||
<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(
|
|
||||||
"size-1.5 rounded-full",
|
|
||||||
STATUS_DOT_CLASSNAME[row.status]
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{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>
|
|
||||||
</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,47 +0,0 @@
|
|||||||
import { ArrowLeftIcon, PlusIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
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>
|
|
||||||
<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>
|
|
||||||
<div>
|
|
||||||
<ButtonGroup className="gap-2 sm:ml-auto md:gap-4">
|
|
||||||
<Button>
|
|
||||||
<PlusIcon data-icon="inline-start" />
|
|
||||||
New Article
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
|
||||||
|
|
||||||
import { ArticleDirectory as ArticleDirectoryList } from "./components/article-directory"
|
|
||||||
import { PreviewHeader } from "./components/preview-header"
|
|
||||||
|
|
||||||
export function ArticleDirectory() {
|
|
||||||
return (
|
|
||||||
<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)] 2xl:py-8 **:[.container]:px-(--gap)">
|
|
||||||
<PreviewHeader />
|
|
||||||
<Separator className="hidden sm:block" />
|
|
||||||
<div className="container py-(--gap)">
|
|
||||||
<ArticleDirectoryList />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { MoveRightIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { Button } from "@/styles/base-sera/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/styles/base-sera/ui/card"
|
|
||||||
import {
|
|
||||||
Progress,
|
|
||||||
ProgressLabel,
|
|
||||||
ProgressValue,
|
|
||||||
} from "@/styles/base-sera/ui/progress"
|
|
||||||
|
|
||||||
const DEMOGRAPHIC_DATA = [
|
|
||||||
{ age: "18 - 24", percentage: 22 },
|
|
||||||
{ age: "25 - 34", percentage: 64 },
|
|
||||||
{ age: "35 - 44", percentage: 12 },
|
|
||||||
{ age: "45+", percentage: 5 },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function Demographics({ ...props }: React.ComponentProps<typeof Card>) {
|
|
||||||
return (
|
|
||||||
<Card {...props}>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-2xl">Demographics</CardTitle>
|
|
||||||
<CardDescription>Reader Profile</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex flex-col gap-10">
|
|
||||||
{DEMOGRAPHIC_DATA.map((item) => (
|
|
||||||
<Progress
|
|
||||||
key={item.age}
|
|
||||||
value={item.percentage}
|
|
||||||
aria-label={item.age}
|
|
||||||
>
|
|
||||||
<ProgressLabel>{item.age}</ProgressLabel>
|
|
||||||
<ProgressValue>
|
|
||||||
{(formattedValue) => `${formattedValue}`}
|
|
||||||
</ProgressValue>
|
|
||||||
</Progress>
|
|
||||||
))}
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter>
|
|
||||||
<Button variant="link" className="w-full">
|
|
||||||
View all source <MoveRightIcon data-icon="inline-end" />
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import { TrendingDownIcon, TrendingUpIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/styles/base-sera/ui/card"
|
|
||||||
|
|
||||||
type Metric = {
|
|
||||||
label: string
|
|
||||||
value: string
|
|
||||||
comparison: string
|
|
||||||
change: string
|
|
||||||
trend: "up" | "down"
|
|
||||||
}
|
|
||||||
|
|
||||||
const METRIC_CARDS: Metric[] = [
|
|
||||||
{
|
|
||||||
label: "Total visitors",
|
|
||||||
value: "248.5k",
|
|
||||||
comparison: "12.4%",
|
|
||||||
change: "vs last period",
|
|
||||||
trend: "up",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Unique readers",
|
|
||||||
value: "182.1k",
|
|
||||||
comparison: "8.7%",
|
|
||||||
change: "vs last period",
|
|
||||||
trend: "up",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Avg. time on page",
|
|
||||||
value: "3m 42s",
|
|
||||||
comparison: "1.2%",
|
|
||||||
change: "vs last period",
|
|
||||||
trend: "down",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Bounce rate",
|
|
||||||
value: "42.8%",
|
|
||||||
comparison: "3.5%",
|
|
||||||
change: "vs last period",
|
|
||||||
trend: "down",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function MetricsGrid() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{METRIC_CARDS.map((metric) => (
|
|
||||||
<MetricCard
|
|
||||||
key={metric.label}
|
|
||||||
metric={metric}
|
|
||||||
className="col-span-full md:col-span-6 lg:col-span-3"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function MetricCard({
|
|
||||||
metric,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
metric: Metric
|
|
||||||
className: string
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Card className={cn("gap-0", className)}>
|
|
||||||
<CardContent className="flex flex-col gap-2">
|
|
||||||
<CardDescription className="text-xs uppercase">
|
|
||||||
{metric.label}
|
|
||||||
</CardDescription>
|
|
||||||
<CardTitle className="text-5xl tracking-tight lowercase">
|
|
||||||
{metric.value}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{metric.trend === "up" ? (
|
|
||||||
<TrendingUpIcon className="inline-block size-2.5 text-muted-foreground" />
|
|
||||||
) : (
|
|
||||||
<TrendingDownIcon className="inline-block size-2.5 text-muted-foreground" />
|
|
||||||
)}{" "}
|
|
||||||
<span className="text-foreground">{metric.comparison}</span>{" "}
|
|
||||||
<span>{metric.change}</span>
|
|
||||||
</CardDescription>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { ChevronDownIcon, DownloadIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { Button } from "@/styles/base-sera/ui/button"
|
|
||||||
import { ButtonGroup } from "@/styles/base-sera/ui/button-group"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/styles/base-sera/ui/dropdown-menu"
|
|
||||||
|
|
||||||
const EXPORT_DATE_OPTIONS = [
|
|
||||||
{
|
|
||||||
label: "Last 7 days",
|
|
||||||
value: "last-7-days",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Last 30 days",
|
|
||||||
value: "last-30-days",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "This month",
|
|
||||||
value: "this-month",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Last month",
|
|
||||||
value: "last-month",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function PreviewHeader() {
|
|
||||||
const [selectedDateRange, setSelectedDateRange] =
|
|
||||||
React.useState("last-30-days")
|
|
||||||
|
|
||||||
const selectedDateRangeLabel = React.useMemo(() => {
|
|
||||||
const selectedOption = EXPORT_DATE_OPTIONS.find(
|
|
||||||
(option) => option.value === selectedDateRange
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!selectedOption) {
|
|
||||||
return "Last 30 days"
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedOption.label
|
|
||||||
}, [selectedDateRange])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<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">
|
|
||||||
<h1 className="line-clamp-1 font-heading text-3xl tracking-wide uppercase md:text-3xl lg:text-4xl">
|
|
||||||
Audience Analytics
|
|
||||||
</h1>
|
|
||||||
<div className="line-clamp-1 text-sm font-medium tracking-wider text-muted-foreground uppercase">
|
|
||||||
Editorial Performance Dashboard
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ButtonGroup className="gap-2 sm:ml-auto md:gap-4">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="bg-background hover:bg-background/80 data-popup-open:bg-background"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{selectedDateRangeLabel}{" "}
|
|
||||||
<ChevronDownIcon data-icon="inline-end" />
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={selectedDateRange}
|
|
||||||
onValueChange={setSelectedDateRange}
|
|
||||||
>
|
|
||||||
{EXPORT_DATE_OPTIONS.map((option) => (
|
|
||||||
<DropdownMenuRadioItem
|
|
||||||
key={option.value}
|
|
||||||
value={option.value}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<Button>
|
|
||||||
<DownloadIcon data-icon="inline-start" />
|
|
||||||
<span className="lg:hidden">Export</span>
|
|
||||||
<span className="hidden lg:inline">Export Report</span>
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { ArrowDownIcon, MoreHorizontalIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { Button } from "@/styles/base-sera/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/styles/base-sera/ui/card"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/styles/base-sera/ui/dropdown-menu"
|
|
||||||
import { Spinner } from "@/styles/base-sera/ui/spinner"
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/styles/base-sera/ui/table"
|
|
||||||
import {
|
|
||||||
ToggleGroup,
|
|
||||||
ToggleGroupItem,
|
|
||||||
} from "@/styles/base-sera/ui/toggle-group"
|
|
||||||
|
|
||||||
type EditorialMetric = "views" | "time" | "shares"
|
|
||||||
|
|
||||||
type EditorialRow = {
|
|
||||||
rank: number
|
|
||||||
title: string
|
|
||||||
author: string
|
|
||||||
published: string
|
|
||||||
pageviews: string
|
|
||||||
avgTime: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const METRIC_LABEL: Record<EditorialMetric, string> = {
|
|
||||||
views: "VIEWS",
|
|
||||||
time: "TIME",
|
|
||||||
shares: "SHARES",
|
|
||||||
}
|
|
||||||
|
|
||||||
const EDITORIAL_ROWS: EditorialRow[] = [
|
|
||||||
{
|
|
||||||
rank: 1,
|
|
||||||
title: "The New Vanguard of Minimalist Architecture",
|
|
||||||
author: "Elena Rostova",
|
|
||||||
published: "Oct 12",
|
|
||||||
pageviews: "45.2k",
|
|
||||||
avgTime: "04:15",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 2,
|
|
||||||
title: "Autumn Sartorial Code: Deconstructed Classics",
|
|
||||||
author: "Julian Vance",
|
|
||||||
published: "Oct 05",
|
|
||||||
pageviews: "38.9k",
|
|
||||||
avgTime: "03:42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 3,
|
|
||||||
title: "Interview: Director Sofia Coppola on The Aesthetics of Isolation",
|
|
||||||
author: "Marcus Trent",
|
|
||||||
published: "Sep 28",
|
|
||||||
pageviews: "31.4k",
|
|
||||||
avgTime: "06:20",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 4,
|
|
||||||
title: "Sourcing Ceramics from Kyoto's Oldest Kilns",
|
|
||||||
author: "Sarah Lin",
|
|
||||||
published: "Oct 18",
|
|
||||||
pageviews: "22.1k",
|
|
||||||
avgTime: "02:55",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 5,
|
|
||||||
title: "Field Notes from Copenhagen Design Week",
|
|
||||||
author: "Noah Bennett",
|
|
||||||
published: "Oct 21",
|
|
||||||
pageviews: "19.7k",
|
|
||||||
avgTime: "03:18",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 6,
|
|
||||||
title: "A Studio Visit with Milan's Most Elusive Lighting Designer",
|
|
||||||
author: "Claire Duval",
|
|
||||||
published: "Oct 09",
|
|
||||||
pageviews: "17.4k",
|
|
||||||
avgTime: "04:02",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 7,
|
|
||||||
title: "Collecting the New Avant-Garde in Contemporary Furniture",
|
|
||||||
author: "Tommy Rhodes",
|
|
||||||
published: "Sep 30",
|
|
||||||
pageviews: "15.9k",
|
|
||||||
avgTime: "03:36",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 8,
|
|
||||||
title: "Inside Lisbon's Quiet Culinary Renaissance",
|
|
||||||
author: "Amara Iqbal",
|
|
||||||
published: "Oct 14",
|
|
||||||
pageviews: "14.2k",
|
|
||||||
avgTime: "05:08",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 9,
|
|
||||||
title: "Why Slow Interiors Are Defining the Next Luxury Wave",
|
|
||||||
author: "Henry Vale",
|
|
||||||
published: "Oct 03",
|
|
||||||
pageviews: "12.7k",
|
|
||||||
avgTime: "03:11",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rank: 10,
|
|
||||||
title: "The Return of Print: Independent Magazine Covers to Watch",
|
|
||||||
author: "Mina Okafor",
|
|
||||||
published: "Sep 26",
|
|
||||||
pageviews: "11.3k",
|
|
||||||
avgTime: "02:49",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
type TopEditorialProps = React.ComponentProps<typeof Card> & {
|
|
||||||
selectedMetric?: EditorialMetric
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TopEditorial({
|
|
||||||
selectedMetric = "views",
|
|
||||||
...props
|
|
||||||
}: TopEditorialProps) {
|
|
||||||
const [visibleCount, setVisibleCount] = React.useState(5)
|
|
||||||
const [isLoadingMore, setIsLoadingMore] = React.useState(false)
|
|
||||||
const hasMoreRows = visibleCount < EDITORIAL_ROWS.length
|
|
||||||
const visibleRows = EDITORIAL_ROWS.slice(0, visibleCount)
|
|
||||||
|
|
||||||
const handleLoadMore = React.useCallback(() => {
|
|
||||||
if (!hasMoreRows || isLoadingMore) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoadingMore(true)
|
|
||||||
|
|
||||||
window.setTimeout(() => {
|
|
||||||
setVisibleCount(EDITORIAL_ROWS.length)
|
|
||||||
setIsLoadingMore(false)
|
|
||||||
}, 2000)
|
|
||||||
}, [hasMoreRows, isLoadingMore])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card {...props}>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex flex-col gap-(--gap) sm:flex-row">
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<CardTitle className="text-2xl">Top Editorials</CardTitle>
|
|
||||||
<CardDescription>Ranked by engagement</CardDescription>
|
|
||||||
</div>
|
|
||||||
<ToggleGroup
|
|
||||||
aria-label="Top editorials metric selector"
|
|
||||||
value={[selectedMetric]}
|
|
||||||
variant="outline"
|
|
||||||
className="w-full sm:ml-auto sm:w-fit"
|
|
||||||
>
|
|
||||||
{(["views", "time", "shares"] as const).map((metric) => {
|
|
||||||
return (
|
|
||||||
<ToggleGroupItem key={metric} value={metric} className="flex-1">
|
|
||||||
{METRIC_LABEL[metric]}
|
|
||||||
</ToggleGroupItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ToggleGroup>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex-1 **:data-[slot=table-container]:no-scrollbar **:data-[slot=table-container]:overflow-y-hidden">
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>#</TableHead>
|
|
||||||
<TableHead>Title</TableHead>
|
|
||||||
<TableHead>Published</TableHead>
|
|
||||||
<TableHead>Page Views</TableHead>
|
|
||||||
<TableHead>Read Time</TableHead>
|
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{visibleRows.map((row) => (
|
|
||||||
<TableRow key={row.rank}>
|
|
||||||
<TableCell className="translate-y-1 align-text-top">
|
|
||||||
{row.rank}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<p className="font-heading text-xl tracking-tight text-foreground">
|
|
||||||
{row.title}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">
|
|
||||||
By {row.author}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{row.published}</TableCell>
|
|
||||||
<TableCell>{row.pageviews}</TableCell>
|
|
||||||
<TableCell>{row.avgTime}</TableCell>
|
|
||||||
<TableCell className="text-right">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
render={<Button variant="ghost" size="icon-xs" />}
|
|
||||||
aria-label={`Open actions for ${row.title}`}
|
|
||||||
>
|
|
||||||
<MoreHorizontalIcon />
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Publish</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem variant="destructive">
|
|
||||||
Delete
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="justify-center">
|
|
||||||
{hasMoreRows ? (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleLoadMore}
|
|
||||||
disabled={isLoadingMore}
|
|
||||||
>
|
|
||||||
Load more content{" "}
|
|
||||||
{isLoadingMore ? (
|
|
||||||
<Spinner data-icon="inline-end" />
|
|
||||||
) : (
|
|
||||||
<ArrowDownIcon data-icon="inline-end" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import dynamic from "next/dynamic"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/styles/base-sera/ui/card"
|
|
||||||
|
|
||||||
const TrafficOverviewContent = dynamic(
|
|
||||||
() => import("./traffic-overview").then((mod) => mod.TrafficOverview),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => <TrafficOverviewFallback />,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export function TrafficOverviewDeferred({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof Card>) {
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<TrafficOverviewContent {...props} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TrafficOverviewFallback() {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-2xl">Traffic Overview</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Traffic for the last 30 days has increased by 12.4% compared to the
|
|
||||||
previous period.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
className="flex h-82 w-full flex-col justify-end gap-6 overflow-hidden bg-muted/40 p-5"
|
|
||||||
>
|
|
||||||
<div className="h-px w-full bg-border" />
|
|
||||||
<div className="h-px w-full bg-border" />
|
|
||||||
<div className="h-px w-full bg-border" />
|
|
||||||
<div className="h-px w-full bg-border" />
|
|
||||||
<div className="h-px w-full bg-border" />
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { TrendingUpIcon } from "lucide-react"
|
|
||||||
import {
|
|
||||||
CartesianGrid,
|
|
||||||
Line,
|
|
||||||
LineChart,
|
|
||||||
ReferenceDot,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
} from "recharts"
|
|
||||||
|
|
||||||
import { Badge } from "@/styles/base-sera/ui/badge"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardAction,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/styles/base-sera/ui/card"
|
|
||||||
import {
|
|
||||||
ChartContainer,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
type ChartConfig,
|
|
||||||
} from "@/styles/base-sera/ui/chart"
|
|
||||||
|
|
||||||
const TRAFFIC_OVERVIEW_DATA = [
|
|
||||||
{ date: "2025-10-01", views: 2600, unique: 1600 },
|
|
||||||
{ date: "2025-10-04", views: 4500, unique: 3000 },
|
|
||||||
{ date: "2025-10-08", views: 3500, unique: 2500 },
|
|
||||||
{ date: "2025-10-10", views: 6400, unique: 4500 },
|
|
||||||
{ date: "2025-10-13", views: 5400, unique: 4000 },
|
|
||||||
{ date: "2025-10-15", views: 8300, unique: 6500 },
|
|
||||||
{ date: "2025-10-17", views: 7400, unique: 6000 },
|
|
||||||
{ date: "2025-10-18", views: 9240, unique: 7105 },
|
|
||||||
{ date: "2025-10-22", views: 7700, unique: 6400 },
|
|
||||||
{ date: "2025-10-26", views: 8800, unique: 7000 },
|
|
||||||
{ date: "2025-10-29", views: 9800, unique: 8400 },
|
|
||||||
]
|
|
||||||
|
|
||||||
const TRAFFIC_CHART_CONFIG = {
|
|
||||||
views: {
|
|
||||||
label: "Views",
|
|
||||||
theme: {
|
|
||||||
light: "var(--chart-5)",
|
|
||||||
dark: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
unique: {
|
|
||||||
label: "Unique",
|
|
||||||
theme: {
|
|
||||||
light: "var(--chart-1)",
|
|
||||||
dark: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
const X_AXIS_DATE_FORMATTER = new Intl.DateTimeFormat("en-US", {
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
timeZone: "UTC",
|
|
||||||
})
|
|
||||||
|
|
||||||
function formatYAxisTick(value: number) {
|
|
||||||
if (value === 0) {
|
|
||||||
return "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value % 1000 === 0) {
|
|
||||||
return `${value / 1000}k`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${value / 1000}k`
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatXAxisTick(value: string) {
|
|
||||||
const date = new Date(`${value}T00:00:00Z`)
|
|
||||||
|
|
||||||
if (Number.isNaN(date.getTime())) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_AXIS_DATE_FORMATTER.format(date)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TrafficOverview({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof Card>) {
|
|
||||||
return (
|
|
||||||
<Card {...props}>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-2xl">Traffic Overview</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Traffic for the last 30 days has increased by 12.4% compared to the
|
|
||||||
previous period.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<ChartContainer config={TRAFFIC_CHART_CONFIG} className="h-82 w-full">
|
|
||||||
<LineChart data={TRAFFIC_OVERVIEW_DATA}>
|
|
||||||
<CartesianGrid
|
|
||||||
vertical={false}
|
|
||||||
strokeDasharray="3 6"
|
|
||||||
stroke="var(--border)"
|
|
||||||
/>
|
|
||||||
<XAxis
|
|
||||||
dataKey="date"
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
interval="preserveStartEnd"
|
|
||||||
tickMargin={10}
|
|
||||||
tickFormatter={formatXAxisTick}
|
|
||||||
/>
|
|
||||||
<YAxis
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
width={44}
|
|
||||||
domain={[0, 10000]}
|
|
||||||
ticks={[0, 2500, 5000, 7500, 10000]}
|
|
||||||
tickFormatter={formatYAxisTick}
|
|
||||||
hide
|
|
||||||
/>
|
|
||||||
<ChartTooltip content={<ChartTooltipContent />} />
|
|
||||||
<Line
|
|
||||||
type="linear"
|
|
||||||
dataKey="views"
|
|
||||||
stroke="var(--color-views)"
|
|
||||||
strokeWidth={2.2}
|
|
||||||
dot={false}
|
|
||||||
activeDot={{ r: 3.5, fill: "var(--color-views)" }}
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
type="linear"
|
|
||||||
dataKey="unique"
|
|
||||||
stroke="var(--color-unique)"
|
|
||||||
strokeWidth={2}
|
|
||||||
strokeDasharray="4 6"
|
|
||||||
dot={false}
|
|
||||||
activeDot={false}
|
|
||||||
/>
|
|
||||||
<ReferenceDot
|
|
||||||
x="2025-10-18"
|
|
||||||
y={9240}
|
|
||||||
r={2.5}
|
|
||||||
fill="var(--color-views)"
|
|
||||||
stroke="var(--color-views)"
|
|
||||||
/>
|
|
||||||
</LineChart>
|
|
||||||
</ChartContainer>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
|
||||||
|
|
||||||
import { Demographics } from "./components/demographics"
|
|
||||||
import { MetricsGrid } from "./components/metrics-grid"
|
|
||||||
import { PreviewHeader } from "./components/preview-header"
|
|
||||||
import { TopEditorial } from "./components/top-editorial"
|
|
||||||
import { TrafficOverviewDeferred } from "./components/traffic-overview-deferred"
|
|
||||||
|
|
||||||
export function AudienceAnalytics() {
|
|
||||||
return (
|
|
||||||
<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)] 2xl:py-8 **:[.container]:px-(--gap)">
|
|
||||||
<PreviewHeader />
|
|
||||||
<Separator className="hidden sm:block" />
|
|
||||||
<div className="container grid grid-cols-12 gap-(--gap) py-(--gap)">
|
|
||||||
<MetricsGrid />
|
|
||||||
<TrafficOverviewDeferred className="col-span-full md:col-span-6 lg:col-span-8" />
|
|
||||||
<Demographics className="col-span-full md:col-span-6 lg:col-span-4" />
|
|
||||||
<TopEditorial className="col-span-full" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import Image from "next/image"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
export function ImagePreview() {
|
|
||||||
return (
|
|
||||||
<div className="mt-8 flex flex-col overflow-hidden md:hidden">
|
|
||||||
<ImagePreviewItem name="sera-01" />
|
|
||||||
<ImagePreviewItem name="sera-03" />
|
|
||||||
<ImagePreviewItem name="sera-02" />
|
|
||||||
<ImagePreviewItem name="sera-06" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ImagePreviewItem({
|
|
||||||
name,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
name: string
|
|
||||||
className?: string
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"theme-taupe overflow-hidden bg-muted px-4 py-2 first:pt-4 last:pb-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={`/images/${name}-light.png`}
|
|
||||||
alt={name}
|
|
||||||
width={1440}
|
|
||||||
height={900}
|
|
||||||
className="dark:hidden"
|
|
||||||
/>
|
|
||||||
<Image
|
|
||||||
src={`/images/${name}-dark.png`}
|
|
||||||
alt={name}
|
|
||||||
width={1440}
|
|
||||||
height={900}
|
|
||||||
className="hidden dark:block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import dynamic from "next/dynamic"
|
|
||||||
|
|
||||||
type LazyPreviewName =
|
|
||||||
| "articleDirectory"
|
|
||||||
| "emptyState"
|
|
||||||
| "editArticle"
|
|
||||||
| "mediaLibrary"
|
|
||||||
| "mediaLibraryTable"
|
|
||||||
|
|
||||||
const PREVIEW_MIN_HEIGHTS: Record<LazyPreviewName, number> = {
|
|
||||||
articleDirectory: 760,
|
|
||||||
emptyState: 560,
|
|
||||||
editArticle: 980,
|
|
||||||
mediaLibrary: 880,
|
|
||||||
mediaLibraryTable: 980,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ArticleDirectoryPreview = dynamic(
|
|
||||||
() => import("../article-directory").then((mod) => mod.ArticleDirectory),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => (
|
|
||||||
<PreviewPlaceholder minHeight={PREVIEW_MIN_HEIGHTS.articleDirectory} />
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const EmptyStatePreview = dynamic(
|
|
||||||
() => import("../empty-state").then((mod) => mod.EmptyState),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => (
|
|
||||||
<PreviewPlaceholder minHeight={PREVIEW_MIN_HEIGHTS.emptyState} />
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const EditArticlePreview = dynamic(
|
|
||||||
() => import("../edit-article").then((mod) => mod.EditArticle),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => (
|
|
||||||
<PreviewPlaceholder minHeight={PREVIEW_MIN_HEIGHTS.editArticle} />
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const MediaLibraryPreview = dynamic(
|
|
||||||
() => import("../media-library").then((mod) => mod.MediaLibrary),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => (
|
|
||||||
<PreviewPlaceholder minHeight={PREVIEW_MIN_HEIGHTS.mediaLibrary} />
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const MediaLibraryTablePreview = dynamic(
|
|
||||||
() => import("../media-library-table").then((mod) => mod.MediaLibraryTable),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => (
|
|
||||||
<PreviewPlaceholder minHeight={PREVIEW_MIN_HEIGHTS.mediaLibraryTable} />
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const PREVIEW_COMPONENTS: Record<LazyPreviewName, React.ComponentType> = {
|
|
||||||
articleDirectory: ArticleDirectoryPreview,
|
|
||||||
emptyState: EmptyStatePreview,
|
|
||||||
editArticle: EditArticlePreview,
|
|
||||||
mediaLibrary: MediaLibraryPreview,
|
|
||||||
mediaLibraryTable: MediaLibraryTablePreview,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LazyPreview({ name }: { name: LazyPreviewName }) {
|
|
||||||
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
||||||
const [shouldRender, setShouldRender] = React.useState(false)
|
|
||||||
const PreviewComponent = PREVIEW_COMPONENTS[name]
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (shouldRender) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = containerRef.current
|
|
||||||
|
|
||||||
if (!container || !("IntersectionObserver" in window)) {
|
|
||||||
setShouldRender(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
(entries) => {
|
|
||||||
if (!entries.some((entry) => entry.isIntersecting)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setShouldRender(true)
|
|
||||||
observer.disconnect()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rootMargin: "800px 0px",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
observer.observe(container)
|
|
||||||
|
|
||||||
return () => observer.disconnect()
|
|
||||||
}, [shouldRender])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={containerRef}>
|
|
||||||
{shouldRender ? (
|
|
||||||
<PreviewComponent />
|
|
||||||
) : (
|
|
||||||
<PreviewPlaceholder minHeight={PREVIEW_MIN_HEIGHTS[name]} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PreviewPlaceholder({ minHeight }: { minHeight: number }) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
className="preview theme-taupe @container/preview w-full flex-1 bg-muted p-4 font-sans ring-1 ring-foreground/5 [--gap:--spacing(4)] sm:p-6 md:[--gap:--spacing(6)] xl:[--gap:--spacing(8)]"
|
|
||||||
style={{ minHeight }}
|
|
||||||
>
|
|
||||||
<div className="container flex flex-col gap-(--gap) py-(--gap)">
|
|
||||||
<div className="flex items-center justify-between gap-4">
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<div className="h-5 w-44 bg-background/80" />
|
|
||||||
<div className="h-3 w-56 max-w-full bg-background/60" />
|
|
||||||
</div>
|
|
||||||
<div className="hidden h-8 w-28 bg-background/70 sm:block" />
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 gap-(--gap) md:grid-cols-3">
|
|
||||||
<div className="min-h-48 bg-background/70 md:col-span-2" />
|
|
||||||
<div className="min-h-48 bg-background/70" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const THEME_OPTIONS = [
|
|
||||||
{ label: "Taupe", value: "theme-taupe" },
|
|
||||||
{ label: "Neutral", value: "theme-neutral" },
|
|
||||||
{ label: "Stone", value: "theme-stone" },
|
|
||||||
{ label: "Zinc", value: "theme-zinc" },
|
|
||||||
{ label: "Mauve", value: "theme-mauve" },
|
|
||||||
{ label: "Olive", value: "theme-olive" },
|
|
||||||
{ label: "Mist", value: "theme-mist" },
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const DEFAULT_THEME = "theme-taupe"
|
|
||||||
|
|
||||||
function applyThemeToPreviews(theme: string) {
|
|
||||||
const previewElements = document.querySelectorAll<HTMLElement>(".preview")
|
|
||||||
|
|
||||||
previewElements.forEach((element) => {
|
|
||||||
THEME_OPTIONS.forEach((option) => {
|
|
||||||
element.classList.remove(option.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
element.classList.add(theme)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThemeSwitcher() {
|
|
||||||
const [theme, setTheme] = React.useState<string>(DEFAULT_THEME)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
applyThemeToPreviews(theme)
|
|
||||||
}, [theme])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="fixed inset-x-0 bottom-8 z-50 flex justify-center px-4">
|
|
||||||
<div className="w-full max-w-[60vw] rounded-full border-0 bg-neutral-950/50 p-1.5 shadow-xl backdrop-blur-xl sm:max-w-fit">
|
|
||||||
<div className="no-scrollbar flex snap-x snap-mandatory items-center overflow-x-auto">
|
|
||||||
{THEME_OPTIONS.map((option) => (
|
|
||||||
<button
|
|
||||||
data-active={theme === option.value}
|
|
||||||
key={option.value}
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setTheme(option.value)
|
|
||||||
}}
|
|
||||||
className="shrink-0 snap-center rounded-full px-3 py-1.5 text-sm font-medium text-neutral-300 outline-hidden transition-colors select-none hover:text-neutral-100 data-active:bg-neutral-500 data-active:text-neutral-100"
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
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,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/styles/base-sera/ui/card"
|
|
||||||
import { Checkbox } from "@/styles/base-sera/ui/checkbox"
|
|
||||||
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
|
|
||||||
complete: boolean
|
|
||||||
note?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const MILESTONES: Milestone[] = [
|
|
||||||
{
|
|
||||||
name: "Outline & Commissioning",
|
|
||||||
complete: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "First Draft Submitted",
|
|
||||||
complete: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Review & Revisions",
|
|
||||||
complete: false,
|
|
||||||
note: "Waiting on editor",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Final Copy Edit",
|
|
||||||
complete: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Art Direction & Layout",
|
|
||||||
complete: false,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
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 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>
|
|
||||||
<Button variant="ghost" size="icon-sm" aria-label="Italic">
|
|
||||||
<ItalicIcon />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon-sm" aria-label="Underline">
|
|
||||||
<UnderlineIcon />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon-sm"
|
|
||||||
aria-label="Strikethrough"
|
|
||||||
className="hidden md:flex"
|
|
||||||
>
|
|
||||||
<StrikethroughIcon />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon-sm"
|
|
||||||
aria-label="Code"
|
|
||||||
className="hidden md:flex"
|
|
||||||
>
|
|
||||||
<Code2Icon />
|
|
||||||
</Button>
|
|
||||||
<ButtonGroupSeparator className="mx-2 hidden md:flex data-vertical:h-4 data-vertical:self-center" />
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon-sm"
|
|
||||||
aria-label="Align Left"
|
|
||||||
className="hidden md:flex"
|
|
||||||
>
|
|
||||||
<AlignLeftIcon />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon-sm"
|
|
||||||
aria-label="Align Center"
|
|
||||||
className="hidden md:flex"
|
|
||||||
>
|
|
||||||
<AlignCenterIcon />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon-sm"
|
|
||||||
aria-label="Align Right"
|
|
||||||
className="hidden md:flex"
|
|
||||||
>
|
|
||||||
<AlignRightIcon />
|
|
||||||
</Button>
|
|
||||||
<ButtonGroupSeparator className="mx-2 hidden md:flex data-vertical:h-4 data-vertical:self-center" />
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon-sm"
|
|
||||||
aria-label="Link"
|
|
||||||
className="hidden md:flex"
|
|
||||||
>
|
|
||||||
<LinkIcon />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon-sm"
|
|
||||||
aria-label="Image"
|
|
||||||
className="hidden md:flex"
|
|
||||||
>
|
|
||||||
<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="mx-auto flex max-w-2xl flex-1 flex-col gap-8 px-10 py-10 leading-loose md:px-14 lg:py-18">
|
|
||||||
<h1 className="font-heading text-4xl leading-12 font-medium 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>
|
|
||||||
Historically, urban development has been a zero-sum game with
|
|
||||||
nature.
|
|
||||||
</p>
|
|
||||||
<h2 className="font-heading text-2xl tracking-wide uppercase">
|
|
||||||
The Living Building Challenge
|
|
||||||
</h2>
|
|
||||||
<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>
|
|
||||||
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="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="grid grid-cols-12 gap-(--gap) xl:flex xl:flex-col">
|
|
||||||
<Card className="col-span-full md:col-span-6 lg:col-span-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Article Details</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<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="col-span-full md:col-span-6 lg:col-span-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Publication Flow</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<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 className="col-span-full lg:col-span-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Word Count</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Progress value={70}>
|
|
||||||
<ProgressLabel>1,402 / 2,000 words</ProgressLabel>
|
|
||||||
<ProgressValue />
|
|
||||||
</Progress>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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>
|
|
||||||
<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,16 +0,0 @@
|
|||||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
|
||||||
|
|
||||||
import { EditorWorkspace } from "./components/editor-workspace"
|
|
||||||
import { PreviewHeader } from "./components/preview-header"
|
|
||||||
|
|
||||||
export function EditArticle() {
|
|
||||||
return (
|
|
||||||
<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)] 2xl:py-8 **:[.container]:px-(--gap)">
|
|
||||||
<PreviewHeader />
|
|
||||||
<Separator className="hidden sm:block" />
|
|
||||||
<div className="container py-(--gap)">
|
|
||||||
<EditorWorkspace />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import { FileTextIcon, PlusIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { Badge } from "@/styles/base-sera/ui/badge"
|
|
||||||
import { Button } from "@/styles/base-sera/ui/button"
|
|
||||||
import { Card, CardContent } from "@/styles/base-sera/ui/card"
|
|
||||||
import {
|
|
||||||
Empty,
|
|
||||||
EmptyContent,
|
|
||||||
EmptyDescription,
|
|
||||||
EmptyHeader,
|
|
||||||
EmptyMedia,
|
|
||||||
EmptyTitle,
|
|
||||||
} from "@/styles/base-sera/ui/empty"
|
|
||||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
|
||||||
|
|
||||||
type Stage = {
|
|
||||||
id: string
|
|
||||||
label: string
|
|
||||||
description: string
|
|
||||||
dotClassName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const STAGES: Stage[] = [
|
|
||||||
{
|
|
||||||
id: "drafting",
|
|
||||||
label: "Drafting",
|
|
||||||
description:
|
|
||||||
"Start the writing process. Articles here are works in progress, visible only to editors and authors.",
|
|
||||||
dotClassName: "bg-amber-600",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "in-revision",
|
|
||||||
label: "In Revision",
|
|
||||||
description:
|
|
||||||
"Content undergoing editorial review. Track changes and word counts as pieces take shape.",
|
|
||||||
dotClassName: "bg-orange-700",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "final-edit",
|
|
||||||
label: "Final Edit",
|
|
||||||
description:
|
|
||||||
"The final polish before publication. Ensure all styling and factual checks are complete.",
|
|
||||||
dotClassName: "bg-foreground",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function EmptyDirectory() {
|
|
||||||
return (
|
|
||||||
<Card className="py-24">
|
|
||||||
<CardContent className="flex flex-col items-center gap-10">
|
|
||||||
<Empty className="min-h-96">
|
|
||||||
<EmptyHeader>
|
|
||||||
<EmptyMedia
|
|
||||||
variant="icon"
|
|
||||||
className="size-14 rounded-full bg-muted/70 text-muted-foreground"
|
|
||||||
>
|
|
||||||
<FileTextIcon className="size-5" />
|
|
||||||
</EmptyMedia>
|
|
||||||
<EmptyTitle className="font-heading text-2xl tracking-normal normal-case">
|
|
||||||
A Blank Canvas
|
|
||||||
</EmptyTitle>
|
|
||||||
<EmptyDescription>
|
|
||||||
Your editorial directory is currently empty. Start building your
|
|
||||||
publication's next issue by drafting the first piece.
|
|
||||||
</EmptyDescription>
|
|
||||||
</EmptyHeader>
|
|
||||||
<EmptyContent>
|
|
||||||
<Button>
|
|
||||||
<PlusIcon data-icon="inline-start" />
|
|
||||||
Create first article
|
|
||||||
</Button>
|
|
||||||
</EmptyContent>
|
|
||||||
</Empty>
|
|
||||||
<Separator className="max-w-2xl" />
|
|
||||||
<div className="grid w-full max-w-2xl grid-cols-1 gap-8 sm:grid-cols-3">
|
|
||||||
{STAGES.map((stage) => (
|
|
||||||
<div key={stage.id} className="flex flex-col gap-2">
|
|
||||||
<Badge>
|
|
||||||
<span
|
|
||||||
aria-hidden
|
|
||||||
className={`size-1.5 rounded-full ${stage.dotClassName}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{stage.label}
|
|
||||||
</Badge>
|
|
||||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
|
||||||
{stage.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { ArrowLeftIcon, PlusIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
} from "@/styles/base-sera/ui/breadcrumb"
|
|
||||||
import { Button } from "@/styles/base-sera/ui/button"
|
|
||||||
|
|
||||||
export function PreviewHeader() {
|
|
||||||
return (
|
|
||||||
<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" />
|
|
||||||
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>
|
|
||||||
<Button className="sm:ml-auto">
|
|
||||||
<PlusIcon data-icon="inline-start" />
|
|
||||||
New Article
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
|
||||||
|
|
||||||
import { EmptyDirectory } from "./components/empty-directory"
|
|
||||||
import { PreviewHeader } from "./components/preview-header"
|
|
||||||
|
|
||||||
export function EmptyState() {
|
|
||||||
return (
|
|
||||||
<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)] 2xl:py-8 **:[.container]:px-(--gap)">
|
|
||||||
<PreviewHeader />
|
|
||||||
<Separator className="hidden sm:block" />
|
|
||||||
<div className="container py-(--gap)">
|
|
||||||
<EmptyDirectory />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
FileTextIcon,
|
|
||||||
ImageIcon,
|
|
||||||
MoreVerticalIcon,
|
|
||||||
SearchIcon,
|
|
||||||
VideoIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { Badge } from "@/styles/base-sera/ui/badge"
|
|
||||||
import { Button } from "@/styles/base-sera/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
} from "@/styles/base-sera/ui/card"
|
|
||||||
import { Checkbox } from "@/styles/base-sera/ui/checkbox"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/styles/base-sera/ui/dropdown-menu"
|
|
||||||
import {
|
|
||||||
InputGroup,
|
|
||||||
InputGroupAddon,
|
|
||||||
InputGroupInput,
|
|
||||||
} from "@/styles/base-sera/ui/input-group"
|
|
||||||
import {
|
|
||||||
Pagination,
|
|
||||||
PaginationContent,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationLink,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationPrevious,
|
|
||||||
} from "@/styles/base-sera/ui/pagination"
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/styles/base-sera/ui/table"
|
|
||||||
|
|
||||||
import { ASSETS, type AssetType } from "../../media-library/data"
|
|
||||||
|
|
||||||
function AssetTypeIcon({
|
|
||||||
type,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
type: AssetType
|
|
||||||
className?: string
|
|
||||||
}) {
|
|
||||||
if (type === "MP4") {
|
|
||||||
return <VideoIcon className={className} />
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "PDF") {
|
|
||||||
return <FileTextIcon className={className} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ImageIcon className={className} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AssetTable() {
|
|
||||||
const [selectedIds, setSelectedIds] = React.useState<Set<string>>(
|
|
||||||
new Set(["1"])
|
|
||||||
)
|
|
||||||
|
|
||||||
const toggleSelection = React.useCallback((id: string) => {
|
|
||||||
setSelectedIds((previous) => {
|
|
||||||
const next = new Set(previous)
|
|
||||||
|
|
||||||
if (next.has(id)) {
|
|
||||||
next.delete(id)
|
|
||||||
} else {
|
|
||||||
next.add(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<InputGroup className="w-full">
|
|
||||||
<InputGroupAddon>
|
|
||||||
<SearchIcon />
|
|
||||||
</InputGroupAddon>
|
|
||||||
<InputGroupInput placeholder="Search files, tags, or metadata..." />
|
|
||||||
</InputGroup>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="px-0 py-0">
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead className="w-10 pl-6" aria-label="Select" />
|
|
||||||
<TableHead className="w-20" aria-label="Preview" />
|
|
||||||
<TableHead>Filename</TableHead>
|
|
||||||
<TableHead>Type</TableHead>
|
|
||||||
<TableHead>Dimensions</TableHead>
|
|
||||||
<TableHead>Size</TableHead>
|
|
||||||
<TableHead>Uploaded By</TableHead>
|
|
||||||
<TableHead>Date</TableHead>
|
|
||||||
<TableHead className="w-10 pr-6" aria-label="Actions" />
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{ASSETS.map((asset) => {
|
|
||||||
const isSelected = selectedIds.has(asset.id)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={asset.id}
|
|
||||||
data-state={isSelected ? "selected" : undefined}
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => toggleSelection(asset.id)}
|
|
||||||
>
|
|
||||||
<TableCell className="pl-6">
|
|
||||||
<Checkbox
|
|
||||||
checked={isSelected}
|
|
||||||
aria-label={`Select ${asset.name}`}
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onCheckedChange={() => toggleSelection(asset.id)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="relative flex aspect-4/3 w-16 items-center justify-center bg-muted/60 ring-1 ring-border/70 ring-inset">
|
|
||||||
{asset.duration ? (
|
|
||||||
<span className="absolute right-1 bottom-1 bg-foreground/90 px-1 text-[0.5rem] font-semibold tracking-wider text-background">
|
|
||||||
{asset.duration}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
<AssetTypeIcon
|
|
||||||
type={asset.type}
|
|
||||||
className="size-4 text-muted-foreground/60"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-sm font-medium text-foreground">
|
|
||||||
{asset.name}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge
|
|
||||||
variant="outline"
|
|
||||||
className="border px-2 py-0.5 text-[0.625rem]"
|
|
||||||
>
|
|
||||||
{asset.type}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-sm">{asset.dimensions}</TableCell>
|
|
||||||
<TableCell className="text-sm">{asset.size}</TableCell>
|
|
||||||
<TableCell>{asset.uploadedBy}</TableCell>
|
|
||||||
<TableCell className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
|
|
||||||
{asset.date}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="pr-6 text-right">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
render={<Button variant="ghost" size="icon-xs" />}
|
|
||||||
aria-label={`Open actions for ${asset.name}`}
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
>
|
|
||||||
<MoreVerticalIcon />
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem>Preview</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Download</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem variant="destructive">
|
|
||||||
Delete
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="justify-center py-4">
|
|
||||||
<Pagination>
|
|
||||||
<PaginationContent>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationPrevious href="#" text="" />
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#" isActive>
|
|
||||||
1
|
|
||||||
</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#">2</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#">3</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationNext href="#" text="" />
|
|
||||||
</PaginationItem>
|
|
||||||
</PaginationContent>
|
|
||||||
</Pagination>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { addDays, format } from "date-fns"
|
|
||||||
import { CalendarIcon, FilterIcon, XIcon } from "lucide-react"
|
|
||||||
import { type DateRange } from "react-day-picker"
|
|
||||||
|
|
||||||
import { Button } from "@/styles/base-sera/ui/button"
|
|
||||||
import { Calendar } from "@/styles/base-sera/ui/calendar"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/styles/base-sera/ui/card"
|
|
||||||
import { Checkbox } from "@/styles/base-sera/ui/checkbox"
|
|
||||||
import {
|
|
||||||
Combobox,
|
|
||||||
ComboboxChip,
|
|
||||||
ComboboxChips,
|
|
||||||
ComboboxChipsInput,
|
|
||||||
ComboboxContent,
|
|
||||||
ComboboxEmpty,
|
|
||||||
ComboboxItem,
|
|
||||||
ComboboxList,
|
|
||||||
ComboboxValue,
|
|
||||||
useComboboxAnchor,
|
|
||||||
} from "@/styles/base-sera/ui/combobox"
|
|
||||||
import {
|
|
||||||
Field,
|
|
||||||
FieldGroup,
|
|
||||||
FieldLabel,
|
|
||||||
FieldLegend,
|
|
||||||
FieldSet,
|
|
||||||
} from "@/styles/base-sera/ui/field"
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/styles/base-sera/ui/popover"
|
|
||||||
import { RadioGroup, RadioGroupItem } from "@/styles/base-sera/ui/radio-group"
|
|
||||||
import { Slider } from "@/styles/base-sera/ui/slider"
|
|
||||||
|
|
||||||
const FILE_TYPES = [
|
|
||||||
{
|
|
||||||
id: "images",
|
|
||||||
label: "Images (JPEG, PNG, WEBP)",
|
|
||||||
defaultChecked: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "video",
|
|
||||||
label: "Video (MP4, MOV)",
|
|
||||||
defaultChecked: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "documents",
|
|
||||||
label: "Documents (PDF)",
|
|
||||||
defaultChecked: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "audio",
|
|
||||||
label: "Audio (MP3, WAV)",
|
|
||||||
defaultChecked: false,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const DATE_OPTIONS = [
|
|
||||||
{ value: "any", label: "Any time" },
|
|
||||||
{ value: "24h", label: "Past 24 hours" },
|
|
||||||
{ value: "week", label: "Past week" },
|
|
||||||
{ value: "month", label: "Past month" },
|
|
||||||
]
|
|
||||||
|
|
||||||
const TAGS = [
|
|
||||||
"architecture",
|
|
||||||
"brutalism",
|
|
||||||
"ceramics",
|
|
||||||
"design-week",
|
|
||||||
"editorial",
|
|
||||||
"exterior",
|
|
||||||
"film",
|
|
||||||
"food",
|
|
||||||
"furniture",
|
|
||||||
"interior",
|
|
||||||
"kyoto",
|
|
||||||
"minimalism",
|
|
||||||
"print",
|
|
||||||
"sustainability",
|
|
||||||
"summer-issue",
|
|
||||||
"video",
|
|
||||||
] as const
|
|
||||||
|
|
||||||
export function FilterLibrary() {
|
|
||||||
const tagAnchor = useComboboxAnchor()
|
|
||||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
|
||||||
from: new Date(new Date().getFullYear(), new Date().getMonth(), 1),
|
|
||||||
to: addDays(
|
|
||||||
new Date(new Date().getFullYear(), new Date().getMonth(), 1),
|
|
||||||
21
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="border-b">
|
|
||||||
<CardTitle>Filter Library</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<FieldGroup>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>File Type</FieldLegend>
|
|
||||||
<Field>
|
|
||||||
{FILE_TYPES.map((type) => (
|
|
||||||
<Field key={type.id} orientation="horizontal">
|
|
||||||
<Checkbox id={type.id} defaultChecked={type.defaultChecked} />
|
|
||||||
<FieldLabel htmlFor={type.id}>{type.label}</FieldLabel>
|
|
||||||
</Field>
|
|
||||||
))}
|
|
||||||
</Field>
|
|
||||||
</FieldSet>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Date Uploaded</FieldLegend>
|
|
||||||
<RadioGroup defaultValue="any">
|
|
||||||
{DATE_OPTIONS.map((option) => (
|
|
||||||
<Field key={option.value} orientation="horizontal">
|
|
||||||
<RadioGroupItem
|
|
||||||
value={option.value}
|
|
||||||
id={`date-${option.value}`}
|
|
||||||
/>
|
|
||||||
<FieldLabel htmlFor={`date-${option.value}`}>
|
|
||||||
{option.label}
|
|
||||||
</FieldLabel>
|
|
||||||
</Field>
|
|
||||||
))}
|
|
||||||
</RadioGroup>
|
|
||||||
</FieldSet>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel htmlFor="custom-range">Custom Range</FieldLabel>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
id="custom-range"
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CalendarIcon data-icon="inline-start" />
|
|
||||||
{dateRange?.from ? (
|
|
||||||
dateRange.to ? (
|
|
||||||
<>
|
|
||||||
{format(dateRange.from, "LLL dd, y")} –{" "}
|
|
||||||
{format(dateRange.to, "LLL dd, y")}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
format(dateRange.from, "LLL dd, y")
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<span>Pick a date range</span>
|
|
||||||
)}
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0" align="end">
|
|
||||||
<Calendar
|
|
||||||
mode="range"
|
|
||||||
defaultMonth={dateRange?.from}
|
|
||||||
selected={dateRange}
|
|
||||||
onSelect={setDateRange}
|
|
||||||
numberOfMonths={2}
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</Field>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>File Size</FieldLegend>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<div className="flex items-center justify-between text-xs font-medium tracking-wider text-muted-foreground uppercase">
|
|
||||||
<span>0 MB</span>
|
|
||||||
<span>500+ MB</span>
|
|
||||||
</div>
|
|
||||||
<Slider defaultValue={[0, 60]} max={100} step={1} />
|
|
||||||
<div className="flex items-center justify-between text-xs font-medium">
|
|
||||||
<span>Min: 0 MB</span>
|
|
||||||
<span>Max: 300 MB</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FieldSet>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Tags</FieldLegend>
|
|
||||||
<Field>
|
|
||||||
<Combobox
|
|
||||||
multiple
|
|
||||||
autoHighlight
|
|
||||||
items={TAGS}
|
|
||||||
defaultValue={["architecture", "brutalism"]}
|
|
||||||
>
|
|
||||||
<ComboboxChips ref={tagAnchor}>
|
|
||||||
<ComboboxValue>
|
|
||||||
{(values) => (
|
|
||||||
<React.Fragment>
|
|
||||||
{values.map((value: string) => (
|
|
||||||
<ComboboxChip key={value}>{value}</ComboboxChip>
|
|
||||||
))}
|
|
||||||
<ComboboxChipsInput placeholder="Filter by tag..." />
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
</ComboboxValue>
|
|
||||||
</ComboboxChips>
|
|
||||||
<ComboboxContent anchor={tagAnchor}>
|
|
||||||
<ComboboxEmpty>No tags found.</ComboboxEmpty>
|
|
||||||
<ComboboxList>
|
|
||||||
{(item) => (
|
|
||||||
<ComboboxItem key={item} value={item}>
|
|
||||||
{item}
|
|
||||||
</ComboboxItem>
|
|
||||||
)}
|
|
||||||
</ComboboxList>
|
|
||||||
</ComboboxContent>
|
|
||||||
</Combobox>
|
|
||||||
</Field>
|
|
||||||
</FieldSet>
|
|
||||||
</FieldGroup>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex flex-col gap-2 border-t">
|
|
||||||
<Button className="w-full">Apply Filters</Button>
|
|
||||||
<Button variant="ghost" className="w-full">
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { ArrowLeftIcon, SlidersHorizontalIcon, UploadIcon } from "lucide-react"
|
|
||||||
|
|
||||||
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>
|
|
||||||
<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" />
|
|
||||||
Asset management
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
<h1 className="line-clamp-1 font-heading text-3xl tracking-wide uppercase md:text-3xl lg:text-4xl">
|
|
||||||
Media Library
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<ButtonGroup className="gap-2 sm:ml-auto md:gap-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="bg-background hover:bg-background/80"
|
|
||||||
>
|
|
||||||
<SlidersHorizontalIcon data-icon="inline-start" />
|
|
||||||
Filters
|
|
||||||
</Button>
|
|
||||||
<Button>
|
|
||||||
<UploadIcon data-icon="inline-start" />
|
|
||||||
Upload Assets
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
|
||||||
|
|
||||||
import { AssetTable } from "./components/asset-table"
|
|
||||||
import { FilterLibrary } from "./components/filter-library"
|
|
||||||
import { PreviewHeader } from "./components/preview-header"
|
|
||||||
|
|
||||||
export function MediaLibraryTable() {
|
|
||||||
return (
|
|
||||||
<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)] 2xl:py-8 **:[.container]:px-(--gap)">
|
|
||||||
<PreviewHeader />
|
|
||||||
<Separator className="hidden sm:block" />
|
|
||||||
<div className="container grid grid-cols-1 items-start gap-(--gap) py-(--gap) xl:grid-cols-[minmax(0,1fr)_320px]">
|
|
||||||
<AssetTable />
|
|
||||||
<FilterLibrary />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import {
|
|
||||||
DownloadIcon,
|
|
||||||
FileTextIcon,
|
|
||||||
ImageIcon,
|
|
||||||
PlusIcon,
|
|
||||||
VideoIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { Badge } from "@/styles/base-sera/ui/badge"
|
|
||||||
import { Button } from "@/styles/base-sera/ui/button"
|
|
||||||
import { Card, CardContent, CardFooter } from "@/styles/base-sera/ui/card"
|
|
||||||
import {
|
|
||||||
Item,
|
|
||||||
ItemContent,
|
|
||||||
ItemDescription,
|
|
||||||
ItemTitle,
|
|
||||||
} from "@/styles/base-sera/ui/item"
|
|
||||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
|
||||||
|
|
||||||
import { type Asset, type AssetType } from "../data"
|
|
||||||
|
|
||||||
const TYPE_LABEL: Record<AssetType, string> = {
|
|
||||||
JPEG: "Image / JPEG",
|
|
||||||
PNG: "Image / PNG",
|
|
||||||
WEBP: "Image / WEBP",
|
|
||||||
MP4: "Video / MP4",
|
|
||||||
PDF: "Document / PDF",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AssetDetails({ asset }: { asset: Asset }) {
|
|
||||||
return (
|
|
||||||
<Card className="gap-0">
|
|
||||||
<CardContent className="flex flex-col gap-6">
|
|
||||||
<div className="flex aspect-5/4 items-center justify-center bg-muted/60 text-muted-foreground/60 ring-1 ring-border/70 ring-inset">
|
|
||||||
{asset.type === "MP4" ? (
|
|
||||||
<VideoIcon className="size-8" />
|
|
||||||
) : asset.type === "PDF" ? (
|
|
||||||
<FileTextIcon className="size-8" />
|
|
||||||
) : (
|
|
||||||
<ImageIcon className="size-8" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<h2 className="line-clamp-2 font-heading text-xl tracking-wide">
|
|
||||||
{asset.name}
|
|
||||||
</h2>
|
|
||||||
<Separator />
|
|
||||||
<dl className="flex flex-col gap-5 text-sm">
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<dt className="text-[0.625rem] font-semibold tracking-widest text-muted-foreground uppercase">
|
|
||||||
Asset Type
|
|
||||||
</dt>
|
|
||||||
<dd>{TYPE_LABEL[asset.type]}</dd>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<dt className="text-[0.625rem] font-semibold tracking-widest text-muted-foreground uppercase">
|
|
||||||
Dimensions
|
|
||||||
</dt>
|
|
||||||
<dd>{asset.dimensions}</dd>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<dt className="text-[0.625rem] font-semibold tracking-widest text-muted-foreground uppercase">
|
|
||||||
File Size
|
|
||||||
</dt>
|
|
||||||
<dd>{asset.size}</dd>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
<Separator />
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h3 className="text-[0.625rem] font-semibold tracking-widest text-muted-foreground uppercase">
|
|
||||||
Tags
|
|
||||||
</h3>
|
|
||||||
<Button variant="ghost" size="icon-xs" aria-label="Add tag">
|
|
||||||
<PlusIcon />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-x-4 gap-y-2">
|
|
||||||
{asset.tags.map((tag) => (
|
|
||||||
<Badge key={tag}>{tag}</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<h3 className="text-[0.625rem] font-semibold tracking-widest text-muted-foreground uppercase">
|
|
||||||
Used In
|
|
||||||
</h3>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{asset.usedIn.map((usage) => (
|
|
||||||
<Item key={usage.title} variant="outline">
|
|
||||||
<ItemContent>
|
|
||||||
<ItemTitle>{usage.title}</ItemTitle>
|
|
||||||
<ItemDescription>{usage.role}</ItemDescription>
|
|
||||||
</ItemContent>
|
|
||||||
</Item>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="mt-6 border-t pt-6">
|
|
||||||
<Button className="w-full">
|
|
||||||
<DownloadIcon data-icon="inline-start" />
|
|
||||||
Download
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import {
|
|
||||||
CheckIcon,
|
|
||||||
FileTextIcon,
|
|
||||||
ImageIcon,
|
|
||||||
SearchIcon,
|
|
||||||
VideoIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
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,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationPrevious,
|
|
||||||
} from "@/styles/base-sera/ui/pagination"
|
|
||||||
|
|
||||||
import { ASSETS, type Asset, type AssetType } from "../data"
|
|
||||||
|
|
||||||
function AssetTypeIcon({
|
|
||||||
type,
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
type: AssetType
|
|
||||||
className?: string
|
|
||||||
}) {
|
|
||||||
if (type === "MP4") {
|
|
||||||
return <VideoIcon className={className} />
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "PDF") {
|
|
||||||
return <FileTextIcon className={className} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ImageIcon className={className} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AssetGrid({
|
|
||||||
selectedId,
|
|
||||||
onSelect,
|
|
||||||
}: {
|
|
||||||
selectedId: string
|
|
||||||
onSelect: (id: string) => void
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<InputGroup className="w-full">
|
|
||||||
<InputGroupAddon>
|
|
||||||
<SearchIcon />
|
|
||||||
</InputGroupAddon>
|
|
||||||
<InputGroupInput placeholder="Search files, tags, or metadata..." />
|
|
||||||
</InputGroup>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-2 gap-6 sm:grid-cols-3 lg:grid-cols-4">
|
|
||||||
{ASSETS.map((asset) => (
|
|
||||||
<AssetGridItem
|
|
||||||
key={asset.id}
|
|
||||||
asset={asset}
|
|
||||||
selected={asset.id === selectedId}
|
|
||||||
onSelect={() => onSelect(asset.id)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="justify-center">
|
|
||||||
<Pagination>
|
|
||||||
<PaginationContent>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationPrevious href="#" />
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#" isActive>
|
|
||||||
1
|
|
||||||
</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#">2</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#">3</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationNext href="#" />
|
|
||||||
</PaginationItem>
|
|
||||||
</PaginationContent>
|
|
||||||
</Pagination>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AssetGridItem({
|
|
||||||
asset,
|
|
||||||
selected,
|
|
||||||
onSelect,
|
|
||||||
}: {
|
|
||||||
asset: Asset
|
|
||||||
selected: boolean
|
|
||||||
onSelect: () => void
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onSelect}
|
|
||||||
aria-pressed={selected}
|
|
||||||
className="group flex flex-col gap-2.5 text-left outline-none focus-visible:[&>div:first-child]:ring-2 focus-visible:[&>div:first-child]:ring-ring"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"relative flex aspect-4/3 items-center justify-center bg-muted/60 ring-1 ring-border/70 transition-shadow ring-inset group-hover:ring-foreground/40",
|
|
||||||
selected && "ring-2 ring-foreground group-hover:ring-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{selected ? (
|
|
||||||
<div className="absolute top-2 left-2 flex size-5 items-center justify-center bg-foreground text-background">
|
|
||||||
<CheckIcon className="size-3" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<Badge
|
|
||||||
variant="outline"
|
|
||||||
className="absolute top-2 right-2 border bg-background px-2 py-1 text-[0.625rem]"
|
|
||||||
>
|
|
||||||
{asset.type}
|
|
||||||
</Badge>
|
|
||||||
{asset.duration ? (
|
|
||||||
<Badge className="absolute bottom-2 left-2 bg-foreground px-2 py-1 text-background">
|
|
||||||
{asset.duration}
|
|
||||||
</Badge>
|
|
||||||
) : null}
|
|
||||||
<AssetTypeIcon
|
|
||||||
type={asset.type}
|
|
||||||
className="size-7 text-muted-foreground/60"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-0.5 px-0.5">
|
|
||||||
<p className="line-clamp-1 text-sm font-medium">{asset.name}</p>
|
|
||||||
<p className="text-[0.625rem] font-semibold tracking-wider text-muted-foreground uppercase">
|
|
||||||
{asset.date} · {asset.size}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { ArrowLeftIcon, SlidersHorizontalIcon, UploadIcon } from "lucide-react"
|
|
||||||
|
|
||||||
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>
|
|
||||||
<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" />
|
|
||||||
Asset management
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
<h1 className="line-clamp-1 font-heading text-3xl tracking-wide uppercase md:text-3xl lg:text-4xl">
|
|
||||||
Media Library
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<ButtonGroup className="gap-2 sm:ml-auto md:gap-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="bg-background hover:bg-background/80"
|
|
||||||
>
|
|
||||||
<SlidersHorizontalIcon data-icon="inline-start" />
|
|
||||||
Filters
|
|
||||||
</Button>
|
|
||||||
<Button>
|
|
||||||
<UploadIcon data-icon="inline-start" />
|
|
||||||
Upload Assets
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
export type AssetType = "JPEG" | "PNG" | "WEBP" | "MP4" | "PDF"
|
|
||||||
|
|
||||||
export type Asset = {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
date: string
|
|
||||||
size: string
|
|
||||||
type: AssetType
|
|
||||||
dimensions: string
|
|
||||||
duration?: string
|
|
||||||
uploadedBy: string
|
|
||||||
uploadedByInitials: string
|
|
||||||
uploadedOn: string
|
|
||||||
tags: string[]
|
|
||||||
usedIn: { title: string; role: string }[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ASSETS: Asset[] = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
name: "brutalism-facade-01.jpg",
|
|
||||||
date: "Oct 24",
|
|
||||||
size: "4.2 MB",
|
|
||||||
type: "JPEG",
|
|
||||||
dimensions: "4000 × 3000",
|
|
||||||
uploadedBy: "Marcus Chen",
|
|
||||||
uploadedByInitials: "MC",
|
|
||||||
uploadedOn: "Oct 24, 2024",
|
|
||||||
tags: ["architecture", "brutalism", "exterior", "summer-issue"],
|
|
||||||
usedIn: [
|
|
||||||
{ title: "Brutalism's Second Act", role: "Cover Image" },
|
|
||||||
{ title: "Autumn Sartorial Code", role: "Inline Gallery" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
name: "brutalism-interior-raw.jpg",
|
|
||||||
date: "Oct 24",
|
|
||||||
size: "3.8 MB",
|
|
||||||
type: "JPEG",
|
|
||||||
dimensions: "3800 × 2850",
|
|
||||||
uploadedBy: "Marcus Chen",
|
|
||||||
uploadedByInitials: "MC",
|
|
||||||
uploadedOn: "Oct 24, 2024",
|
|
||||||
tags: ["architecture", "brutalism", "interior"],
|
|
||||||
usedIn: [{ title: "Brutalism's Second Act", role: "Inline Gallery" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
name: "seattle-living-building-diagram.png",
|
|
||||||
date: "Oct 22",
|
|
||||||
size: "1.1 MB",
|
|
||||||
type: "PNG",
|
|
||||||
dimensions: "2000 × 1500",
|
|
||||||
uploadedBy: "Sarah Jenkins",
|
|
||||||
uploadedByInitials: "SJ",
|
|
||||||
uploadedOn: "Oct 22, 2024",
|
|
||||||
tags: ["diagram", "sustainability", "seattle"],
|
|
||||||
usedIn: [
|
|
||||||
{ title: "The Future of Sustainable Architecture", role: "Diagram" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "4",
|
|
||||||
name: "interview-sofia-coppola-clip1.mp4",
|
|
||||||
date: "Oct 18",
|
|
||||||
size: "45.0 MB",
|
|
||||||
type: "MP4",
|
|
||||||
dimensions: "1920 × 1080",
|
|
||||||
duration: "0:45",
|
|
||||||
uploadedBy: "Emma Ross",
|
|
||||||
uploadedByInitials: "ER",
|
|
||||||
uploadedOn: "Oct 18, 2024",
|
|
||||||
tags: ["video", "interview", "film"],
|
|
||||||
usedIn: [{ title: "The Aesthetics of Isolation", role: "Featured Video" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "5",
|
|
||||||
name: "kyoto-kilns-pottery-detail.jpg",
|
|
||||||
date: "Oct 15",
|
|
||||||
size: "5.6 MB",
|
|
||||||
type: "JPEG",
|
|
||||||
dimensions: "4500 × 3000",
|
|
||||||
uploadedBy: "Marcus Chen",
|
|
||||||
uploadedByInitials: "MC",
|
|
||||||
uploadedOn: "Oct 15, 2024",
|
|
||||||
tags: ["ceramics", "kyoto", "craft"],
|
|
||||||
usedIn: [{ title: "Kyoto's Oldest Kilns", role: "Hero Image" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "6",
|
|
||||||
name: "copenhagen-design-week-street.jpg",
|
|
||||||
date: "Oct 12",
|
|
||||||
size: "3.2 MB",
|
|
||||||
type: "JPEG",
|
|
||||||
dimensions: "3600 × 2400",
|
|
||||||
uploadedBy: "Noah Bennett",
|
|
||||||
uploadedByInitials: "NB",
|
|
||||||
uploadedOn: "Oct 12, 2024",
|
|
||||||
tags: ["copenhagen", "design-week", "street"],
|
|
||||||
usedIn: [{ title: "Field Notes from Copenhagen", role: "Inline Gallery" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "7",
|
|
||||||
name: "minimalist-chair-render.webp",
|
|
||||||
date: "Oct 10",
|
|
||||||
size: "0.8 MB",
|
|
||||||
type: "WEBP",
|
|
||||||
dimensions: "2400 × 1600",
|
|
||||||
uploadedBy: "Claire Duval",
|
|
||||||
uploadedByInitials: "CD",
|
|
||||||
uploadedOn: "Oct 10, 2024",
|
|
||||||
tags: ["furniture", "minimalism", "render"],
|
|
||||||
usedIn: [{ title: "The New Vanguard", role: "Product Shot" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "8",
|
|
||||||
name: "autumn-issue-style-guide.pdf",
|
|
||||||
date: "Oct 05",
|
|
||||||
size: "12.4 MB",
|
|
||||||
type: "PDF",
|
|
||||||
dimensions: "N/A",
|
|
||||||
uploadedBy: "Emma Ross",
|
|
||||||
uploadedByInitials: "ER",
|
|
||||||
uploadedOn: "Oct 05, 2024",
|
|
||||||
tags: ["guidelines", "internal", "autumn"],
|
|
||||||
usedIn: [{ title: "Autumn Issue 2024", role: "Reference" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "9",
|
|
||||||
name: "milan-lighting-studio-visit.jpg",
|
|
||||||
date: "Oct 09",
|
|
||||||
size: "6.1 MB",
|
|
||||||
type: "JPEG",
|
|
||||||
dimensions: "5200 × 3466",
|
|
||||||
uploadedBy: "Claire Duval",
|
|
||||||
uploadedByInitials: "CD",
|
|
||||||
uploadedOn: "Oct 09, 2024",
|
|
||||||
tags: ["milan", "lighting", "studio"],
|
|
||||||
usedIn: [
|
|
||||||
{ title: "Milan's Most Elusive Lighting Designer", role: "Hero Image" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "10",
|
|
||||||
name: "lisbon-culinary-scene-raw.webp",
|
|
||||||
date: "Oct 14",
|
|
||||||
size: "2.4 MB",
|
|
||||||
type: "WEBP",
|
|
||||||
dimensions: "3000 × 2000",
|
|
||||||
uploadedBy: "Amara Iqbal",
|
|
||||||
uploadedByInitials: "AI",
|
|
||||||
uploadedOn: "Oct 14, 2024",
|
|
||||||
tags: ["lisbon", "food", "editorial"],
|
|
||||||
usedIn: [
|
|
||||||
{ title: "Lisbon's Quiet Culinary Renaissance", role: "Inline Gallery" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "11",
|
|
||||||
name: "print-magazine-covers-mo...",
|
|
||||||
date: "Sep 26",
|
|
||||||
size: "8.9 MB",
|
|
||||||
type: "PNG",
|
|
||||||
dimensions: "3200 × 2400",
|
|
||||||
uploadedBy: "Mina Okafor",
|
|
||||||
uploadedByInitials: "MO",
|
|
||||||
uploadedOn: "Sep 26, 2024",
|
|
||||||
tags: ["print", "magazine", "covers"],
|
|
||||||
usedIn: [{ title: "The Return of Print", role: "Cover Image" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "12",
|
|
||||||
name: "avant-garde-furniture-trailer.mp4",
|
|
||||||
date: "Sep 30",
|
|
||||||
size: "78.2 MB",
|
|
||||||
type: "MP4",
|
|
||||||
dimensions: "3840 × 2160",
|
|
||||||
duration: "1:12",
|
|
||||||
uploadedBy: "Tommy Rhodes",
|
|
||||||
uploadedByInitials: "TR",
|
|
||||||
uploadedOn: "Sep 30, 2024",
|
|
||||||
tags: ["video", "furniture", "trailer"],
|
|
||||||
usedIn: [
|
|
||||||
{ title: "Collecting the New Avant-Garde", role: "Featured Video" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import { Separator } from "@/styles/base-sera/ui/separator"
|
|
||||||
|
|
||||||
import { AssetDetails } from "./components/asset-details"
|
|
||||||
import { AssetGrid } from "./components/asset-grid"
|
|
||||||
import { PreviewHeader } from "./components/preview-header"
|
|
||||||
import { ASSETS } from "./data"
|
|
||||||
|
|
||||||
export function MediaLibrary() {
|
|
||||||
const [selectedId, setSelectedId] = React.useState<string>(ASSETS[0].id)
|
|
||||||
|
|
||||||
const selectedAsset = React.useMemo(
|
|
||||||
() => ASSETS.find((asset) => asset.id === selectedId) ?? ASSETS[0],
|
|
||||||
[selectedId]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<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)] 2xl:py-8 **:[.container]:px-(--gap)">
|
|
||||||
<PreviewHeader />
|
|
||||||
<Separator className="hidden sm:block" />
|
|
||||||
<div className="container grid grid-cols-1 items-start gap-(--gap) py-(--gap) xl:grid-cols-[minmax(0,1fr)_320px]">
|
|
||||||
<AssetGrid selectedId={selectedId} onSelect={setSelectedId} />
|
|
||||||
<AssetDetails asset={selectedAsset} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 100 KiB |
@@ -1,72 +0,0 @@
|
|||||||
import { type Metadata } from "next"
|
|
||||||
import Link from "next/link"
|
|
||||||
|
|
||||||
import {
|
|
||||||
PageActions,
|
|
||||||
PageHeader,
|
|
||||||
PageHeaderDescription,
|
|
||||||
PageHeaderHeading,
|
|
||||||
} from "@/components/page-header"
|
|
||||||
import { Button } from "@/styles/radix-sera/ui/button"
|
|
||||||
|
|
||||||
import { AudienceAnalytics } from "./audience-analytics"
|
|
||||||
import { LazyPreview } from "./components/lazy-preview"
|
|
||||||
|
|
||||||
import "./style.css"
|
|
||||||
|
|
||||||
import { ArrowRightIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { ImagePreview } from "./components/image-preview"
|
|
||||||
|
|
||||||
const title = "Introducing Sera"
|
|
||||||
const description =
|
|
||||||
"Minimal. Editorial. Typographic. Underline Controls and Uppercase Headings. Shaped by Print Design Principles."
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
openGraph: {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SeraPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader>
|
|
||||||
<PageHeaderHeading className="font-(family-name:--font-playfair-display) text-[2.875rem] tracking-tight!">
|
|
||||||
{title}
|
|
||||||
</PageHeaderHeading>
|
|
||||||
<PageHeaderDescription className="max-w-2xl text-pretty md:text-balance">
|
|
||||||
{description}
|
|
||||||
</PageHeaderDescription>
|
|
||||||
<PageActions className="**:[.container]:justify-start">
|
|
||||||
<Button asChild size="sm">
|
|
||||||
<Link href="/create?preset=b4xFeBLg4O">
|
|
||||||
Open in shadcn/create
|
|
||||||
<ArrowRightIcon data-icon="inline-end" />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</PageActions>
|
|
||||||
</PageHeader>
|
|
||||||
<ImagePreview />
|
|
||||||
<div className="container-wrapper hidden flex-1 flex-col section-soft px-0 md:flex md:px-2 md:py-12">
|
|
||||||
<div className="container flex flex-1 flex-col gap-10 px-0 3xl:max-w-[2000px] md:px-6">
|
|
||||||
<AudienceAnalytics />
|
|
||||||
<LazyPreview name="articleDirectory" />
|
|
||||||
<LazyPreview name="emptyState" />
|
|
||||||
<LazyPreview name="editArticle" />
|
|
||||||
<LazyPreview name="mediaLibrary" />
|
|
||||||
<LazyPreview name="mediaLibraryTable" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <ThemeSwitcher /> */}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,495 +0,0 @@
|
|||||||
@layer base {
|
|
||||||
.preview {
|
|
||||||
--font-sans: var(--font-noto-sans);
|
|
||||||
--font-heading: var(--font-playfair-display);
|
|
||||||
contain-intrinsic-size: auto 900px;
|
|
||||||
content-visibility: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-taupe {
|
|
||||||
--radius: 0;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.147 0.004 49.3);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.147 0.004 49.3);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.147 0.004 49.3);
|
|
||||||
--primary: oklch(0.214 0.009 43.1);
|
|
||||||
--primary-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--secondary: oklch(0.96 0.002 17.2);
|
|
||||||
--secondary-foreground: oklch(0.214 0.009 43.1);
|
|
||||||
--muted: oklch(0.96 0.002 17.2);
|
|
||||||
--muted-foreground: oklch(0.547 0.021 43.1);
|
|
||||||
--accent: oklch(0.96 0.002 17.2);
|
|
||||||
--accent-foreground: oklch(0.214 0.009 43.1);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0.005 34.3);
|
|
||||||
--input: oklch(0.922 0.005 34.3);
|
|
||||||
--ring: oklch(0.714 0.014 41.2);
|
|
||||||
--chart-1: oklch(0.868 0.007 39.5);
|
|
||||||
--chart-2: oklch(0.547 0.021 43.1);
|
|
||||||
--chart-3: oklch(0.438 0.017 39.3);
|
|
||||||
--chart-4: oklch(0.367 0.016 35.7);
|
|
||||||
--chart-5: oklch(0.268 0.011 36.5);
|
|
||||||
--sidebar: oklch(0.986 0.002 67.8);
|
|
||||||
--sidebar-foreground: oklch(0.147 0.004 49.3);
|
|
||||||
--sidebar-primary: oklch(0.214 0.009 43.1);
|
|
||||||
--sidebar-primary-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--sidebar-accent: oklch(0.96 0.002 17.2);
|
|
||||||
--sidebar-accent-foreground: oklch(0.214 0.009 43.1);
|
|
||||||
--sidebar-border: oklch(0.922 0.005 34.3);
|
|
||||||
--sidebar-ring: oklch(0.714 0.014 41.2);
|
|
||||||
|
|
||||||
.dark & {
|
|
||||||
--background: oklch(0.147 0.004 49.3);
|
|
||||||
--foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--card: oklch(0.214 0.009 43.1);
|
|
||||||
--card-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--popover: oklch(0.214 0.009 43.1);
|
|
||||||
--popover-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--primary: oklch(0.922 0.005 34.3);
|
|
||||||
--primary-foreground: oklch(0.214 0.009 43.1);
|
|
||||||
--secondary: oklch(0.268 0.011 36.5);
|
|
||||||
--secondary-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--muted: oklch(0.268 0.011 36.5);
|
|
||||||
--muted-foreground: oklch(0.714 0.014 41.2);
|
|
||||||
--accent: oklch(0.268 0.011 36.5);
|
|
||||||
--accent-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.547 0.021 43.1);
|
|
||||||
--chart-1: oklch(0.868 0.007 39.5);
|
|
||||||
--chart-2: oklch(0.547 0.021 43.1);
|
|
||||||
--chart-3: oklch(0.438 0.017 39.3);
|
|
||||||
--chart-4: oklch(0.367 0.016 35.7);
|
|
||||||
--chart-5: oklch(0.268 0.011 36.5);
|
|
||||||
--sidebar: oklch(0.214 0.009 43.1);
|
|
||||||
--sidebar-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--sidebar-accent: oklch(0.268 0.011 36.5);
|
|
||||||
--sidebar-accent-foreground: oklch(0.986 0.002 67.8);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.547 0.021 43.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-neutral {
|
|
||||||
--radius: 0;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.145 0 0);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.145 0 0);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
|
||||||
--primary: oklch(0.205 0 0);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.97 0 0);
|
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
|
||||||
--muted: oklch(0.97 0 0);
|
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
|
||||||
--accent: oklch(0.97 0 0);
|
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0 0);
|
|
||||||
--input: oklch(0.922 0 0);
|
|
||||||
--ring: oklch(0.708 0 0);
|
|
||||||
--chart-1: oklch(0.87 0 0);
|
|
||||||
--chart-2: oklch(0.556 0 0);
|
|
||||||
--chart-3: oklch(0.439 0 0);
|
|
||||||
--chart-4: oklch(0.371 0 0);
|
|
||||||
--chart-5: oklch(0.269 0 0);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
|
||||||
|
|
||||||
.dark & {
|
|
||||||
--background: oklch(0.145 0 0);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.205 0 0);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.205 0 0);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.922 0 0);
|
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
|
||||||
--secondary: oklch(0.269 0 0);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.269 0 0);
|
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
|
||||||
--accent: oklch(0.269 0 0);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.556 0 0);
|
|
||||||
--chart-1: oklch(0.87 0 0);
|
|
||||||
--chart-2: oklch(0.556 0 0);
|
|
||||||
--chart-3: oklch(0.439 0 0);
|
|
||||||
--chart-4: oklch(0.371 0 0);
|
|
||||||
--chart-5: oklch(0.269 0 0);
|
|
||||||
--sidebar: oklch(0.205 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-stone {
|
|
||||||
--radius: 0;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.147 0.004 49.25);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.147 0.004 49.25);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.147 0.004 49.25);
|
|
||||||
--primary: oklch(0.216 0.006 56.043);
|
|
||||||
--primary-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--secondary: oklch(0.97 0.001 106.424);
|
|
||||||
--secondary-foreground: oklch(0.216 0.006 56.043);
|
|
||||||
--muted: oklch(0.97 0.001 106.424);
|
|
||||||
--muted-foreground: oklch(0.553 0.013 58.071);
|
|
||||||
--accent: oklch(0.97 0.001 106.424);
|
|
||||||
--accent-foreground: oklch(0.216 0.006 56.043);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.923 0.003 48.717);
|
|
||||||
--input: oklch(0.923 0.003 48.717);
|
|
||||||
--ring: oklch(0.709 0.01 56.259);
|
|
||||||
--chart-1: oklch(0.869 0.005 56.366);
|
|
||||||
--chart-2: oklch(0.553 0.013 58.071);
|
|
||||||
--chart-3: oklch(0.444 0.011 73.639);
|
|
||||||
--chart-4: oklch(0.374 0.01 67.558);
|
|
||||||
--chart-5: oklch(0.268 0.007 34.298);
|
|
||||||
--sidebar: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-foreground: oklch(0.147 0.004 49.25);
|
|
||||||
--sidebar-primary: oklch(0.216 0.006 56.043);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-accent: oklch(0.97 0.001 106.424);
|
|
||||||
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
|
|
||||||
--sidebar-border: oklch(0.923 0.003 48.717);
|
|
||||||
--sidebar-ring: oklch(0.709 0.01 56.259);
|
|
||||||
|
|
||||||
.dark & {
|
|
||||||
--background: oklch(0.147 0.004 49.25);
|
|
||||||
--foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--card: oklch(0.216 0.006 56.043);
|
|
||||||
--card-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--popover: oklch(0.216 0.006 56.043);
|
|
||||||
--popover-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--primary: oklch(0.923 0.003 48.717);
|
|
||||||
--primary-foreground: oklch(0.216 0.006 56.043);
|
|
||||||
--secondary: oklch(0.268 0.007 34.298);
|
|
||||||
--secondary-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--muted: oklch(0.268 0.007 34.298);
|
|
||||||
--muted-foreground: oklch(0.709 0.01 56.259);
|
|
||||||
--accent: oklch(0.268 0.007 34.298);
|
|
||||||
--accent-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.553 0.013 58.071);
|
|
||||||
--chart-1: oklch(0.869 0.005 56.366);
|
|
||||||
--chart-2: oklch(0.553 0.013 58.071);
|
|
||||||
--chart-3: oklch(0.444 0.011 73.639);
|
|
||||||
--chart-4: oklch(0.374 0.01 67.558);
|
|
||||||
--chart-5: oklch(0.268 0.007 34.298);
|
|
||||||
--sidebar: oklch(0.216 0.006 56.043);
|
|
||||||
--sidebar-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-accent: oklch(0.268 0.007 34.298);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.553 0.013 58.071);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-zinc {
|
|
||||||
--radius: 0;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--primary: oklch(0.21 0.006 285.885);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.967 0.001 286.375);
|
|
||||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--muted: oklch(0.967 0.001 286.375);
|
|
||||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
||||||
--accent: oklch(0.967 0.001 286.375);
|
|
||||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.92 0.004 286.32);
|
|
||||||
--input: oklch(0.92 0.004 286.32);
|
|
||||||
--ring: oklch(0.705 0.015 286.067);
|
|
||||||
--chart-1: oklch(0.871 0.006 286.286);
|
|
||||||
--chart-2: oklch(0.552 0.016 285.938);
|
|
||||||
--chart-3: oklch(0.442 0.017 285.786);
|
|
||||||
--chart-4: oklch(0.37 0.013 285.805);
|
|
||||||
--chart-5: oklch(0.274 0.006 286.033);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
||||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
||||||
--sidebar-ring: oklch(0.705 0.015 286.067);
|
|
||||||
|
|
||||||
.dark & {
|
|
||||||
--background: oklch(0.141 0.005 285.823);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.21 0.006 285.885);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.21 0.006 285.885);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.92 0.004 286.32);
|
|
||||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--secondary: oklch(0.274 0.006 286.033);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.274 0.006 286.033);
|
|
||||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
||||||
--accent: oklch(0.274 0.006 286.033);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.552 0.016 285.938);
|
|
||||||
--chart-1: oklch(0.871 0.006 286.286);
|
|
||||||
--chart-2: oklch(0.552 0.016 285.938);
|
|
||||||
--chart-3: oklch(0.442 0.017 285.786);
|
|
||||||
--chart-4: oklch(0.37 0.013 285.805);
|
|
||||||
--chart-5: oklch(0.274 0.006 286.033);
|
|
||||||
--sidebar: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.552 0.016 285.938);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-mauve {
|
|
||||||
--radius: 0;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.145 0.008 326);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.145 0.008 326);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.145 0.008 326);
|
|
||||||
--primary: oklch(0.212 0.019 322.12);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.96 0.003 325.6);
|
|
||||||
--secondary-foreground: oklch(0.212 0.019 322.12);
|
|
||||||
--muted: oklch(0.96 0.003 325.6);
|
|
||||||
--muted-foreground: oklch(0.542 0.034 322.5);
|
|
||||||
--accent: oklch(0.96 0.003 325.6);
|
|
||||||
--accent-foreground: oklch(0.212 0.019 322.12);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0.005 325.62);
|
|
||||||
--input: oklch(0.922 0.005 325.62);
|
|
||||||
--ring: oklch(0.711 0.019 323.02);
|
|
||||||
--chart-1: oklch(0.865 0.012 325.68);
|
|
||||||
--chart-2: oklch(0.542 0.034 322.5);
|
|
||||||
--chart-3: oklch(0.435 0.029 321.78);
|
|
||||||
--chart-4: oklch(0.364 0.029 323.89);
|
|
||||||
--chart-5: oklch(0.263 0.024 320.12);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.145 0.008 326);
|
|
||||||
--sidebar-primary: oklch(0.212 0.019 322.12);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.96 0.003 325.6);
|
|
||||||
--sidebar-accent-foreground: oklch(0.212 0.019 322.12);
|
|
||||||
--sidebar-border: oklch(0.922 0.005 325.62);
|
|
||||||
--sidebar-ring: oklch(0.711 0.019 323.02);
|
|
||||||
|
|
||||||
.dark & {
|
|
||||||
--background: oklch(0.145 0.008 326);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.212 0.019 322.12);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.212 0.019 322.12);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.922 0.005 325.62);
|
|
||||||
--primary-foreground: oklch(0.212 0.019 322.12);
|
|
||||||
--secondary: oklch(0.263 0.024 320.12);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.263 0.024 320.12);
|
|
||||||
--muted-foreground: oklch(0.711 0.019 323.02);
|
|
||||||
--accent: oklch(0.263 0.024 320.12);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.542 0.034 322.5);
|
|
||||||
--chart-1: oklch(0.865 0.012 325.68);
|
|
||||||
--chart-2: oklch(0.542 0.034 322.5);
|
|
||||||
--chart-3: oklch(0.435 0.029 321.78);
|
|
||||||
--chart-4: oklch(0.364 0.029 323.89);
|
|
||||||
--chart-5: oklch(0.263 0.024 320.12);
|
|
||||||
--sidebar: oklch(0.212 0.019 322.12);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.263 0.024 320.12);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.542 0.034 322.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-olive {
|
|
||||||
--radius: 0;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.153 0.006 107.1);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.153 0.006 107.1);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.153 0.006 107.1);
|
|
||||||
--primary: oklch(0.228 0.013 107.4);
|
|
||||||
--primary-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--secondary: oklch(0.966 0.005 106.5);
|
|
||||||
--secondary-foreground: oklch(0.228 0.013 107.4);
|
|
||||||
--muted: oklch(0.966 0.005 106.5);
|
|
||||||
--muted-foreground: oklch(0.58 0.031 107.3);
|
|
||||||
--accent: oklch(0.966 0.005 106.5);
|
|
||||||
--accent-foreground: oklch(0.228 0.013 107.4);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.93 0.007 106.5);
|
|
||||||
--input: oklch(0.93 0.007 106.5);
|
|
||||||
--ring: oklch(0.737 0.021 106.9);
|
|
||||||
--chart-1: oklch(0.88 0.011 106.6);
|
|
||||||
--chart-2: oklch(0.58 0.031 107.3);
|
|
||||||
--chart-3: oklch(0.466 0.025 107.3);
|
|
||||||
--chart-4: oklch(0.394 0.023 107.4);
|
|
||||||
--chart-5: oklch(0.286 0.016 107.4);
|
|
||||||
--sidebar: oklch(0.988 0.003 106.5);
|
|
||||||
--sidebar-foreground: oklch(0.153 0.006 107.1);
|
|
||||||
--sidebar-primary: oklch(0.228 0.013 107.4);
|
|
||||||
--sidebar-primary-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--sidebar-accent: oklch(0.966 0.005 106.5);
|
|
||||||
--sidebar-accent-foreground: oklch(0.228 0.013 107.4);
|
|
||||||
--sidebar-border: oklch(0.93 0.007 106.5);
|
|
||||||
--sidebar-ring: oklch(0.737 0.021 106.9);
|
|
||||||
|
|
||||||
.dark & {
|
|
||||||
--background: oklch(0.153 0.006 107.1);
|
|
||||||
--foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--card: oklch(0.228 0.013 107.4);
|
|
||||||
--card-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--popover: oklch(0.228 0.013 107.4);
|
|
||||||
--popover-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--primary: oklch(0.93 0.007 106.5);
|
|
||||||
--primary-foreground: oklch(0.228 0.013 107.4);
|
|
||||||
--secondary: oklch(0.286 0.016 107.4);
|
|
||||||
--secondary-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--muted: oklch(0.286 0.016 107.4);
|
|
||||||
--muted-foreground: oklch(0.737 0.021 106.9);
|
|
||||||
--accent: oklch(0.286 0.016 107.4);
|
|
||||||
--accent-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.58 0.031 107.3);
|
|
||||||
--chart-1: oklch(0.88 0.011 106.6);
|
|
||||||
--chart-2: oklch(0.58 0.031 107.3);
|
|
||||||
--chart-3: oklch(0.466 0.025 107.3);
|
|
||||||
--chart-4: oklch(0.394 0.023 107.4);
|
|
||||||
--chart-5: oklch(0.286 0.016 107.4);
|
|
||||||
--sidebar: oklch(0.228 0.013 107.4);
|
|
||||||
--sidebar-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--sidebar-accent: oklch(0.286 0.016 107.4);
|
|
||||||
--sidebar-accent-foreground: oklch(0.988 0.003 106.5);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.58 0.031 107.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-mist {
|
|
||||||
--radius: 0;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.148 0.004 228.8);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.148 0.004 228.8);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.148 0.004 228.8);
|
|
||||||
--primary: oklch(0.218 0.008 223.9);
|
|
||||||
--primary-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--secondary: oklch(0.963 0.002 197.1);
|
|
||||||
--secondary-foreground: oklch(0.218 0.008 223.9);
|
|
||||||
--muted: oklch(0.963 0.002 197.1);
|
|
||||||
--muted-foreground: oklch(0.56 0.021 213.5);
|
|
||||||
--accent: oklch(0.963 0.002 197.1);
|
|
||||||
--accent-foreground: oklch(0.218 0.008 223.9);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.925 0.005 214.3);
|
|
||||||
--input: oklch(0.925 0.005 214.3);
|
|
||||||
--ring: oklch(0.723 0.014 214.4);
|
|
||||||
--chart-1: oklch(0.872 0.007 219.6);
|
|
||||||
--chart-2: oklch(0.56 0.021 213.5);
|
|
||||||
--chart-3: oklch(0.45 0.017 213.2);
|
|
||||||
--chart-4: oklch(0.378 0.015 216);
|
|
||||||
--chart-5: oklch(0.275 0.011 216.9);
|
|
||||||
--sidebar: oklch(0.987 0.002 197.1);
|
|
||||||
--sidebar-foreground: oklch(0.148 0.004 228.8);
|
|
||||||
--sidebar-primary: oklch(0.218 0.008 223.9);
|
|
||||||
--sidebar-primary-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--sidebar-accent: oklch(0.963 0.002 197.1);
|
|
||||||
--sidebar-accent-foreground: oklch(0.218 0.008 223.9);
|
|
||||||
--sidebar-border: oklch(0.925 0.005 214.3);
|
|
||||||
--sidebar-ring: oklch(0.723 0.014 214.4);
|
|
||||||
|
|
||||||
.dark & {
|
|
||||||
--background: oklch(0.148 0.004 228.8);
|
|
||||||
--foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--card: oklch(0.218 0.008 223.9);
|
|
||||||
--card-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--popover: oklch(0.218 0.008 223.9);
|
|
||||||
--popover-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--primary: oklch(0.925 0.005 214.3);
|
|
||||||
--primary-foreground: oklch(0.218 0.008 223.9);
|
|
||||||
--secondary: oklch(0.275 0.011 216.9);
|
|
||||||
--secondary-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--muted: oklch(0.275 0.011 216.9);
|
|
||||||
--muted-foreground: oklch(0.723 0.014 214.4);
|
|
||||||
--accent: oklch(0.275 0.011 216.9);
|
|
||||||
--accent-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.56 0.021 213.5);
|
|
||||||
--chart-1: oklch(0.872 0.007 219.6);
|
|
||||||
--chart-2: oklch(0.56 0.021 213.5);
|
|
||||||
--chart-3: oklch(0.45 0.017 213.2);
|
|
||||||
--chart-4: oklch(0.378 0.015 216);
|
|
||||||
--chart-5: oklch(0.275 0.011 216.9);
|
|
||||||
--sidebar: oklch(0.218 0.008 223.9);
|
|
||||||
--sidebar-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--sidebar-accent: oklch(0.275 0.011 216.9);
|
|
||||||
--sidebar-accent-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.56 0.021 213.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@utility font-heading {
|
|
||||||
font-family: var(--font-serif);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 100 KiB |
@@ -5,7 +5,6 @@ import * as React from "react"
|
|||||||
import {
|
import {
|
||||||
buildRegistryTheme,
|
buildRegistryTheme,
|
||||||
DEFAULT_CONFIG,
|
DEFAULT_CONFIG,
|
||||||
POINTER_CURSOR_SELECTOR,
|
|
||||||
type DesignSystemConfig,
|
type DesignSystemConfig,
|
||||||
} from "@/registry/config"
|
} from "@/registry/config"
|
||||||
import { useIframeMessageListener } from "@/app/(app)/create/hooks/use-iframe-sync"
|
import { useIframeMessageListener } from "@/app/(app)/create/hooks/use-iframe-sync"
|
||||||
@@ -17,12 +16,6 @@ import {
|
|||||||
|
|
||||||
const THEME_STYLE_ELEMENT_ID = "design-system-theme-vars"
|
const THEME_STYLE_ELEMENT_ID = "design-system-theme-vars"
|
||||||
const MANAGED_BODY_CLASS_PREFIXES = ["style-", "base-color-"] as const
|
const MANAGED_BODY_CLASS_PREFIXES = ["style-", "base-color-"] as const
|
||||||
const POINTER_CURSOR_CSS = `@layer base {
|
|
||||||
${POINTER_CURSOR_SELECTOR} {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
type RegistryThemeCssVars = NonNullable<
|
type RegistryThemeCssVars = NonNullable<
|
||||||
ReturnType<typeof buildRegistryTheme>["cssVars"]
|
ReturnType<typeof buildRegistryTheme>["cssVars"]
|
||||||
@@ -51,17 +44,14 @@ function buildCssRule(selector: string, cssVars?: Record<string, string>) {
|
|||||||
return `${selector} {\n${declarations}\n}\n`
|
return `${selector} {\n${declarations}\n}\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildThemeCssText(cssVars: RegistryThemeCssVars, pointer: boolean) {
|
function buildThemeCssText(cssVars: RegistryThemeCssVars) {
|
||||||
return [
|
return [
|
||||||
buildCssRule(":root", {
|
buildCssRule(":root", {
|
||||||
...(cssVars.theme ?? {}),
|
...(cssVars.theme ?? {}),
|
||||||
...(cssVars.light ?? {}),
|
...(cssVars.light ?? {}),
|
||||||
}),
|
}),
|
||||||
buildCssRule(".dark", cssVars.dark),
|
buildCssRule(".dark", cssVars.dark),
|
||||||
pointer ? POINTER_CURSOR_CSS : "",
|
].join("\n")
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join("\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DesignSystemProvider({
|
export function DesignSystemProvider({
|
||||||
@@ -83,7 +73,6 @@ export function DesignSystemProvider({
|
|||||||
chartColor,
|
chartColor,
|
||||||
menuAccent,
|
menuAccent,
|
||||||
menuColor,
|
menuColor,
|
||||||
pointer,
|
|
||||||
radius,
|
radius,
|
||||||
} = searchParams
|
} = searchParams
|
||||||
const effectiveRadius = style === "lyra" ? "none" : radius
|
const effectiveRadius = style === "lyra" ? "none" : radius
|
||||||
@@ -141,7 +130,7 @@ export function DesignSystemProvider({
|
|||||||
useIframeMessageListener("design-system-params", handleDesignSystemMessage)
|
useIframeMessageListener("design-system-params", handleDesignSystemMessage)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (style === "lyra" || (style === "sera" && radius !== "none")) {
|
if (style === "lyra" && radius !== "none") {
|
||||||
setSearchParams({ radius: "none" })
|
setSearchParams({ radius: "none" })
|
||||||
}
|
}
|
||||||
}, [style, radius, setSearchParams])
|
}, [style, radius, setSearchParams])
|
||||||
@@ -219,8 +208,8 @@ export function DesignSystemProvider({
|
|||||||
document.head.appendChild(styleElement)
|
document.head.appendChild(styleElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
styleElement.textContent = buildThemeCssText(registryTheme.cssVars, pointer)
|
styleElement.textContent = buildThemeCssText(registryTheme.cssVars)
|
||||||
}, [registryTheme, pointer])
|
}, [registryTheme])
|
||||||
|
|
||||||
// Handle menu color inversion by adding/removing dark class to elements with cn-menu-target.
|
// Handle menu color inversion by adding/removing dark class to elements with cn-menu-target.
|
||||||
// useLayoutEffect to apply classes synchronously before paint, avoiding flash.
|
// useLayoutEffect to apply classes synchronously before paint, avoiding flash.
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {
|
import { Copy01Icon, Globe02Icon, Tick02Icon } from "@hugeicons/core-free-icons"
|
||||||
Copy01Icon,
|
|
||||||
Globe02Icon,
|
|
||||||
HandPointingRight04Icon,
|
|
||||||
Tick02Icon,
|
|
||||||
} from "@hugeicons/core-free-icons"
|
|
||||||
import { HugeiconsIcon } from "@hugeicons/react"
|
import { HugeiconsIcon } from "@hugeicons/react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -90,8 +85,7 @@ export function ProjectForm({
|
|||||||
const templateFlag = ` --template ${framework}`
|
const templateFlag = ` --template ${framework}`
|
||||||
const monorepoFlag = isMonorepo ? " --monorepo" : ""
|
const monorepoFlag = isMonorepo ? " --monorepo" : ""
|
||||||
const rtlFlag = params.rtl ? " --rtl" : ""
|
const rtlFlag = params.rtl ? " --rtl" : ""
|
||||||
const pointerFlag = params.pointer ? " --pointer" : ""
|
const flags = `${presetFlag}${baseFlag}${templateFlag}${monorepoFlag}${rtlFlag}`
|
||||||
const flags = `${presetFlag}${baseFlag}${templateFlag}${monorepoFlag}${rtlFlag}${pointerFlag}`
|
|
||||||
|
|
||||||
return IS_LOCAL_DEV
|
return IS_LOCAL_DEV
|
||||||
? {
|
? {
|
||||||
@@ -106,14 +100,7 @@ export function ProjectForm({
|
|||||||
yarn: `yarn dlx shadcn${SHADCN_VERSION} init${flags}`,
|
yarn: `yarn dlx shadcn${SHADCN_VERSION} init${flags}`,
|
||||||
bun: `bunx --bun shadcn${SHADCN_VERSION} init${flags}`,
|
bun: `bunx --bun shadcn${SHADCN_VERSION} init${flags}`,
|
||||||
}
|
}
|
||||||
}, [
|
}, [framework, isMonorepo, params.base, params.rtl, presetCode])
|
||||||
framework,
|
|
||||||
isMonorepo,
|
|
||||||
params.base,
|
|
||||||
params.pointer,
|
|
||||||
params.rtl,
|
|
||||||
presetCode,
|
|
||||||
])
|
|
||||||
|
|
||||||
const command = commands[packageManager]
|
const command = commands[packageManager]
|
||||||
|
|
||||||
@@ -167,23 +154,6 @@ export function ProjectForm({
|
|||||||
<FieldLegend variant="label" className="sr-only">
|
<FieldLegend variant="label" className="sr-only">
|
||||||
Options
|
Options
|
||||||
</FieldLegend>
|
</FieldLegend>
|
||||||
<Field orientation="horizontal">
|
|
||||||
<FieldLabel htmlFor="pointer">
|
|
||||||
<HugeiconsIcon
|
|
||||||
icon={HandPointingRight04Icon}
|
|
||||||
className="size-4 -rotate-90"
|
|
||||||
/>
|
|
||||||
Use pointer on buttons
|
|
||||||
</FieldLabel>
|
|
||||||
<Switch
|
|
||||||
id="pointer"
|
|
||||||
checked={params.pointer}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setParams({ pointer: checked === true })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<FieldSeparator className="-mx-6" />
|
|
||||||
<Field
|
<Field
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
data-disabled={hasMonorepo ? undefined : "true"}
|
data-disabled={hasMonorepo ? undefined : "true"}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function RadiusPicker({
|
|||||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||||
}) {
|
}) {
|
||||||
const [params, setParams] = useDesignSystemSearchParams()
|
const [params, setParams] = useDesignSystemSearchParams()
|
||||||
const isRadiusLocked = params.style === "lyra" || params.style === "sera"
|
const isRadiusLocked = params.style === "lyra"
|
||||||
const selectedRadiusName = isRadiusLocked ? "none" : params.radius
|
const selectedRadiusName = isRadiusLocked ? "none" : params.radius
|
||||||
|
|
||||||
const currentRadius = RADII.find(
|
const currentRadius = RADII.find(
|
||||||
|
|||||||
@@ -16,17 +16,8 @@ export function ShareButton() {
|
|||||||
|
|
||||||
const shareUrl = React.useMemo(() => {
|
const shareUrl = React.useMemo(() => {
|
||||||
const origin = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"
|
const origin = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"
|
||||||
const searchParams = new URLSearchParams({
|
return `${origin}/create?preset=${presetCode}&item=${params.item}`
|
||||||
preset: presetCode,
|
}, [presetCode, params.item])
|
||||||
item: params.item,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (params.pointer) {
|
|
||||||
searchParams.set("pointer", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${origin}/create?${searchParams.toString()}`
|
|
||||||
}, [params.item, params.pointer, presetCode])
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (hasCopied) {
|
if (hasCopied) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { PRESETS, type Style, type StyleName } from "@/registry/config"
|
import { type Style, type StyleName } from "@/registry/config"
|
||||||
import { LockButton } from "@/app/(app)/create/components/lock-button"
|
import { LockButton } from "@/app/(app)/create/components/lock-button"
|
||||||
import {
|
import {
|
||||||
Picker,
|
Picker,
|
||||||
@@ -53,24 +53,7 @@ export function StylePicker({
|
|||||||
<PickerRadioGroup
|
<PickerRadioGroup
|
||||||
value={currentStyle?.name}
|
value={currentStyle?.name}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const styleName = value as StyleName
|
setParams({ style: value as StyleName })
|
||||||
const preset = PRESETS.find(
|
|
||||||
(p) => p.base === params.base && p.style === styleName
|
|
||||||
)
|
|
||||||
setParams({
|
|
||||||
style: styleName,
|
|
||||||
...(preset && {
|
|
||||||
baseColor: preset.baseColor,
|
|
||||||
theme: preset.theme,
|
|
||||||
chartColor: preset.chartColor,
|
|
||||||
iconLibrary: preset.iconLibrary,
|
|
||||||
font: preset.font,
|
|
||||||
fontHeading: preset.fontHeading,
|
|
||||||
menuAccent: preset.menuAccent,
|
|
||||||
menuColor: preset.menuColor,
|
|
||||||
radius: preset.radius,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PickerGroup>
|
<PickerGroup>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
DM_Sans,
|
DM_Sans,
|
||||||
EB_Garamond,
|
|
||||||
Figtree,
|
Figtree,
|
||||||
Geist,
|
Geist,
|
||||||
Geist_Mono,
|
Geist_Mono,
|
||||||
IBM_Plex_Sans,
|
IBM_Plex_Sans,
|
||||||
Instrument_Sans,
|
Instrument_Sans,
|
||||||
Instrument_Serif,
|
|
||||||
Inter,
|
Inter,
|
||||||
JetBrains_Mono,
|
JetBrains_Mono,
|
||||||
Lora,
|
Lora,
|
||||||
@@ -151,17 +149,6 @@ const playfairDisplay = Playfair_Display({
|
|||||||
variable: "--font-playfair-display",
|
variable: "--font-playfair-display",
|
||||||
})
|
})
|
||||||
|
|
||||||
const ebGaramond = EB_Garamond({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-eb-garamond",
|
|
||||||
})
|
|
||||||
|
|
||||||
const instrumentSerif = Instrument_Serif({
|
|
||||||
subsets: ["latin"],
|
|
||||||
weight: "400",
|
|
||||||
variable: "--font-instrument-serif",
|
|
||||||
})
|
|
||||||
|
|
||||||
const PREVIEW_FONTS = {
|
const PREVIEW_FONTS = {
|
||||||
geist: geistSans,
|
geist: geistSans,
|
||||||
inter,
|
inter,
|
||||||
@@ -187,8 +174,6 @@ const PREVIEW_FONTS = {
|
|||||||
merriweather,
|
merriweather,
|
||||||
lora,
|
lora,
|
||||||
"playfair-display": playfairDisplay,
|
"playfair-display": playfairDisplay,
|
||||||
"eb-garamond": ebGaramond,
|
|
||||||
"instrument-serif": instrumentSerif,
|
|
||||||
} satisfies Record<FontName, PreviewFont>
|
} satisfies Record<FontName, PreviewFont>
|
||||||
|
|
||||||
function createFontOption(name: FontName) {
|
function createFontOption(name: FontName) {
|
||||||
@@ -231,8 +216,6 @@ export const FONTS = [
|
|||||||
createFontOption("merriweather"),
|
createFontOption("merriweather"),
|
||||||
createFontOption("lora"),
|
createFontOption("lora"),
|
||||||
createFontOption("playfair-display"),
|
createFontOption("playfair-display"),
|
||||||
createFontOption("eb-garamond"),
|
|
||||||
createFontOption("instrument-serif"),
|
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type Font = (typeof FONTS)[number]
|
export type Font = (typeof FONTS)[number]
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ const designSystemSearchParams = {
|
|||||||
"laravel",
|
"laravel",
|
||||||
] as const).withDefault("next"),
|
] as const).withDefault("next"),
|
||||||
rtl: parseAsBoolean.withDefault(false),
|
rtl: parseAsBoolean.withDefault(false),
|
||||||
pointer: parseAsBoolean.withDefault(false),
|
|
||||||
size: parseAsInteger.withDefault(100),
|
size: parseAsInteger.withDefault(100),
|
||||||
custom: parseAsBoolean.withDefault(false),
|
custom: parseAsBoolean.withDefault(false),
|
||||||
}
|
}
|
||||||
@@ -127,7 +126,6 @@ const NON_DESIGN_SYSTEM_KEYS = [
|
|||||||
"preset",
|
"preset",
|
||||||
"template",
|
"template",
|
||||||
"rtl",
|
"rtl",
|
||||||
"pointer",
|
|
||||||
"size",
|
"size",
|
||||||
"custom",
|
"custom",
|
||||||
] as const
|
] as const
|
||||||
@@ -226,7 +224,6 @@ function resolvePresetParams(
|
|||||||
preset: rawParams.preset,
|
preset: rawParams.preset,
|
||||||
template: rawParams.template,
|
template: rawParams.template,
|
||||||
rtl: rawParams.rtl,
|
rtl: rawParams.rtl,
|
||||||
pointer: rawParams.pointer,
|
|
||||||
size: rawParams.size,
|
size: rawParams.size,
|
||||||
custom: rawParams.custom,
|
custom: rawParams.custom,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Button } from "@/styles/radix-nova/ui/button"
|
|||||||
export const revalidate = false
|
export const revalidate = false
|
||||||
export const dynamic = "force-static"
|
export const dynamic = "force-static"
|
||||||
|
|
||||||
const NUMBER_OF_LATEST_PAGES = 5
|
const NUMBER_OF_LATEST_PAGES = 2
|
||||||
|
|
||||||
export function generateMetadata() {
|
export function generateMetadata() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,51 +3,6 @@ import { describe, expect, it } from "vitest"
|
|||||||
import { parseDesignSystemConfig } from "./parse-config"
|
import { parseDesignSystemConfig } from "./parse-config"
|
||||||
|
|
||||||
describe("parseDesignSystemConfig", () => {
|
describe("parseDesignSystemConfig", () => {
|
||||||
it("defaults pointer to false when omitted", () => {
|
|
||||||
const result = parseDesignSystemConfig(
|
|
||||||
new URLSearchParams(
|
|
||||||
"base=base&style=sera&baseColor=taupe&theme=taupe&iconLibrary=lucide&font=noto-sans&rtl=false&menuAccent=subtle&menuColor=default&radius=default&fontHeading=playfair-display&template=vite&track=1"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.success).toBe(true)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(result.data.pointer).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("parses pointer=true", () => {
|
|
||||||
const result = parseDesignSystemConfig(
|
|
||||||
new URLSearchParams(
|
|
||||||
"base=base&style=sera&baseColor=taupe&theme=taupe&iconLibrary=lucide&font=noto-sans&rtl=false&pointer=true&menuAccent=subtle&menuColor=default&radius=default&fontHeading=playfair-display&template=vite&track=1"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.success).toBe(true)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(result.data.pointer).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("defaults missing chartColor from the selected theme", () => {
|
|
||||||
const result = parseDesignSystemConfig(
|
|
||||||
new URLSearchParams(
|
|
||||||
"base=base&style=sera&baseColor=taupe&theme=taupe&iconLibrary=lucide&font=noto-sans&rtl=false&menuAccent=subtle&menuColor=default&radius=default&fontHeading=playfair-display&template=vite&track=1"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.success).toBe(true)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(result.data.chartColor).toBe("taupe")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("honors explicit fontHeading and chartColor overrides when a preset is present", () => {
|
it("honors explicit fontHeading and chartColor overrides when a preset is present", () => {
|
||||||
const result = parseDesignSystemConfig(
|
const result = parseDesignSystemConfig(
|
||||||
new URLSearchParams(
|
new URLSearchParams(
|
||||||
@@ -63,17 +18,4 @@ describe("parseDesignSystemConfig", () => {
|
|||||||
expect(result.data.fontHeading).toBe("playfair-display")
|
expect(result.data.fontHeading).toBe("playfair-display")
|
||||||
expect(result.data.chartColor).toBe("emerald")
|
expect(result.data.chartColor).toBe("emerald")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("keeps pointer outside preset decoding", () => {
|
|
||||||
const result = parseDesignSystemConfig(
|
|
||||||
new URLSearchParams("preset=a0&pointer=true")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.success).toBe(true)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(result.data.pointer).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export function parseDesignSystemConfig(searchParams: URLSearchParams) {
|
|||||||
base: searchParams.get("base") ?? "radix",
|
base: searchParams.get("base") ?? "radix",
|
||||||
template: searchParams.get("template") ?? undefined,
|
template: searchParams.get("template") ?? undefined,
|
||||||
rtl: searchParams.get("rtl") === "true",
|
rtl: searchParams.get("rtl") === "true",
|
||||||
pointer: searchParams.get("pointer") === "true",
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
configInput = {
|
configInput = {
|
||||||
@@ -40,7 +39,6 @@ export function parseDesignSystemConfig(searchParams: URLSearchParams) {
|
|||||||
radius: searchParams.get("radius"),
|
radius: searchParams.get("radius"),
|
||||||
template: searchParams.get("template") ?? undefined,
|
template: searchParams.get("template") ?? undefined,
|
||||||
rtl: searchParams.get("rtl") === "true",
|
rtl: searchParams.get("rtl") === "true",
|
||||||
pointer: searchParams.get("pointer") === "true",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import { describe, expect, it } from "vitest"
|
|
||||||
|
|
||||||
import {
|
|
||||||
buildRegistryBase,
|
|
||||||
DEFAULT_CONFIG,
|
|
||||||
POINTER_CURSOR_SELECTOR,
|
|
||||||
} from "@/registry/config"
|
|
||||||
|
|
||||||
import { GET } from "./route"
|
|
||||||
|
|
||||||
function createRequest(search = "") {
|
|
||||||
const searchParams = new URLSearchParams(
|
|
||||||
Object.entries(DEFAULT_CONFIG).map(([key, value]) => [key, String(value)])
|
|
||||||
)
|
|
||||||
const url = new URL(`http://localhost:4000/init${search}`)
|
|
||||||
|
|
||||||
for (const [key, value] of url.searchParams) {
|
|
||||||
searchParams.set(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
nextUrl: new URL(`http://localhost:4000/init?${searchParams}`),
|
|
||||||
} as Parameters<typeof GET>[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("GET /init", () => {
|
|
||||||
it("returns the full registry base when only is omitted", async () => {
|
|
||||||
const response = await GET(createRequest())
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
expect(response.status).toBe(200)
|
|
||||||
expect(json).toEqual(buildRegistryBase(DEFAULT_CONFIG))
|
|
||||||
expect(json.css["@layer base"][POINTER_CURSOR_SELECTOR]).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns pointer cursor css when pointer is enabled", async () => {
|
|
||||||
const response = await GET(createRequest("?pointer=true"))
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
expect(response.status).toBe(200)
|
|
||||||
expect(json.css["@layer base"][POINTER_CURSOR_SELECTOR]).toEqual({
|
|
||||||
cursor: "pointer",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns a sparse registry base when only is provided", async () => {
|
|
||||||
const response = await GET(createRequest("?only=theme"))
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
expect(response.status).toBe(200)
|
|
||||||
expect(json.type).toBe("registry:base")
|
|
||||||
expect(json.config).toEqual({
|
|
||||||
menuColor: "default",
|
|
||||||
menuAccent: "subtle",
|
|
||||||
tailwind: {
|
|
||||||
baseColor: "neutral",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(json.cssVars.light).toBeDefined()
|
|
||||||
expect(json.cssVars.light.radius).toBe("0.625rem")
|
|
||||||
expect(json.dependencies).toBeUndefined()
|
|
||||||
expect(json.registryDependencies).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("rejects unsupported only values", async () => {
|
|
||||||
const response = await GET(createRequest("?only=icon"))
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
expect(response.status).toBe(400)
|
|
||||||
expect(json.error).toBe(
|
|
||||||
"Invalid only value. Use one or more of: theme, font"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -3,11 +3,7 @@ import { track } from "@vercel/analytics/server"
|
|||||||
import { isPresetCode } from "shadcn/preset"
|
import { isPresetCode } from "shadcn/preset"
|
||||||
import { registryItemSchema } from "shadcn/schema"
|
import { registryItemSchema } from "shadcn/schema"
|
||||||
|
|
||||||
import {
|
import { buildRegistryBase } from "@/registry/config"
|
||||||
buildPartialRegistryBase,
|
|
||||||
buildRegistryBase,
|
|
||||||
parseRegistryBaseParts,
|
|
||||||
} from "@/registry/config"
|
|
||||||
import { getPresetCode } from "@/app/(app)/create/lib/preset-code"
|
import { getPresetCode } from "@/app/(app)/create/lib/preset-code"
|
||||||
import { parseDesignSystemConfig } from "@/app/(create)/init/parse-config"
|
import { parseDesignSystemConfig } from "@/app/(create)/init/parse-config"
|
||||||
|
|
||||||
@@ -20,20 +16,13 @@ export async function GET(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: result.error }, { status: 400 })
|
return NextResponse.json({ error: result.error }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const onlyResult = parseRegistryBaseParts(searchParams.get("only"))
|
|
||||||
if (!onlyResult.success) {
|
|
||||||
return NextResponse.json({ error: onlyResult.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawPreset = searchParams.get("preset")
|
const rawPreset = searchParams.get("preset")
|
||||||
const presetCode =
|
const presetCode =
|
||||||
rawPreset && isPresetCode(rawPreset)
|
rawPreset && isPresetCode(rawPreset)
|
||||||
? rawPreset
|
? rawPreset
|
||||||
: getPresetCode(result.data)
|
: getPresetCode(result.data)
|
||||||
|
|
||||||
const registryBase = onlyResult.parts
|
const registryBase = buildRegistryBase(result.data)
|
||||||
? buildPartialRegistryBase(result.data, onlyResult.parts)
|
|
||||||
: buildRegistryBase(result.data)
|
|
||||||
const parseResult = registryItemSchema.safeParse(registryBase)
|
const parseResult = registryItemSchema.safeParse(registryBase)
|
||||||
|
|
||||||
if (!parseResult.success) {
|
if (!parseResult.success) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
@import "../registry/styles/style-maia.css" layer(base);
|
@import "../registry/styles/style-maia.css" layer(base);
|
||||||
@import "../registry/styles/style-mira.css" layer(base);
|
@import "../registry/styles/style-mira.css" layer(base);
|
||||||
@import "../registry/styles/style-luma.css" layer(base);
|
@import "../registry/styles/style-luma.css" layer(base);
|
||||||
@import "../registry/styles/style-sera.css" layer(base);
|
|
||||||
|
|
||||||
@custom-variant style-vega (&:where(.style-vega *));
|
@custom-variant style-vega (&:where(.style-vega *));
|
||||||
@custom-variant style-nova (&:where(.style-nova *));
|
@custom-variant style-nova (&:where(.style-nova *));
|
||||||
@@ -17,7 +16,6 @@
|
|||||||
@custom-variant style-maia (&:where(.style-maia *));
|
@custom-variant style-maia (&:where(.style-maia *));
|
||||||
@custom-variant style-mira (&:where(.style-mira *));
|
@custom-variant style-mira (&:where(.style-mira *));
|
||||||
@custom-variant style-luma (&:where(.style-luma *));
|
@custom-variant style-luma (&:where(.style-luma *));
|
||||||
@custom-variant style-sera (&:where(.style-sera *));
|
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
@custom-variant fixed (&:is(.layout-fixed *));
|
@custom-variant fixed (&:is(.layout-fixed *));
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function Announcement() {
|
|||||||
return (
|
return (
|
||||||
<Badge asChild variant="secondary" className="bg-muted">
|
<Badge asChild variant="secondary" className="bg-muted">
|
||||||
<Link href="/docs/changelog">
|
<Link href="/docs/changelog">
|
||||||
New preset commands <ArrowRightIcon />
|
Introducing Luma <ArrowRightIcon />
|
||||||
</Link>
|
</Link>
|
||||||
</Badge>
|
</Badge>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const SHOW = true
|
const SHOW = false
|
||||||
|
|
||||||
export function TailwindIndicator({
|
export function TailwindIndicator({
|
||||||
forceMount = false,
|
forceMount = false,
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ Options:
|
|||||||
--no-monorepo skip the monorepo prompt.
|
--no-monorepo skip the monorepo prompt.
|
||||||
--rtl enable RTL support.
|
--rtl enable RTL support.
|
||||||
--no-rtl disable RTL support.
|
--no-rtl disable RTL support.
|
||||||
--pointer enable pointer cursor for buttons.
|
|
||||||
--no-pointer disable pointer cursor for buttons.
|
|
||||||
--reinstall re-install existing UI components.
|
--reinstall re-install existing UI components.
|
||||||
--no-reinstall do not re-install existing UI components.
|
--no-reinstall do not re-install existing UI components.
|
||||||
-h, --help display help for command
|
-h, --help display help for command
|
||||||
@@ -92,17 +90,9 @@ Options:
|
|||||||
Use the `apply` command to apply a preset to an existing project.
|
Use the `apply` command to apply a preset to an existing project.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx shadcn@latest apply a2r6bw
|
npx shadcn@latest apply --preset a2r6bw
|
||||||
```
|
```
|
||||||
|
|
||||||
You can apply only the theme or fonts from a preset without reinstalling UI components:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest apply a2r6bw --only theme
|
|
||||||
```
|
|
||||||
|
|
||||||
Supported values for `--only` are `theme` and `font`.
|
|
||||||
|
|
||||||
**Options**
|
**Options**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -115,7 +105,6 @@ Arguments:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--preset <preset> preset configuration to apply
|
--preset <preset> preset configuration to apply
|
||||||
--only [parts] apply only parts of a preset: theme, font
|
|
||||||
-y, --yes skip confirmation prompt. (default: false)
|
-y, --yes skip confirmation prompt. (default: false)
|
||||||
-c, --cwd <cwd> the working directory. defaults to the current directory.
|
-c, --cwd <cwd> the working directory. defaults to the current directory.
|
||||||
-s, --silent mute output. (default: false)
|
-s, --silent mute output. (default: false)
|
||||||
@@ -124,110 +113,6 @@ Options:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## preset
|
|
||||||
|
|
||||||
Use the `preset` command to inspect preset codes and resolve the preset for an existing project.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset decode a2r6bw
|
|
||||||
```
|
|
||||||
|
|
||||||
### preset decode
|
|
||||||
|
|
||||||
Use `preset decode` to decode a preset code.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset decode a2r6bw
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Usage: shadcn preset decode [options] <code>
|
|
||||||
|
|
||||||
decode a preset code
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
code the preset code to decode
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--json output as JSON. (default: false)
|
|
||||||
-h, --help display help for command
|
|
||||||
```
|
|
||||||
|
|
||||||
### preset resolve
|
|
||||||
|
|
||||||
Use `preset resolve` to resolve the preset from the current project.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset resolve
|
|
||||||
```
|
|
||||||
|
|
||||||
The `preset info` command is an alias for `preset resolve`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset info
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Usage: shadcn preset resolve|info [options]
|
|
||||||
|
|
||||||
resolve a preset from your project
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-c, --cwd <cwd> the working directory. defaults to the current directory.
|
|
||||||
--json output as JSON. (default: false)
|
|
||||||
-h, --help display help for command
|
|
||||||
```
|
|
||||||
|
|
||||||
### preset url
|
|
||||||
|
|
||||||
Use `preset url` to print the create URL for a preset code.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset url a2r6bw
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Usage: shadcn preset url [options] <code>
|
|
||||||
|
|
||||||
get the create URL for a preset code
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
code the preset code
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help display help for command
|
|
||||||
```
|
|
||||||
|
|
||||||
### preset open
|
|
||||||
|
|
||||||
Use `preset open` to open a preset code in the browser.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset open a2r6bw
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Usage: shadcn preset open [options] <code>
|
|
||||||
|
|
||||||
open a preset code in the browser
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
code the preset code
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help display help for command
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## view
|
## view
|
||||||
|
|
||||||
Use the `view` command to view items from the registry before installing them.
|
Use the `view` command to view items from the registry before installing them.
|
||||||
|
|||||||
@@ -140,91 +140,15 @@ Setting this option to `false` allows components to be added as JavaScript with
|
|||||||
|
|
||||||
## aliases
|
## aliases
|
||||||
|
|
||||||
The CLI uses these values to place generated components in the correct location and rewrite imports.
|
The CLI uses these values and the `paths` config from your `tsconfig.json` or `jsconfig.json` file to place generated components in the correct location.
|
||||||
|
|
||||||
You can back these aliases with either:
|
Path aliases have to be set up in your `tsconfig.json` or `jsconfig.json` file.
|
||||||
|
|
||||||
1. `compilerOptions.paths` in your `tsconfig.json` or `jsconfig.json`
|
|
||||||
2. `package.json#imports` with TypeScript package import resolution enabled
|
|
||||||
|
|
||||||
The aliases in `components.json` are still required when using the CLI. They tell the CLI which import roots map to `components`, `ui`, `lib`, `hooks`, and `utils`.
|
|
||||||
|
|
||||||
<Callout className="mt-6">
|
<Callout className="mt-6">
|
||||||
**Important:** If you're using package imports, enable
|
**Important:** If you're using the `src` directory, make sure it is included
|
||||||
`resolvePackageJsonImports` and use `moduleResolution: "bundler"` in your
|
under `paths` in your `tsconfig.json` or `jsconfig.json` file.
|
||||||
`tsconfig.json`. If you're using `paths`, make sure your aliases include the
|
|
||||||
`src` directory when applicable.
|
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
### Using `tsconfig` or `jsconfig` paths
|
|
||||||
|
|
||||||
```json title="tsconfig.json"
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using `package.json#imports`
|
|
||||||
|
|
||||||
Recommended setup for a single-package app:
|
|
||||||
|
|
||||||
```json title="package.json"
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json title="tsconfig.json"
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolvePackageJsonImports": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json title="components.json"
|
|
||||||
{
|
|
||||||
"aliases": {
|
|
||||||
"components": "#components",
|
|
||||||
"ui": "#components/ui",
|
|
||||||
"lib": "#lib",
|
|
||||||
"hooks": "#hooks",
|
|
||||||
"utils": "#lib/utils"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The aliases in `components.json` still tell the CLI where to place
|
|
||||||
`components`, `ui`, `lib`, `hooks`, and `utils`. `package.json#imports`
|
|
||||||
provides the runtime and TypeScript resolution for those `#...` specifiers.
|
|
||||||
|
|
||||||
The matched `imports` target also controls whether generated `#...` imports keep
|
|
||||||
file extensions:
|
|
||||||
|
|
||||||
- `"#components/*": "./src/components/*"` preserves source extensions and can
|
|
||||||
generate imports like
|
|
||||||
`#components/button.tsx`
|
|
||||||
- `"#components/*": "./src/components/*.tsx"` strips source extensions and
|
|
||||||
generates imports like
|
|
||||||
`#components/button`
|
|
||||||
|
|
||||||
For monorepos, see the <Link href="/docs/monorepo">monorepo docs</Link>. Local
|
|
||||||
workspace aliases can use `package.json#imports`, while shared workspace
|
|
||||||
imports such as `@workspace/ui/components` are resolved from the target
|
|
||||||
package's `exports`.
|
|
||||||
|
|
||||||
For framework-specific setup, see the [package imports guide](/docs/package-imports).
|
|
||||||
|
|
||||||
### aliases.utils
|
### aliases.utils
|
||||||
|
|
||||||
Import alias for your utility functions.
|
Import alias for your utility functions.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ description: Every component recreated in Figma. With customizable props, typogr
|
|||||||
|
|
||||||
## Free
|
## Free
|
||||||
|
|
||||||
|
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted shadcn/ui kit, MIT licensed, maintained by team of designers, with free design to code plugin
|
||||||
- [shadcn/ui components](https://www.figma.com/community/file/1342715840824755935) by [Sitsiilia Bergmann](https://x.com/sitsiilia) - A well-structured component library aligned with the shadcn component system, regularly maintained.
|
- [shadcn/ui components](https://www.figma.com/community/file/1342715840824755935) by [Sitsiilia Bergmann](https://x.com/sitsiilia) - A well-structured component library aligned with the shadcn component system, regularly maintained.
|
||||||
- [shadcn/ui design system](https://www.figma.com/community/file/1203061493325953101) by [Pietro Schirano](https://twitter.com/skirano) - A design companion for shadcn/ui. Each component was painstakingly crafted to perfectly match the code implementation.
|
- [shadcn/ui design system](https://www.figma.com/community/file/1203061493325953101) by [Pietro Schirano](https://twitter.com/skirano) - A design companion for shadcn/ui. Each component was painstakingly crafted to perfectly match the code implementation.
|
||||||
|
|
||||||
@@ -19,4 +20,3 @@ description: Every component recreated in Figma. With customizable props, typogr
|
|||||||
- [shadcncraft Design System](https://shadcncraft.com) - Production-ready shadcn/ui kit with Pro React blocks and 1:1 Figma alignment. Design and ship faster with tweakcn theming, AI-assisted workflows, and Figma to React export, built for real product UIs.
|
- [shadcncraft Design System](https://shadcncraft.com) - Production-ready shadcn/ui kit with Pro React blocks and 1:1 Figma alignment. Design and ship faster with tweakcn theming, AI-assisted workflows, and Figma to React export, built for real product UIs.
|
||||||
- [shadcn/studio UI Kit](https://shadcnstudio.com/figma) - Accelerate design & development with a shadcn/ui compatible Figma kit with updated components, 550+ blocks, 10+ templates, 20+ themes, and an AI tool that converts designs into shadcn/ui code.
|
- [shadcn/studio UI Kit](https://shadcnstudio.com/figma) - Accelerate design & development with a shadcn/ui compatible Figma kit with updated components, 550+ blocks, 10+ templates, 20+ themes, and an AI tool that converts designs into shadcn/ui code.
|
||||||
- [Shadcnblocks.com](https://www.shadcnblocks.com) - A Premium Shadcn Figma UI Kit with components, 500+ pro blocks, shadcn theme variables, light/dark mode and Figma MCP ready.
|
- [Shadcnblocks.com](https://www.shadcnblocks.com) - A Premium Shadcn Figma UI Kit with components, 500+ pro blocks, shadcn theme variables, light/dark mode and Figma MCP ready.
|
||||||
- [Obra shadcn/ui Pro](https://shadcn.obra.studio/products/obra-shadcn-ui-pro) by [Obra Studio](https://obra.studio/) - Focused on designers who need to get work done — the best designer experience for shadcn/ui within Figma. variable consistency with shadcn, plus custom components, Pro blocks, and a design-to-code plugin.
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
"index",
|
"index",
|
||||||
"[Installation](/docs/installation)",
|
"[Installation](/docs/installation)",
|
||||||
"components-json",
|
"components-json",
|
||||||
"package-imports",
|
|
||||||
"theming",
|
"theming",
|
||||||
"[Dark Mode](/docs/dark-mode)",
|
"[Dark Mode](/docs/dark-mode)",
|
||||||
"[RTL](/docs/rtl)",
|
"[RTL](/docs/rtl)",
|
||||||
|
|||||||
@@ -164,91 +164,3 @@ turbo.json
|
|||||||
4. **For Tailwind CSS v4, leave the `tailwind` config empty in the `components.json` file.**
|
4. **For Tailwind CSS v4, leave the `tailwind` config empty in the `components.json` file.**
|
||||||
|
|
||||||
By following these requirements, the CLI will be able to install ui components, blocks, libs and hooks to the correct paths and handle imports for you.
|
By following these requirements, the CLI will be able to install ui components, blocks, libs and hooks to the correct paths and handle imports for you.
|
||||||
|
|
||||||
<Callout className="mt-6">
|
|
||||||
`package.json#imports` works well for package-local aliases inside a
|
|
||||||
workspace, for example inside `packages/ui`. For shared workspace imports such
|
|
||||||
as `@workspace/ui/components`, keep explicit aliases in `components.json`. The
|
|
||||||
CLI uses those aliases to route files across workspace boundaries.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Using `package.json#imports`
|
|
||||||
|
|
||||||
For a monorepo that uses package imports and does not rely on
|
|
||||||
`tsconfig.json` `paths`, use:
|
|
||||||
|
|
||||||
- local `#...` aliases for files inside each workspace
|
|
||||||
- workspace package `exports` for shared imports such as
|
|
||||||
`@workspace/ui/components`
|
|
||||||
|
|
||||||
For example, an app workspace can use local package imports:
|
|
||||||
|
|
||||||
```json showLineNumbers title="apps/web/package.json"
|
|
||||||
{
|
|
||||||
"name": "web",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@workspace/ui": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json showLineNumbers title="apps/web/components.json"
|
|
||||||
{
|
|
||||||
"aliases": {
|
|
||||||
"components": "#components",
|
|
||||||
"ui": "@workspace/ui/components",
|
|
||||||
"lib": "#lib",
|
|
||||||
"hooks": "#hooks",
|
|
||||||
"utils": "@workspace/ui/lib/utils"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And the shared UI package can expose its install targets with `exports`:
|
|
||||||
|
|
||||||
```json showLineNumbers title="packages/ui/package.json"
|
|
||||||
{
|
|
||||||
"name": "@workspace/ui",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
"./globals.css": "./src/styles/globals.css",
|
|
||||||
"./components/*": "./src/components/*.tsx",
|
|
||||||
"./lib/*": "./src/lib/*.ts",
|
|
||||||
"./hooks/*": "./src/hooks/*.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json showLineNumbers title="packages/ui/components.json"
|
|
||||||
{
|
|
||||||
"aliases": {
|
|
||||||
"components": "#components",
|
|
||||||
"ui": "#components",
|
|
||||||
"lib": "#lib",
|
|
||||||
"hooks": "#hooks",
|
|
||||||
"utils": "#lib/utils"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this setup:
|
|
||||||
|
|
||||||
- files added from the app to the shared UI package are routed through
|
|
||||||
`@workspace/ui/...`
|
|
||||||
- files added inside `packages/ui` use the package-local `#...` aliases
|
|
||||||
- the shared package must export any path referenced by another workspace
|
|
||||||
|
|
||||||
For framework-specific package import setup, see the [package imports guide](/docs/package-imports).
|
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
---
|
|
||||||
title: Package Imports
|
|
||||||
description: Configure shadcn/ui with package.json imports.
|
|
||||||
---
|
|
||||||
|
|
||||||
The `shadcn` CLI supports [package imports](https://nodejs.org/api/packages.html#imports)
|
|
||||||
for installing components, rewriting imports, and resolving third-party
|
|
||||||
registries.
|
|
||||||
|
|
||||||
Package imports let you use private `#...` import aliases from your
|
|
||||||
`package.json` instead of `compilerOptions.paths` in `tsconfig.json`.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
You configure `imports` in your `package.json`:
|
|
||||||
|
|
||||||
```json title="package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then import generated components using `#...` specifiers:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Button } from "#components/ui/button"
|
|
||||||
import { cn } from "#lib/utils"
|
|
||||||
```
|
|
||||||
|
|
||||||
<Callout className="mt-6">
|
|
||||||
Package import specifiers must start with `#`. Use TypeScript 5 or later with
|
|
||||||
`moduleResolution: "bundler"` and `resolvePackageJsonImports: true`.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## App
|
|
||||||
|
|
||||||
For Next.js, Vite, and TanStack Start apps that install
|
|
||||||
components into the same workspace.
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
|
|
||||||
### Configure `package.json`
|
|
||||||
|
|
||||||
Add imports for the shadcn/ui install targets.
|
|
||||||
|
|
||||||
```json title="package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If your app does not use a `src` directory, remove `src/` from the targets. For
|
|
||||||
example:
|
|
||||||
|
|
||||||
```json title="package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./components/*.tsx",
|
|
||||||
"#lib/*": "./lib/*.ts",
|
|
||||||
"#hooks/*": "./hooks/*.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configure TypeScript
|
|
||||||
|
|
||||||
Enable package import resolution.
|
|
||||||
|
|
||||||
```json title="tsconfig.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolvePackageJsonImports": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You do not need `compilerOptions.paths` for these aliases.
|
|
||||||
|
|
||||||
### Configure `components.json`
|
|
||||||
|
|
||||||
Use the same `#...` roots in `components.json`.
|
|
||||||
|
|
||||||
```json title="components.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"aliases": {
|
|
||||||
"components": "#components",
|
|
||||||
"ui": "#components/ui",
|
|
||||||
"lib": "#lib",
|
|
||||||
"hooks": "#hooks",
|
|
||||||
"utils": "#lib/utils"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `ui` alias uses `#components/ui`. It is still covered by the
|
|
||||||
`#components/*` import in `package.json`.
|
|
||||||
|
|
||||||
The `utils` alias uses `#lib/utils`. It is covered by `#lib/*`, so you do not
|
|
||||||
need a separate `#utils` import.
|
|
||||||
|
|
||||||
</Steps>
|
|
||||||
|
|
||||||
## Monorepo
|
|
||||||
|
|
||||||
In a monorepo, use package imports for files inside each package and package
|
|
||||||
exports for files shared across workspaces.
|
|
||||||
|
|
||||||
For an app workspace:
|
|
||||||
|
|
||||||
```json title="apps/web/package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"name": "web",
|
|
||||||
"private": true,
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@workspace/ui": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json title="apps/web/components.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"aliases": {
|
|
||||||
"components": "#components",
|
|
||||||
"ui": "@workspace/ui/components",
|
|
||||||
"lib": "#lib",
|
|
||||||
"hooks": "#hooks",
|
|
||||||
"utils": "@workspace/ui/lib/utils"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For the shared UI package:
|
|
||||||
|
|
||||||
```json title="packages/ui/package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"name": "@workspace/ui",
|
|
||||||
"private": true,
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
"./globals.css": "./src/styles/globals.css",
|
|
||||||
"./components/*": "./src/components/*.tsx",
|
|
||||||
"./lib/*": "./src/lib/*.ts",
|
|
||||||
"./hooks/*": "./src/hooks/*.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json title="packages/ui/components.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"aliases": {
|
|
||||||
"components": "#components",
|
|
||||||
"ui": "#components",
|
|
||||||
"lib": "#lib",
|
|
||||||
"hooks": "#hooks",
|
|
||||||
"utils": "#lib/utils"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When you run `add` from `apps/web`, app-local files use `#...` imports and
|
|
||||||
shared UI files are imported from `@workspace/ui`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Button } from "@workspace/ui/components/button"
|
|
||||||
import { LoginForm } from "#components/login-form"
|
|
||||||
```
|
|
||||||
|
|
||||||
## File extensions
|
|
||||||
|
|
||||||
The target pattern in `package.json#imports` controls whether generated imports
|
|
||||||
include file extensions.
|
|
||||||
|
|
||||||
```json title="package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This generates imports without extensions:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Button } from "#components/ui/button"
|
|
||||||
```
|
|
||||||
|
|
||||||
If you use a target without the extension:
|
|
||||||
|
|
||||||
```json title="package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The generated import keeps the source extension:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Button } from "#components/ui/button.tsx"
|
|
||||||
```
|
|
||||||
|
|
||||||
For most apps, use the extension in the target pattern.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If TypeScript cannot resolve a `#...` import, check that:
|
|
||||||
|
|
||||||
- the specifier starts with `#`
|
|
||||||
- the `imports` entry is in the nearest `package.json`
|
|
||||||
- `moduleResolution` is set to `bundler`
|
|
||||||
- `resolvePackageJsonImports` is enabled
|
|
||||||
- the matching target exists after components are added
|
|
||||||
|
|
||||||
If a component is installed but imports still point to `@/...`, check that
|
|
||||||
`components.json` uses the same `#...` aliases as your package imports.
|
|
||||||
@@ -116,7 +116,7 @@ npx shadcn@latest docs combobox
|
|||||||
|
|
||||||
combobox
|
combobox
|
||||||
- docs https://ui.shadcn.com/docs/components/radix/combobox
|
- docs https://ui.shadcn.com/docs/components/radix/combobox
|
||||||
- examples https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/combobox-example.tsx
|
- examples https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/combobox-example.tsx
|
||||||
- api https://base-ui.com/react/components/combobox
|
- api https://base-ui.com/react/components/combobox
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
title: April 2026 - Partial Preset Apply
|
|
||||||
description: Apply only the theme or fonts from a preset while keeping your existing components.
|
|
||||||
date: 2026-04-22
|
|
||||||
---
|
|
||||||
|
|
||||||
You can now selectively apply a preset.
|
|
||||||
|
|
||||||
Say someone shares a preset with you and you already have your own components, but you like the theme or the fonts. Now you can apply just that.
|
|
||||||
|
|
||||||
Keep your components. Apply only what you want.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Apply the full preset.
|
|
||||||
npx shadcn@latest apply --preset b2D0vQ7G4
|
|
||||||
|
|
||||||
# Apply only the theme.
|
|
||||||
npx shadcn@latest apply --preset b2D0vQ7G4 --only theme
|
|
||||||
|
|
||||||
# Apply only the fonts.
|
|
||||||
npx shadcn@latest apply --preset b2D0vQ7G4 --only font
|
|
||||||
|
|
||||||
# Apply theme and fonts.
|
|
||||||
npx shadcn@latest apply --preset b2D0vQ7G4 --only theme,font
|
|
||||||
```
|
|
||||||
|
|
||||||
The default behavior is unchanged. Running `shadcn apply --preset <preset>` still applies the full preset.
|
|
||||||
|
|
||||||
Partial preset apply currently supports `theme` and `font`.
|
|
||||||
|
|
||||||
<Button asChild size="sm">
|
|
||||||
<Link href="/create" className="mt-6 no-underline!">
|
|
||||||
Try a Preset
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: April 2026 - Pointer Cursor
|
|
||||||
description: Add cursor pointer behavior for buttons during project setup.
|
|
||||||
date: 2026-04-25
|
|
||||||
---
|
|
||||||
|
|
||||||
You can now enable `cursor: pointer` for buttons when initializing a project.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest init --pointer
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds the following CSS to your global CSS file:
|
|
||||||
|
|
||||||
```css title="globals.css"
|
|
||||||
@layer base {
|
|
||||||
button:not(:disabled),
|
|
||||||
[role="button"]:not(:disabled) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `--pointer` option is not part of preset codes. It is applied as a project setup option, similar to `--rtl`.
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
---
|
|
||||||
title: April 2026 - shadcn preset
|
|
||||||
description: Decode, share, open, and resolve preset codes from the shadcn CLI.
|
|
||||||
date: 2026-04-28
|
|
||||||
---
|
|
||||||
|
|
||||||
We added `shadcn preset` commands for working with preset codes.
|
|
||||||
|
|
||||||
## Decode a preset
|
|
||||||
|
|
||||||
You can decode a preset code to see exactly what it contains:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset decode b5owWMfJ8l
|
|
||||||
```
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Preset
|
|
||||||
code b5owWMfJ8l
|
|
||||||
version b
|
|
||||||
style mira
|
|
||||||
baseColor mauve
|
|
||||||
theme mauve
|
|
||||||
chartColor amber
|
|
||||||
iconLibrary hugeicons
|
|
||||||
font inter
|
|
||||||
fontHeading oxanium
|
|
||||||
radius large
|
|
||||||
menuAccent subtle
|
|
||||||
menuColor inverted-translucent
|
|
||||||
url https://ui.shadcn.com/create?preset=b5owWMfJ8l
|
|
||||||
```
|
|
||||||
|
|
||||||
## Resolve from a project
|
|
||||||
|
|
||||||
Use `preset resolve` in an existing project to see the preset that matches your current configuration.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset resolve
|
|
||||||
```
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Preset
|
|
||||||
code b5Kc6P0Vc
|
|
||||||
version b
|
|
||||||
style luma
|
|
||||||
baseColor olive
|
|
||||||
theme lime
|
|
||||||
chartColor sky
|
|
||||||
iconLibrary hugeicons
|
|
||||||
font geist
|
|
||||||
fontHeading inherit
|
|
||||||
radius default
|
|
||||||
menuAccent subtle
|
|
||||||
menuColor default
|
|
||||||
url https://ui.shadcn.com/create?preset=b5Kc6P0Vc
|
|
||||||
```
|
|
||||||
|
|
||||||
It works with monorepos too:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset resolve -c apps/web
|
|
||||||
```
|
|
||||||
|
|
||||||
## Share or open
|
|
||||||
|
|
||||||
Use `preset url` when you need a shareable link:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset url b5owWMfJ8l
|
|
||||||
```
|
|
||||||
|
|
||||||
```txt
|
|
||||||
https://ui.shadcn.com/create?preset=b5owWMfJ8l
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `preset open` to open the preset on shadcn/create for customization:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx shadcn@latest preset open b5owWMfJ8l
|
|
||||||
```
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Opening https://ui.shadcn.com/create?preset=b5owWMfJ8l in your browser.
|
|
||||||
```
|
|
||||||
|
|
||||||
This makes presets easier to inspect, share, and hand off to coding agents without manually decoding codes or building URLs.
|
|
||||||
|
|
||||||
<Button asChild size="sm">
|
|
||||||
<Link href="/create" className="mt-6 no-underline!">
|
|
||||||
Try a Preset
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
title: April 2026 - Introducing Sera
|
|
||||||
description: Minimal. Editorial. Typographic. Underline Controls and Uppercase Headings. Shaped by Print Design Principles.
|
|
||||||
date: 2026-04-16
|
|
||||||
---
|
|
||||||
|
|
||||||
Introducing Sera, a new shadcn/ui style. Minimal. Editorial. Typographic. Underline Controls and Uppercase Headings. Shaped by Print Design Principles.
|
|
||||||
|
|
||||||
<a href="/create?preset=b4xFeBLg4O">
|
|
||||||
<Image
|
|
||||||
src="/images/sera-01-light.png"
|
|
||||||
width="2160"
|
|
||||||
height="1832"
|
|
||||||
alt="Sera style preview"
|
|
||||||
className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
|
|
||||||
/>
|
|
||||||
<Image
|
|
||||||
src="/images/sera-01-dark.png"
|
|
||||||
width="2160"
|
|
||||||
height="1832"
|
|
||||||
alt="Sera style preview"
|
|
||||||
className="mt-6 hidden w-full overflow-hidden rounded-lg border shadow-sm dark:block"
|
|
||||||
/>
|
|
||||||
<span className="sr-only">Try Sera in shadcn/create</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
Sera is a typography-first style built on print design principles. It pairs serif headings with sans-serif body text, uses square corners, uppercase tracking, and underlined controls to create an editorial feel for your app.
|
|
||||||
|
|
||||||
Like the other new styles, Sera goes beyond theming. It changes the geometry, spacing, and feel of the components so your app starts from a different visual baseline.
|
|
||||||
|
|
||||||
Available now in [shadcn/create](/create) for both Radix and Base UI.
|
|
||||||
|
|
||||||
<Button asChild size="sm">
|
|
||||||
<Link href="/create?preset=b4xFeBLg4O" className="mt-6 no-underline!">
|
|
||||||
Try Sera
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
---
|
|
||||||
title: May 2026 - Package Imports and Target Aliases
|
|
||||||
description: Configure shadcn/ui with package.json imports and portable registry target aliases.
|
|
||||||
date: 2026-05-05
|
|
||||||
---
|
|
||||||
|
|
||||||
We've added support for package imports and aliases in `files.target` in `shadcn@4.7.0`.
|
|
||||||
|
|
||||||
## Package imports
|
|
||||||
|
|
||||||
The `shadcn` CLI now supports `package.json#imports` for installing components,
|
|
||||||
rewriting imports, and resolving third-party registries. You can use private
|
|
||||||
`#...` import aliases from your `package.json` instead of relying only on
|
|
||||||
`compilerOptions.paths` in `tsconfig.json`.
|
|
||||||
|
|
||||||
```json title="package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then use the same roots in `components.json`:
|
|
||||||
|
|
||||||
```json title="components.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"aliases": {
|
|
||||||
"components": "#components",
|
|
||||||
"ui": "#components/ui",
|
|
||||||
"lib": "#lib",
|
|
||||||
"hooks": "#hooks",
|
|
||||||
"utils": "#lib/utils"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This also works in monorepos where app-local files use package imports and
|
|
||||||
shared UI files are imported from workspace package exports.
|
|
||||||
|
|
||||||
See the [package imports guide](/docs/package-imports) for setup details.
|
|
||||||
|
|
||||||
## Target aliases
|
|
||||||
|
|
||||||
Registry items can now use target aliases in `files[].target` to install files
|
|
||||||
under the user's configured shadcn directories. For example, the following registry item will install the `prompt-input.tsx` file under the `ui/ai` directory.
|
|
||||||
|
|
||||||
```json title="example.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"path": "registry/default/ai/prompt-input.tsx",
|
|
||||||
"type": "registry:ui",
|
|
||||||
"target": "@ui/ai/prompt-input.tsx"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [registry examples](/docs/registry/examples#target-placeholders) for
|
|
||||||
more details.
|
|
||||||
@@ -66,8 +66,6 @@ Tailwind v4 [switched](https://tailwindcss.com/docs/upgrade-guide#buttons-use-th
|
|||||||
|
|
||||||
If you want to keep the `cursor: pointer` behavior, add the following code to your CSS file:
|
If you want to keep the `cursor: pointer` behavior, add the following code to your CSS file:
|
||||||
|
|
||||||
You can also enable this during project setup with `npx shadcn@latest init --pointer`.
|
|
||||||
|
|
||||||
```css showLineNumbers title="globals.css"
|
```css showLineNumbers title="globals.css"
|
||||||
@layer base {
|
@layer base {
|
||||||
button:not(:disabled),
|
button:not(:disabled),
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ Tailwind v4 [switched](https://tailwindcss.com/docs/upgrade-guide#buttons-use-th
|
|||||||
|
|
||||||
If you want to keep the `cursor: pointer` behavior, add the following code to your CSS file:
|
If you want to keep the `cursor: pointer` behavior, add the following code to your CSS file:
|
||||||
|
|
||||||
You can also enable this during project setup with `npx shadcn@latest init --pointer`.
|
|
||||||
|
|
||||||
```css showLineNumbers title="globals.css"
|
```css showLineNumbers title="globals.css"
|
||||||
@layer base {
|
@layer base {
|
||||||
button:not(:disabled),
|
button:not(:disabled),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "Dark mode",
|
"title": "Dark mode",
|
||||||
"pages": ["index", "next", "vite", "astro", "remix", "tanstack-start"]
|
"pages": ["index", "next", "vite", "astro", "remix"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
---
|
|
||||||
title: TanStack Start
|
|
||||||
description: Adding dark mode to your TanStack Start app.
|
|
||||||
---
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
|
|
||||||
### Create a theme provider
|
|
||||||
|
|
||||||
TanStack Start uses `ScriptOnce` from `@tanstack/react-router` to inject a script that runs before React hydrates, preventing flash of unstyled content (FOUC).
|
|
||||||
|
|
||||||
```tsx title="components/theme-provider.tsx" showLineNumbers
|
|
||||||
import { createContext, useContext, useEffect, useState } from "react"
|
|
||||||
import { ScriptOnce } from "@tanstack/react-router"
|
|
||||||
|
|
||||||
type Theme = "dark" | "light" | "system"
|
|
||||||
|
|
||||||
type ThemeProviderProps = {
|
|
||||||
children: React.ReactNode
|
|
||||||
defaultTheme?: Theme
|
|
||||||
storageKey?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThemeProviderState = {
|
|
||||||
theme: Theme
|
|
||||||
setTheme: (theme: Theme) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function getThemeScript(storageKey: string, defaultTheme: Theme) {
|
|
||||||
const key = JSON.stringify(storageKey)
|
|
||||||
const fallback = JSON.stringify(defaultTheme)
|
|
||||||
|
|
||||||
return `(function(){try{var t=localStorage.getItem(${key});if(t!=='light'&&t!=='dark'&&t!=='system'){t=${fallback}}var d=matchMedia('(prefers-color-scheme: dark)').matches;var r=t==='system'?(d?'dark':'light'):t;var e=document.documentElement;e.classList.add(r);e.style.colorScheme=r}catch(e){}})();`
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThemeProviderContext = createContext<ThemeProviderState>({
|
|
||||||
theme: "system",
|
|
||||||
setTheme: () => {},
|
|
||||||
})
|
|
||||||
|
|
||||||
function applyTheme(theme: Theme) {
|
|
||||||
const root = document.documentElement
|
|
||||||
root.classList.remove("light", "dark")
|
|
||||||
|
|
||||||
const resolved =
|
|
||||||
theme === "system"
|
|
||||||
? window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
||||||
? "dark"
|
|
||||||
: "light"
|
|
||||||
: theme
|
|
||||||
|
|
||||||
root.classList.add(resolved)
|
|
||||||
root.style.colorScheme = resolved
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThemeProvider({
|
|
||||||
children,
|
|
||||||
defaultTheme = "system",
|
|
||||||
storageKey = "theme",
|
|
||||||
}: ThemeProviderProps) {
|
|
||||||
const [theme, setThemeState] = useState<Theme>(defaultTheme)
|
|
||||||
const [mounted, setMounted] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const stored = localStorage.getItem(storageKey)
|
|
||||||
setThemeState(
|
|
||||||
stored === "light" || stored === "dark" || stored === "system"
|
|
||||||
? stored
|
|
||||||
: defaultTheme
|
|
||||||
)
|
|
||||||
setMounted(true)
|
|
||||||
}, [defaultTheme, storageKey])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!mounted) return
|
|
||||||
applyTheme(theme)
|
|
||||||
}, [theme, mounted])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!mounted || theme !== "system") return
|
|
||||||
|
|
||||||
const media = window.matchMedia("(prefers-color-scheme: dark)")
|
|
||||||
const onChange = () => applyTheme("system")
|
|
||||||
media.addEventListener("change", onChange)
|
|
||||||
return () => media.removeEventListener("change", onChange)
|
|
||||||
}, [theme, mounted])
|
|
||||||
|
|
||||||
const setTheme = (next: Theme) => {
|
|
||||||
localStorage.setItem(storageKey, next)
|
|
||||||
setThemeState(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeProviderContext value={{ theme, setTheme }}>
|
|
||||||
<ScriptOnce>{getThemeScript(storageKey, defaultTheme)}</ScriptOnce>
|
|
||||||
{children}
|
|
||||||
</ThemeProviderContext>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTheme() {
|
|
||||||
const context = useContext(ThemeProviderContext)
|
|
||||||
if (context === undefined)
|
|
||||||
throw new Error("useTheme must be used within a ThemeProvider")
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Wrap your root layout
|
|
||||||
|
|
||||||
Add the `ThemeProvider` to your root layout and add the `suppressHydrationWarning` prop to the `html` tag.
|
|
||||||
|
|
||||||
```tsx {8,19,24-26} title="src/routes/__root.tsx" showLineNumbers
|
|
||||||
import {
|
|
||||||
createRootRoute,
|
|
||||||
HeadContent,
|
|
||||||
Outlet,
|
|
||||||
Scripts,
|
|
||||||
} from "@tanstack/react-router"
|
|
||||||
|
|
||||||
import { ThemeProvider } from "@/components/theme-provider"
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
|
||||||
head: () => ({
|
|
||||||
// ...
|
|
||||||
}),
|
|
||||||
component: RootComponent,
|
|
||||||
})
|
|
||||||
|
|
||||||
function RootComponent() {
|
|
||||||
return (
|
|
||||||
<html lang="en" suppressHydrationWarning>
|
|
||||||
<head>
|
|
||||||
<HeadContent />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ThemeProvider defaultTheme="system" storageKey="theme">
|
|
||||||
<Outlet />
|
|
||||||
</ThemeProvider>
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add a mode toggle
|
|
||||||
|
|
||||||
Place a mode toggle on your site to toggle between light and dark mode.
|
|
||||||
|
|
||||||
```tsx title="components/mode-toggle.tsx" showLineNumbers
|
|
||||||
import { Moon, Sun } from "lucide-react"
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu"
|
|
||||||
import { useTheme } from "@/components/theme-provider"
|
|
||||||
|
|
||||||
export function ModeToggle() {
|
|
||||||
const { setTheme } = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="outline" size="icon">
|
|
||||||
<Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
|
|
||||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
|
|
||||||
<span className="sr-only">Toggle theme</span>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
|
||||||
Light
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
|
||||||
Dark
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
|
||||||
System
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</Steps>
|
|
||||||
@@ -19,11 +19,9 @@ Add the following dependencies to your project:
|
|||||||
npm install shadcn class-variance-authority clsx tailwind-merge lucide-react tw-animate-css
|
npm install shadcn class-variance-authority clsx tailwind-merge lucide-react tw-animate-css
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configure import aliases
|
### Configure path aliases
|
||||||
|
|
||||||
Choose one of the following alias setups.
|
Configure the path aliases in your `tsconfig.json` file.
|
||||||
|
|
||||||
#### Option A: `tsconfig.json` paths
|
|
||||||
|
|
||||||
```json {3-6} title="tsconfig.json" showLineNumbers
|
```json {3-6} title="tsconfig.json" showLineNumbers
|
||||||
{
|
{
|
||||||
@@ -36,31 +34,7 @@ Choose one of the following alias setups.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Option B: `package.json#imports`
|
The `@` alias is a preference. You can use other aliases if you want.
|
||||||
|
|
||||||
```json title="package.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"#components/*": "./src/components/*.tsx",
|
|
||||||
"#lib/*": "./src/lib/*.ts",
|
|
||||||
"#hooks/*": "./src/hooks/*.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```json title="tsconfig.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"resolvePackageJsonImports": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `@` alias is a preference. You can use other aliases if you want. If you
|
|
||||||
use `package.json#imports`, keep the matching alias roots in `components.json`.
|
|
||||||
See the <Link href="/docs/package-imports">package imports guide</Link> for
|
|
||||||
framework-specific setup.
|
|
||||||
|
|
||||||
### Configure styles
|
### Configure styles
|
||||||
|
|
||||||
@@ -237,20 +211,6 @@ Create a `components.json` file in the root of your project.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're using `package.json#imports`, use the corresponding `#...` aliases instead:
|
|
||||||
|
|
||||||
```json title="components.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"aliases": {
|
|
||||||
"components": "#components",
|
|
||||||
"utils": "#lib/utils",
|
|
||||||
"ui": "#components/ui",
|
|
||||||
"lib": "#lib",
|
|
||||||
"hooks": "#hooks"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### That's it
|
### That's it
|
||||||
|
|
||||||
You can now start adding components to your project.
|
You can now start adding components to your project.
|
||||||
|
|||||||
@@ -309,94 +309,6 @@ A `registry:hook` item is a custom React hook.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Target Placeholders
|
|
||||||
|
|
||||||
Use `files[].target` placeholders when a registry item should install files
|
|
||||||
under the user's configured shadcn directories. The available placeholders are
|
|
||||||
`@components/`, `@ui/`, `@lib/` and `@hooks/`.
|
|
||||||
|
|
||||||
The placeholders are resolved from `components.json`, so the same registry item
|
|
||||||
works in projects using `@/`, custom TypeScript aliases, package imports or
|
|
||||||
workspace package exports.
|
|
||||||
|
|
||||||
Anything after the placeholder is preserved. For example,
|
|
||||||
`@ui/ai/prompt-input.tsx` installs under the user's configured `ui` directory
|
|
||||||
at `ai/prompt-input.tsx`.
|
|
||||||
|
|
||||||
```json title="alias-child.json" showLineNumbers {9,15,21}
|
|
||||||
{
|
|
||||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
||||||
"name": "alias-child",
|
|
||||||
"type": "registry:item",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/alias/target-alias-button.tsx",
|
|
||||||
"type": "registry:ui",
|
|
||||||
"target": "@ui/target-alias-button.tsx",
|
|
||||||
"content": "..."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/alias/target-alias-helper.ts",
|
|
||||||
"type": "registry:lib",
|
|
||||||
"target": "@lib/target-alias-helper.ts",
|
|
||||||
"content": "..."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/alias/prompt-input.tsx",
|
|
||||||
"type": "registry:ui",
|
|
||||||
"target": "@ui/ai/prompt-input.tsx",
|
|
||||||
"content": "..."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Registry dependencies can use target placeholders too. In the following example,
|
|
||||||
the child item installs a UI component and a helper, while the parent item
|
|
||||||
installs an app component and a hook.
|
|
||||||
|
|
||||||
```json title="alias-parent.json" showLineNumbers {7,13}
|
|
||||||
{
|
|
||||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
||||||
"name": "alias-parent",
|
|
||||||
"type": "registry:item",
|
|
||||||
"registryDependencies": ["https://example.com/r/alias-child.json"],
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/alias/target-alias-panel.tsx",
|
|
||||||
"type": "registry:component",
|
|
||||||
"target": "@components/target-alias-panel.tsx",
|
|
||||||
"content": "..."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/alias/use-target-alias.ts",
|
|
||||||
"type": "registry:hook",
|
|
||||||
"target": "@hooks/use-target-alias.ts",
|
|
||||||
"content": "..."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `target` controls where the file is written, even when it differs from the
|
|
||||||
file `type`.
|
|
||||||
|
|
||||||
```json title="type-mismatch.json" showLineNumbers {9}
|
|
||||||
{
|
|
||||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
||||||
"name": "type-mismatch",
|
|
||||||
"type": "registry:item",
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/example/format-date.ts",
|
|
||||||
"type": "registry:ui",
|
|
||||||
"target": "@lib/format-date.ts",
|
|
||||||
"content": "..."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## registry:font
|
## registry:font
|
||||||
|
|
||||||
### Custom font
|
### Custom font
|
||||||
|
|||||||
@@ -144,106 +144,6 @@ npm run dev
|
|||||||
|
|
||||||
Your files will now be served at `http://localhost:3000/r/[NAME].json` eg. `http://localhost:3000/r/hello-world.json`.
|
Your files will now be served at `http://localhost:3000/r/[NAME].json` eg. `http://localhost:3000/r/hello-world.json`.
|
||||||
|
|
||||||
## Content negotiation
|
|
||||||
|
|
||||||
The `shadcn` CLI supports **HTTP Content Negotiation**. This allows you to host your registry at any endpoint — including the root of your domain — and serve different content depending on who is asking.
|
|
||||||
|
|
||||||
From a single URL, you can serve:
|
|
||||||
|
|
||||||
- **HTML** to browsers — a landing page, documentation, or marketing site.
|
|
||||||
- **JSON** to the `shadcn` CLI — an installable registry item.
|
|
||||||
- **Markdown** to AI agents and LLMs — a machine-readable version of your content.
|
|
||||||
|
|
||||||
The client signals its preference using the `Accept` request header, and your server decides what to return.
|
|
||||||
|
|
||||||
### Request headers
|
|
||||||
|
|
||||||
When the CLI makes a request to a registry, it sends the following headers:
|
|
||||||
|
|
||||||
- **User-Agent**: `shadcn`
|
|
||||||
- **Accept**: `application/vnd.shadcn.v1+json, application/json;q=0.9`
|
|
||||||
|
|
||||||
### Root hosting
|
|
||||||
|
|
||||||
By checking these headers on your server, you can route CLI traffic to an installable registry item while keeping browser traffic flowing to your documentation or homepage.
|
|
||||||
|
|
||||||
The examples below assume your built registry item is served at `/r/index.json`. Adjust the path to match your output.
|
|
||||||
|
|
||||||
In Next.js, express this as a rewrite in `next.config.ts`. This keeps the negotiation in the routing layer and avoids a Proxy function for this static rewrite:
|
|
||||||
|
|
||||||
```typescript title="next.config.ts" showLineNumbers
|
|
||||||
import type { NextConfig } from "next"
|
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
|
||||||
async rewrites() {
|
|
||||||
return {
|
|
||||||
beforeFiles: [
|
|
||||||
{
|
|
||||||
source: "/",
|
|
||||||
has: [
|
|
||||||
{
|
|
||||||
type: "header",
|
|
||||||
key: "accept",
|
|
||||||
value: "(.*)application/vnd\\.shadcn\\.v1\\+json(.*)",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
destination: "/r/index.json",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: "/",
|
|
||||||
has: [
|
|
||||||
{
|
|
||||||
type: "header",
|
|
||||||
key: "user-agent",
|
|
||||||
value: "shadcn",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
destination: "/r/index.json",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async headers() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
source: "/",
|
|
||||||
headers: [{ key: "Vary", value: "Accept, User-Agent" }],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default nextConfig
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, in an Express.js server:
|
|
||||||
|
|
||||||
```javascript title="server.js" showLineNumbers
|
|
||||||
app.get("/", (req, res) => {
|
|
||||||
res.vary("Accept")
|
|
||||||
res.vary("User-Agent")
|
|
||||||
|
|
||||||
// Check if the client prefers the shadcn vendor type.
|
|
||||||
if (req.accepts("application/vnd.shadcn.v1+json")) {
|
|
||||||
return res.json(registryData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional: Secondary check for the User-Agent.
|
|
||||||
if (req.get("User-Agent") === "shadcn") {
|
|
||||||
return res.json(registryData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, serve your documentation or homepage.
|
|
||||||
res.send(htmlContent)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
This enables:
|
|
||||||
|
|
||||||
- **Branded Registry URLs**: `shadcn add https://ui.example.com`
|
|
||||||
- **Shorter URLs**: Users type your domain root, not `/r/` or `/registry/` sub-paths.
|
|
||||||
- **Easy Mnemonics**: Easier for users to remember and share your registry.
|
|
||||||
|
|
||||||
## Publish your registry
|
## Publish your registry
|
||||||
|
|
||||||
To make your registry available to other developers, you can publish it by deploying your project to a public URL.
|
To make your registry available to other developers, you can publish it by deploying your project to a public URL.
|
||||||
|
|||||||
@@ -226,80 +226,6 @@ By default, the `shadcn` cli will read a project's `components.json` file to det
|
|||||||
|
|
||||||
Use `~` to refer to the root of the project e.g `~/foo.config.js`.
|
Use `~` to refer to the root of the project e.g `~/foo.config.js`.
|
||||||
|
|
||||||
You can also use registry target placeholders to place files under the
|
|
||||||
directories configured by the user's `components.json`. These placeholders are
|
|
||||||
only supported at the start of `target` and are independent of the project's
|
|
||||||
import prefix. For example, `@ui/button.tsx` works whether the project imports
|
|
||||||
components with `@/`, `#`, package imports or workspace exports.
|
|
||||||
|
|
||||||
| Placeholder | Resolves to |
|
|
||||||
| -------------- | -------------------- |
|
|
||||||
| `@components/` | `aliases.components` |
|
|
||||||
| `@ui/` | `aliases.ui` |
|
|
||||||
| `@lib/` | `aliases.lib` |
|
|
||||||
| `@hooks/` | `aliases.hooks` |
|
|
||||||
|
|
||||||
Use these placeholders when you want a registry item to install into the
|
|
||||||
project's configured shadcn directories without hardcoding `components`, `src`
|
|
||||||
or workspace package paths. Anything after the placeholder is preserved, so
|
|
||||||
`@ui/ai/prompt-input.tsx` installs under the user's configured `ui` directory
|
|
||||||
at `ai/prompt-input.tsx`.
|
|
||||||
|
|
||||||
```json title="registry-item.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/example/button.tsx",
|
|
||||||
"type": "registry:ui",
|
|
||||||
"target": "@ui/button.tsx"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/example/prompt-input.tsx",
|
|
||||||
"type": "registry:ui",
|
|
||||||
"target": "@ui/ai/prompt-input.tsx"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/example/card.tsx",
|
|
||||||
"type": "registry:component",
|
|
||||||
"target": "@components/card.tsx"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/example/helper.ts",
|
|
||||||
"type": "registry:lib",
|
|
||||||
"target": "@lib/helper.ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/example/use-demo.ts",
|
|
||||||
"type": "registry:hook",
|
|
||||||
"target": "@hooks/use-demo.ts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `target` property decides where the file is written. It can point to a
|
|
||||||
different shadcn directory than the file `type`.
|
|
||||||
|
|
||||||
```json title="registry-item.json" showLineNumbers
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"path": "registry/new-york/example/format-date.ts",
|
|
||||||
"type": "registry:ui",
|
|
||||||
"target": "@lib/format-date.ts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Unknown placeholders are treated as regular target paths. For example,
|
|
||||||
`@foo/bar.ts` is written as `foo/bar.ts`. Embedded placeholders such as
|
|
||||||
`components/@ui/button.tsx` are also treated as regular paths.
|
|
||||||
|
|
||||||
<Callout>
|
|
||||||
`@utils/` is not supported because `utils` points to a file, not a directory.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
### tailwind
|
### tailwind
|
||||||
|
|
||||||
**DEPRECATED:** Use `cssVars.theme` instead for Tailwind v4 projects.
|
**DEPRECATED:** Use `cssVars.theme` instead for Tailwind v4 projects.
|
||||||
|
|||||||
@@ -301,31 +301,6 @@ export const FONT_DEFINITIONS = [
|
|||||||
dependency: "@fontsource-variable/playfair-display",
|
dependency: "@fontsource-variable/playfair-display",
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "eb-garamond",
|
|
||||||
title: "EB Garamond",
|
|
||||||
type: "serif",
|
|
||||||
family: "'EB Garamond Variable', serif",
|
|
||||||
registryVariable: "--font-serif",
|
|
||||||
previewVariable: "--font-eb-garamond",
|
|
||||||
provider: "google",
|
|
||||||
import: "EB_Garamond",
|
|
||||||
dependency: "@fontsource-variable/eb-garamond",
|
|
||||||
subsets: ["latin"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "instrument-serif",
|
|
||||||
title: "Instrument Serif",
|
|
||||||
type: "serif",
|
|
||||||
family: "'Instrument Serif', serif",
|
|
||||||
registryVariable: "--font-serif",
|
|
||||||
previewVariable: "--font-instrument-serif",
|
|
||||||
provider: "google",
|
|
||||||
import: "Instrument_Serif",
|
|
||||||
dependency: "@fontsource/instrument-serif",
|
|
||||||
subsets: ["latin"],
|
|
||||||
weight: ["400"],
|
|
||||||
},
|
|
||||||
] as const satisfies readonly FontDefinition[]
|
] as const satisfies readonly FontDefinition[]
|
||||||
|
|
||||||
export type FontName = (typeof FONT_DEFINITIONS)[number]["name"]
|
export type FontName = (typeof FONT_DEFINITIONS)[number]["name"]
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Lora as FontLora,
|
|
||||||
Geist_Mono as FontMono,
|
Geist_Mono as FontMono,
|
||||||
Noto_Sans as FontNotoSans,
|
|
||||||
Noto_Sans_Arabic as FontNotoSansArabic,
|
Noto_Sans_Arabic as FontNotoSansArabic,
|
||||||
Noto_Sans_Hebrew as FontNotoSansHebrew,
|
Noto_Sans_Hebrew as FontNotoSansHebrew,
|
||||||
Noto_Serif as FontNotoSerif,
|
|
||||||
Geist as FontSans,
|
Geist as FontSans,
|
||||||
Inter,
|
Inter,
|
||||||
Playfair_Display,
|
|
||||||
} from "next/font/google"
|
} from "next/font/google"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -28,16 +24,6 @@ const fontInter = Inter({
|
|||||||
variable: "--font-inter",
|
variable: "--font-inter",
|
||||||
})
|
})
|
||||||
|
|
||||||
const fontNotoSans = FontNotoSans({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-noto-sans",
|
|
||||||
})
|
|
||||||
|
|
||||||
const fontNotoSerif = FontNotoSerif({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-noto-serif",
|
|
||||||
})
|
|
||||||
|
|
||||||
const fontNotoSansArabic = FontNotoSansArabic({
|
const fontNotoSansArabic = FontNotoSansArabic({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
variable: "--font-ar",
|
variable: "--font-ar",
|
||||||
@@ -48,24 +34,10 @@ const fontNotoSansHebrew = FontNotoSansHebrew({
|
|||||||
variable: "--font-he",
|
variable: "--font-he",
|
||||||
})
|
})
|
||||||
|
|
||||||
const fontLora = FontLora({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-lora",
|
|
||||||
})
|
|
||||||
|
|
||||||
const fontPlayfairDisplay = Playfair_Display({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-playfair-display",
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fontVariables = cn(
|
export const fontVariables = cn(
|
||||||
fontSans.variable,
|
fontSans.variable,
|
||||||
fontMono.variable,
|
fontMono.variable,
|
||||||
fontInter.variable,
|
fontInter.variable,
|
||||||
fontNotoSans.variable,
|
|
||||||
fontNotoSerif.variable,
|
|
||||||
fontNotoSansArabic.variable,
|
fontNotoSansArabic.variable,
|
||||||
fontNotoSansHebrew.variable,
|
fontNotoSansHebrew.variable
|
||||||
fontPlayfairDisplay.variable,
|
|
||||||
fontLora.variable
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -132,12 +132,6 @@ const nextConfig = {
|
|||||||
destination: "/create",
|
destination: "/create",
|
||||||
permanent: true,
|
permanent: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
source: "/code/:path*",
|
|
||||||
destination:
|
|
||||||
"https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/:path*",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
rewrites() {
|
rewrites() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm icons:dev & next dev --turbopack --port 4000",
|
"dev": "pnpm registry:build && pnpm icons:dev & next dev --turbopack --port 4000",
|
||||||
"build": "pnpm registry:build && next build",
|
"build": "pnpm registry:build && next build",
|
||||||
"start": "next start --port 4000",
|
"start": "next start --port 4000",
|
||||||
"preview": "pnpm registry:build && next build && next start --port 4000",
|
"preview": "pnpm registry:build && next build && next start --port 4000",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"rehype-pretty-code": "^0.14.1",
|
"rehype-pretty-code": "^0.14.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"shadcn": "4.7.0",
|
"shadcn": "4.2.0",
|
||||||
"shiki": "^1.10.1",
|
"shiki": "^1.10.1",
|
||||||
"sonner": "^2.0.0",
|
"sonner": "^2.0.0",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 122 KiB |
@@ -215,42 +215,6 @@
|
|||||||
"menuAccent": "subtle",
|
"menuAccent": "subtle",
|
||||||
"menuColor": "default",
|
"menuColor": "default",
|
||||||
"radius": "default"
|
"radius": "default"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "radix-sera",
|
|
||||||
"title": "Sera (Radix)",
|
|
||||||
"description": "Sera / Lucide / Noto Sans + Playfair Display",
|
|
||||||
"base": "radix",
|
|
||||||
"style": "sera",
|
|
||||||
"baseColor": "taupe",
|
|
||||||
"theme": "taupe",
|
|
||||||
"chartColor": "taupe",
|
|
||||||
"iconLibrary": "lucide",
|
|
||||||
"font": "noto-sans",
|
|
||||||
"fontHeading": "playfair-display",
|
|
||||||
"item": "Item",
|
|
||||||
"rtl": false,
|
|
||||||
"menuAccent": "subtle",
|
|
||||||
"menuColor": "default",
|
|
||||||
"radius": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "base-sera",
|
|
||||||
"title": "Sera (Base)",
|
|
||||||
"description": "Sera / Lucide / Noto Sans + Playfair Display",
|
|
||||||
"base": "base",
|
|
||||||
"style": "sera",
|
|
||||||
"baseColor": "taupe",
|
|
||||||
"theme": "taupe",
|
|
||||||
"chartColor": "taupe",
|
|
||||||
"iconLibrary": "lucide",
|
|
||||||
"font": "noto-sans",
|
|
||||||
"fontHeading": "playfair-display",
|
|
||||||
"item": "Item",
|
|
||||||
"rtl": false,
|
|
||||||
"menuAccent": "subtle",
|
|
||||||
"menuColor": "default",
|
|
||||||
"radius": "default"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/accordion",
|
"docs": "https://ui.shadcn.com/docs/components/radix/accordion",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/accordion-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/accordion-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/primitives/docs/components/accordion.md"
|
"api": "https://www.radix-ui.com/primitives/docs/components/accordion.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/accordion",
|
"docs": "https://ui.shadcn.com/docs/components/base/accordion",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/accordion-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/accordion-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/accordion.md"
|
"api": "https://base-ui.com/react/components/accordion.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,11 +36,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/alert",
|
"docs": "https://ui.shadcn.com/docs/components/radix/alert",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/alert-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/alert-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/alert",
|
"docs": "https://ui.shadcn.com/docs/components/base/alert",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/alert-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/alert-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,12 +59,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/alert-dialog",
|
"docs": "https://ui.shadcn.com/docs/components/radix/alert-dialog",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/alert-dialog-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/alert-dialog-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/primitives/docs/components/alert-dialog.md"
|
"api": "https://www.radix-ui.com/primitives/docs/components/alert-dialog.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/alert-dialog",
|
"docs": "https://ui.shadcn.com/docs/components/base/alert-dialog",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/alert-dialog-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/alert-dialog-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/alert-dialog.md"
|
"api": "https://base-ui.com/react/components/alert-dialog.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,12 +83,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/aspect-ratio",
|
"docs": "https://ui.shadcn.com/docs/components/radix/aspect-ratio",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/aspect-ratio-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/aspect-ratio-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/primitives/docs/components/aspect-ratio.md"
|
"api": "https://www.radix-ui.com/primitives/docs/components/aspect-ratio.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/aspect-ratio",
|
"docs": "https://ui.shadcn.com/docs/components/base/aspect-ratio",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/aspect-ratio-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/aspect-ratio-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,12 +106,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/avatar",
|
"docs": "https://ui.shadcn.com/docs/components/radix/avatar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/avatar-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/avatar-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/primitives/docs/components/avatar.md"
|
"api": "https://www.radix-ui.com/primitives/docs/components/avatar.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/avatar",
|
"docs": "https://ui.shadcn.com/docs/components/base/avatar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/avatar-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/avatar-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/avatar.md"
|
"api": "https://base-ui.com/react/components/avatar.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,11 +130,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/badge",
|
"docs": "https://ui.shadcn.com/docs/components/radix/badge",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/badge-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/badge-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/badge",
|
"docs": "https://ui.shadcn.com/docs/components/base/badge",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/badge-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/badge-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,11 +152,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/breadcrumb",
|
"docs": "https://ui.shadcn.com/docs/components/radix/breadcrumb",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/breadcrumb-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/breadcrumb-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/breadcrumb",
|
"docs": "https://ui.shadcn.com/docs/components/base/breadcrumb",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/breadcrumb-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/breadcrumb-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,11 +174,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/button",
|
"docs": "https://ui.shadcn.com/docs/components/radix/button",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/button-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/button-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/button",
|
"docs": "https://ui.shadcn.com/docs/components/base/button",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/button-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/button-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,11 +197,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/button-group",
|
"docs": "https://ui.shadcn.com/docs/components/radix/button-group",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/button-group-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/button-group-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/button-group",
|
"docs": "https://ui.shadcn.com/docs/components/base/button-group",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/button-group-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/button-group-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,12 +221,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/calendar",
|
"docs": "https://ui.shadcn.com/docs/components/radix/calendar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/calendar-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/calendar-example.tsx",
|
||||||
"api": "https://react-day-picker.js.org"
|
"api": "https://react-day-picker.js.org"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/calendar",
|
"docs": "https://ui.shadcn.com/docs/components/base/calendar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/calendar-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/calendar-example.tsx",
|
||||||
"api": "https://react-day-picker.js.org"
|
"api": "https://react-day-picker.js.org"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,11 +245,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/card",
|
"docs": "https://ui.shadcn.com/docs/components/radix/card",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/card-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/card-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/card",
|
"docs": "https://ui.shadcn.com/docs/components/base/card",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/card-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/card-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,12 +269,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/carousel",
|
"docs": "https://ui.shadcn.com/docs/components/radix/carousel",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/carousel-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/carousel-example.tsx",
|
||||||
"api": "https://www.embla-carousel.com/get-started/react"
|
"api": "https://www.embla-carousel.com/get-started/react"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/carousel",
|
"docs": "https://ui.shadcn.com/docs/components/base/carousel",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/carousel-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/carousel-example.tsx",
|
||||||
"api": "https://www.embla-carousel.com/get-started/react"
|
"api": "https://www.embla-carousel.com/get-started/react"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,11 +294,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/chart",
|
"docs": "https://ui.shadcn.com/docs/components/radix/chart",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/chart-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/chart-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/chart",
|
"docs": "https://ui.shadcn.com/docs/components/base/chart",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/chart-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/chart-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,12 +316,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/checkbox",
|
"docs": "https://ui.shadcn.com/docs/components/radix/checkbox",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/checkbox-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/checkbox-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/checkbox.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/checkbox.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/checkbox",
|
"docs": "https://ui.shadcn.com/docs/components/base/checkbox",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/checkbox-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/checkbox-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/checkbox.md"
|
"api": "https://base-ui.com/react/components/checkbox.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,12 +340,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/collapsible",
|
"docs": "https://ui.shadcn.com/docs/components/radix/collapsible",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/collapsible-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/collapsible-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/collapsible.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/collapsible.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/collapsible",
|
"docs": "https://ui.shadcn.com/docs/components/base/collapsible",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/collapsible-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/collapsible-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/collapsible.md"
|
"api": "https://base-ui.com/react/components/collapsible.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,12 +366,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/combobox",
|
"docs": "https://ui.shadcn.com/docs/components/radix/combobox",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/combobox-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/combobox-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/combobox"
|
"api": "https://base-ui.com/react/components/combobox"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/combobox",
|
"docs": "https://ui.shadcn.com/docs/components/base/combobox",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/combobox-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/combobox-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/combobox.md"
|
"api": "https://base-ui.com/react/components/combobox.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,12 +392,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/command",
|
"docs": "https://ui.shadcn.com/docs/components/radix/command",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/command-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/command-example.tsx",
|
||||||
"api": "https://github.com/dip/cmdk"
|
"api": "https://github.com/dip/cmdk"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/command",
|
"docs": "https://ui.shadcn.com/docs/components/base/command",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/command-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/command-example.tsx",
|
||||||
"api": "https://github.com/dip/cmdk"
|
"api": "https://github.com/dip/cmdk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,12 +416,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/context-menu",
|
"docs": "https://ui.shadcn.com/docs/components/radix/context-menu",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/context-menu-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/context-menu-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/context-menu.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/context-menu.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/context-menu",
|
"docs": "https://ui.shadcn.com/docs/components/base/context-menu",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/context-menu-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/context-menu-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/context-menu.md"
|
"api": "https://base-ui.com/react/components/context-menu.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,12 +441,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/dialog",
|
"docs": "https://ui.shadcn.com/docs/components/radix/dialog",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/dialog-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/dialog-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/dialog.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/dialog.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/dialog",
|
"docs": "https://ui.shadcn.com/docs/components/base/dialog",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/dialog-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/dialog-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/dialog.md"
|
"api": "https://base-ui.com/react/components/dialog.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,12 +488,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/drawer",
|
"docs": "https://ui.shadcn.com/docs/components/radix/drawer",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/drawer-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/drawer-example.tsx",
|
||||||
"api": "https://vaul.emilkowal.ski/getting-started"
|
"api": "https://vaul.emilkowal.ski/getting-started"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/drawer",
|
"docs": "https://ui.shadcn.com/docs/components/base/drawer",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/drawer-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/drawer-example.tsx",
|
||||||
"api": "https://vaul.emilkowal.ski/getting-started"
|
"api": "https://vaul.emilkowal.ski/getting-started"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,12 +512,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/dropdown-menu",
|
"docs": "https://ui.shadcn.com/docs/components/radix/dropdown-menu",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/dropdown-menu-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/dropdown-menu-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/dropdown-menu.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/dropdown-menu.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/dropdown-menu",
|
"docs": "https://ui.shadcn.com/docs/components/base/dropdown-menu",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/dropdown-menu-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/dropdown-menu-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/menu.md"
|
"api": "https://base-ui.com/react/components/menu.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,11 +536,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/empty",
|
"docs": "https://ui.shadcn.com/docs/components/radix/empty",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/empty-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/empty-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/empty",
|
"docs": "https://ui.shadcn.com/docs/components/base/empty",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/empty-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/empty-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,11 +559,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/field",
|
"docs": "https://ui.shadcn.com/docs/components/radix/field",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/field-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/field-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/field",
|
"docs": "https://ui.shadcn.com/docs/components/base/field",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/field-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/field-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,12 +585,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/hover-card",
|
"docs": "https://ui.shadcn.com/docs/components/radix/hover-card",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/hover-card-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/hover-card-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/hover-card.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/hover-card.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/hover-card",
|
"docs": "https://ui.shadcn.com/docs/components/base/hover-card",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/hover-card-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/hover-card-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/hover-card.md"
|
"api": "https://base-ui.com/react/components/hover-card.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -609,11 +609,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/input",
|
"docs": "https://ui.shadcn.com/docs/components/radix/input",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/input-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/input-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/input",
|
"docs": "https://ui.shadcn.com/docs/components/base/input",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/input-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/input-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,11 +632,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/input-group",
|
"docs": "https://ui.shadcn.com/docs/components/radix/input-group",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/input-group-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/input-group-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/input-group",
|
"docs": "https://ui.shadcn.com/docs/components/base/input-group",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/input-group-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/input-group-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,12 +655,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/input-otp",
|
"docs": "https://ui.shadcn.com/docs/components/radix/input-otp",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/input-otp-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/input-otp-example.tsx",
|
||||||
"api": "https://input-otp.rodz.dev"
|
"api": "https://input-otp.rodz.dev"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/input-otp",
|
"docs": "https://ui.shadcn.com/docs/components/base/input-otp",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/input-otp-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/input-otp-example.tsx",
|
||||||
"api": "https://input-otp.rodz.dev"
|
"api": "https://input-otp.rodz.dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -680,11 +680,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/item",
|
"docs": "https://ui.shadcn.com/docs/components/radix/item",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/item-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/item-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/item",
|
"docs": "https://ui.shadcn.com/docs/components/base/item",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/item-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/item-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -702,11 +702,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/kbd",
|
"docs": "https://ui.shadcn.com/docs/components/radix/kbd",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/kbd-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/kbd-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/kbd",
|
"docs": "https://ui.shadcn.com/docs/components/base/kbd",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/kbd-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/kbd-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -724,12 +724,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/label",
|
"docs": "https://ui.shadcn.com/docs/components/radix/label",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/label-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/label-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/label.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/label.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/label",
|
"docs": "https://ui.shadcn.com/docs/components/base/label",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/label-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/label-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/label.md"
|
"api": "https://base-ui.com/react/components/label.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -748,12 +748,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/menubar",
|
"docs": "https://ui.shadcn.com/docs/components/radix/menubar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/menubar-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/menubar-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/menubar.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/menubar.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/menubar",
|
"docs": "https://ui.shadcn.com/docs/components/base/menubar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/menubar-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/menubar-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/menubar.md"
|
"api": "https://base-ui.com/react/components/menubar.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -772,11 +772,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/native-select",
|
"docs": "https://ui.shadcn.com/docs/components/radix/native-select",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/native-select-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/native-select-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/native-select",
|
"docs": "https://ui.shadcn.com/docs/components/base/native-select",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/native-select-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/native-select-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -794,12 +794,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/navigation-menu",
|
"docs": "https://ui.shadcn.com/docs/components/radix/navigation-menu",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/navigation-menu-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/navigation-menu-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/navigation-menu.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/navigation-menu.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/navigation-menu",
|
"docs": "https://ui.shadcn.com/docs/components/base/navigation-menu",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/navigation-menu-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/navigation-menu-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/navigation-menu.md"
|
"api": "https://base-ui.com/react/components/navigation-menu.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -819,11 +819,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/pagination",
|
"docs": "https://ui.shadcn.com/docs/components/radix/pagination",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/pagination-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/pagination-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/pagination",
|
"docs": "https://ui.shadcn.com/docs/components/base/pagination",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/pagination-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/pagination-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -841,12 +841,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/popover",
|
"docs": "https://ui.shadcn.com/docs/components/radix/popover",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/popover-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/popover-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/popover.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/popover.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/popover",
|
"docs": "https://ui.shadcn.com/docs/components/base/popover",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/popover-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/popover-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/popover.md"
|
"api": "https://base-ui.com/react/components/popover.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -865,12 +865,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/progress",
|
"docs": "https://ui.shadcn.com/docs/components/radix/progress",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/progress-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/progress-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/progress.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/progress.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/progress",
|
"docs": "https://ui.shadcn.com/docs/components/base/progress",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/progress-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/progress-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/progress.md"
|
"api": "https://base-ui.com/react/components/progress.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -889,12 +889,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/radio-group",
|
"docs": "https://ui.shadcn.com/docs/components/radix/radio-group",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/radio-group-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/radio-group-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/radio-group.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/radio-group.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/radio-group",
|
"docs": "https://ui.shadcn.com/docs/components/base/radio-group",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/radio-group-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/radio-group-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/radio-group.md"
|
"api": "https://base-ui.com/react/components/radio-group.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -914,12 +914,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/resizable",
|
"docs": "https://ui.shadcn.com/docs/components/radix/resizable",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/resizable-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/resizable-example.tsx",
|
||||||
"api": "https://github.com/bvaughn/react-resizable-panels"
|
"api": "https://github.com/bvaughn/react-resizable-panels"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/resizable",
|
"docs": "https://ui.shadcn.com/docs/components/base/resizable",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/resizable-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/resizable-example.tsx",
|
||||||
"api": "https://github.com/bvaughn/react-resizable-panels"
|
"api": "https://github.com/bvaughn/react-resizable-panels"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -938,12 +938,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/scroll-area",
|
"docs": "https://ui.shadcn.com/docs/components/radix/scroll-area",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/scroll-area-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/scroll-area-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/scroll-area.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/scroll-area.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/scroll-area",
|
"docs": "https://ui.shadcn.com/docs/components/base/scroll-area",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/scroll-area-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/scroll-area-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/scroll-area.md"
|
"api": "https://base-ui.com/react/components/scroll-area.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -962,12 +962,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/select",
|
"docs": "https://ui.shadcn.com/docs/components/radix/select",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/select-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/select-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/select.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/select.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/select",
|
"docs": "https://ui.shadcn.com/docs/components/base/select",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/select-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/select-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/select.md"
|
"api": "https://base-ui.com/react/components/select.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -986,12 +986,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/separator",
|
"docs": "https://ui.shadcn.com/docs/components/radix/separator",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/separator-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/separator-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/separator.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/separator.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/separator",
|
"docs": "https://ui.shadcn.com/docs/components/base/separator",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/separator-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/separator-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/separator.md"
|
"api": "https://base-ui.com/react/components/separator.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1011,12 +1011,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/sheet",
|
"docs": "https://ui.shadcn.com/docs/components/radix/sheet",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/sheet-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/sheet-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/dialog.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/dialog.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/sheet",
|
"docs": "https://ui.shadcn.com/docs/components/base/sheet",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/sheet-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/sheet-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/dialog.md"
|
"api": "https://base-ui.com/react/components/dialog.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1044,11 +1044,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/sidebar",
|
"docs": "https://ui.shadcn.com/docs/components/radix/sidebar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/sidebar-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/sidebar-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/sidebar",
|
"docs": "https://ui.shadcn.com/docs/components/base/sidebar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/sidebar-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/sidebar-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1066,11 +1066,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/skeleton",
|
"docs": "https://ui.shadcn.com/docs/components/radix/skeleton",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/skeleton-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/skeleton-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/skeleton",
|
"docs": "https://ui.shadcn.com/docs/components/base/skeleton",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/skeleton-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/skeleton-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1088,12 +1088,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/slider",
|
"docs": "https://ui.shadcn.com/docs/components/radix/slider",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/slider-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/slider-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/slider.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/slider.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/slider",
|
"docs": "https://ui.shadcn.com/docs/components/base/slider",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/slider-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/slider-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/slider.md"
|
"api": "https://base-ui.com/react/components/slider.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1113,12 +1113,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/sonner",
|
"docs": "https://ui.shadcn.com/docs/components/radix/sonner",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/sonner-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/sonner-example.tsx",
|
||||||
"api": "https://sonner.emilkowal.ski"
|
"api": "https://sonner.emilkowal.ski"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/sonner",
|
"docs": "https://ui.shadcn.com/docs/components/base/sonner",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/sonner-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/sonner-example.tsx",
|
||||||
"api": "https://sonner.emilkowal.ski"
|
"api": "https://sonner.emilkowal.ski"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1137,11 +1137,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/spinner",
|
"docs": "https://ui.shadcn.com/docs/components/radix/spinner",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/spinner-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/spinner-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/spinner",
|
"docs": "https://ui.shadcn.com/docs/components/base/spinner",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/spinner-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/spinner-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1159,12 +1159,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/switch",
|
"docs": "https://ui.shadcn.com/docs/components/radix/switch",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/switch-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/switch-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/switch.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/switch.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/switch",
|
"docs": "https://ui.shadcn.com/docs/components/base/switch",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/switch-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/switch-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/switch.md"
|
"api": "https://base-ui.com/react/components/switch.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1183,11 +1183,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/table",
|
"docs": "https://ui.shadcn.com/docs/components/radix/table",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/table-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/table-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/table",
|
"docs": "https://ui.shadcn.com/docs/components/base/table",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/table-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/table-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1205,12 +1205,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/tabs",
|
"docs": "https://ui.shadcn.com/docs/components/radix/tabs",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/tabs-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/tabs-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/tabs.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/tabs.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/tabs",
|
"docs": "https://ui.shadcn.com/docs/components/base/tabs",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/tabs-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/tabs-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/tabs.md"
|
"api": "https://base-ui.com/react/components/tabs.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1229,11 +1229,11 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/textarea",
|
"docs": "https://ui.shadcn.com/docs/components/radix/textarea",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/textarea-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/textarea-example.tsx"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/textarea",
|
"docs": "https://ui.shadcn.com/docs/components/base/textarea",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/textarea-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/textarea-example.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1251,12 +1251,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/toggle",
|
"docs": "https://ui.shadcn.com/docs/components/radix/toggle",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/toggle-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/toggle-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/toggle.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/toggle.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/toggle",
|
"docs": "https://ui.shadcn.com/docs/components/base/toggle",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/toggle-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/toggle-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/toggle.md"
|
"api": "https://base-ui.com/react/components/toggle.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1276,12 +1276,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/toggle-group",
|
"docs": "https://ui.shadcn.com/docs/components/radix/toggle-group",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/toggle-group-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/toggle-group-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/toggle-group.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/toggle-group.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/toggle-group",
|
"docs": "https://ui.shadcn.com/docs/components/base/toggle-group",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/toggle-group-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/toggle-group-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/toggle-group.md"
|
"api": "https://base-ui.com/react/components/toggle-group.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1301,12 +1301,12 @@
|
|||||||
"links": {
|
"links": {
|
||||||
"radix": {
|
"radix": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/radix/tooltip",
|
"docs": "https://ui.shadcn.com/docs/components/radix/tooltip",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/radix/examples/tooltip-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/radix/examples/tooltip-example.tsx",
|
||||||
"api": "https://www.radix-ui.com/docs/primitives/components/tooltip.md"
|
"api": "https://www.radix-ui.com/docs/primitives/components/tooltip.md"
|
||||||
},
|
},
|
||||||
"base": {
|
"base": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/tooltip",
|
"docs": "https://ui.shadcn.com/docs/components/base/tooltip",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/tooltip-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/tooltip-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/tooltip.md"
|
"api": "https://base-ui.com/react/components/tooltip.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,12 +233,6 @@
|
|||||||
"url": "https://eldoraui.site/r/{name}.json",
|
"url": "https://eldoraui.site/r/{name}.json",
|
||||||
"description": "An open-source, modern UI component library for React, built with TypeScript, Tailwind CSS, and Framer Motion. Eldora UI offers beautifully crafted, reusable components designed for performance and elegance."
|
"description": "An open-source, modern UI component library for React, built with TypeScript, Tailwind CSS, and Framer Motion. Eldora UI offers beautifully crafted, reusable components designed for performance and elegance."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "@evilcharts",
|
|
||||||
"homepage": "https://evilcharts.com",
|
|
||||||
"url": "https://evilcharts.com/r/{name}.json",
|
|
||||||
"description": "EvilCharts is an open-source chart UI website built with shadcn and Recharts, beautifully designed and handcrafted."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@formcn",
|
"name": "@formcn",
|
||||||
"homepage": "https://formcn.dev",
|
"homepage": "https://formcn.dev",
|
||||||
@@ -275,12 +269,6 @@
|
|||||||
"url": "https://glass-ui.crenspire.com/r/{name}.json",
|
"url": "https://glass-ui.crenspire.com/r/{name}.json",
|
||||||
"description": "A shadcn-ui compatible registry distributing 40+ glassmorphic React/TypeScript components with Apple-inspired design. Components include enhanced visual effects (glow, shimmer, ripple), theme support, and customizable glassmorphism styling."
|
"description": "A shadcn-ui compatible registry distributing 40+ glassmorphic React/TypeScript components with Apple-inspired design. Components include enhanced visual effects (glow, shimmer, ripple), theme support, and customizable glassmorphism styling."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "@glasscn",
|
|
||||||
"homepage": "https://glasscn.vercel.app/",
|
|
||||||
"url": "https://glasscn-components.vercel.app/r/{name}.json",
|
|
||||||
"description": "A shadcn-compatible registry of glassmorphism components inspired by Apple"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@ha-components",
|
"name": "@ha-components",
|
||||||
"homepage": "https://hacomponents.keshuac.com",
|
"homepage": "https://hacomponents.keshuac.com",
|
||||||
@@ -359,12 +347,6 @@
|
|||||||
"url": "https://limeplay.winoffrg.dev/r/{name}.json",
|
"url": "https://limeplay.winoffrg.dev/r/{name}.json",
|
||||||
"description": "Modern UI Library for building media players in React. Powered by Shaka Player."
|
"description": "Modern UI Library for building media players in React. Powered by Shaka Player."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "@loading-ui",
|
|
||||||
"homepage": "https://loading-ui.com",
|
|
||||||
"url": "https://loading-ui.com/r/{name}.json",
|
|
||||||
"description": "Spinners, loaders, and loading animations for modern web apps. Free and open-source."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@lmscn",
|
"name": "@lmscn",
|
||||||
"homepage": "https://lmscn.vercel.app",
|
"homepage": "https://lmscn.vercel.app",
|
||||||
@@ -419,12 +401,6 @@
|
|||||||
"url": "https://motion-primitives.com/c/{name}.json",
|
"url": "https://motion-primitives.com/c/{name}.json",
|
||||||
"description": "Beautifully designed motions components. Easy copy-paste. Customizable. Open Source. Built for engineers and designers."
|
"description": "Beautifully designed motions components. Easy copy-paste. Customizable. Open Source. Built for engineers and designers."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "@nordaun",
|
|
||||||
"homepage": "https://ui.nordaun.com",
|
|
||||||
"url": "https://ui.nordaun.com/r/{name}.json",
|
|
||||||
"description": "Simple components for your extraordinary creations."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@ncdai",
|
"name": "@ncdai",
|
||||||
"homepage": "https://chanhdai.com/components",
|
"homepage": "https://chanhdai.com/components",
|
||||||
@@ -615,7 +591,7 @@
|
|||||||
"name": "@shadcnblocks",
|
"name": "@shadcnblocks",
|
||||||
"homepage": "https://shadcnblocks.com",
|
"homepage": "https://shadcnblocks.com",
|
||||||
"url": "https://shadcnblocks.com/r/{name}.json",
|
"url": "https://shadcnblocks.com/r/{name}.json",
|
||||||
"description": "A shadcn/ui registry with 1429 blocks, 1189 component variants, 14 templates, themes, and admin dashboard patterns."
|
"description": "A registry with hundreds of extra blocks for shadcn ui."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@shadcndesign",
|
"name": "@shadcndesign",
|
||||||
@@ -851,12 +827,6 @@
|
|||||||
"url": "https://darshitdev.in/r/{name}.json",
|
"url": "https://darshitdev.in/r/{name}.json",
|
||||||
"description": "Magic 3D Tabs component featuring mouse-interactive 3D rotation, floating particles background effect, and smooth spring animations."
|
"description": "Magic 3D Tabs component featuring mouse-interactive 3D rotation, floating particles background effect, and smooth spring animations."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "@devl",
|
|
||||||
"homepage": "https://devl.dev",
|
|
||||||
"url": "https://devl.dev/r/{name}.json",
|
|
||||||
"description": "Hand-crafted layouts and UI primitives for shipping fast."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "@beste-ui",
|
"name": "@beste-ui",
|
||||||
"homepage": "https://ui.beste.co",
|
"homepage": "https://ui.beste.co",
|
||||||
@@ -1060,53 +1030,5 @@
|
|||||||
"homepage": "https://www.remocn.dev/",
|
"homepage": "https://www.remocn.dev/",
|
||||||
"url": "https://www.remocn.dev/r/{name}.json",
|
"url": "https://www.remocn.dev/r/{name}.json",
|
||||||
"description": "Production-ready components for Remotion - text animations, backgrounds, transitions, UI blocks, and full scene compositions"
|
"description": "Production-ready components for Remotion - text animations, backgrounds, transitions, UI blocks, and full scene compositions"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@exabase",
|
|
||||||
"homepage": "https://exawizards.com/exabase/design/",
|
|
||||||
"url": "https://exawizards.com/exabase/design/registry/{name}.json",
|
|
||||||
"description": "A collection of UI components based on the exaBase Design System, built with React and Tailwind CSS."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@aicanvas",
|
|
||||||
"homepage": "https://aicanvas.me",
|
|
||||||
"url": "https://aicanvas.me/r/{name}.json",
|
|
||||||
"description": "54 animated React components with AI reproduction prompts for Claude Code, Lovable, and v0. Free and open source."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@xcn",
|
|
||||||
"homepage": "https://ui.radiumcoders.com",
|
|
||||||
"url": "https://ui.radiumcoders.com/r/xcn/{name}.json",
|
|
||||||
"description": "Hand-crafted, beautiful, and minimal UI components built with Tailwind CSS and Motion."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@dotmatrix",
|
|
||||||
"homepage": "https://dotmatrix.zzzzshawn.cloud",
|
|
||||||
"url": "https://dotmatrix.zzzzshawn.cloud/r/{name}.json",
|
|
||||||
"description": "Production-ready dot-matrix loading components for React, featuring square, circular, and triangle animations with polished motion."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@delta",
|
|
||||||
"homepage": "https://deltacomponents.dev",
|
|
||||||
"url": "https://deltacomponents.dev/r/{name}.json",
|
|
||||||
"description": "A shadcn registry for AI and media-rich interfaces — streaming LLM chat, zoomable images, swipeable card decks, interactive maps, plus dashboard and landing-page blocks."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@shieldcn",
|
|
||||||
"homepage": "https://shieldcn.dev",
|
|
||||||
"url": "https://shieldcn.dev/r/{name}.json",
|
|
||||||
"description": "Beautiful README badges as a service. A shields.io alternative with the visual quality of shadcn/ui. Drop-in SVG badge components for npm, GitHub, Discord, and more."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@evilbuttons",
|
|
||||||
"homepage": "https://evilbuttons.radiumcoders.com/docs",
|
|
||||||
"url": "https://evilbuttons.radiumcoders.com/r/{name}.json",
|
|
||||||
"description": "A shadcn/ui registry featuring a collection of animated buttons built with Motion. Each component is designed to add punchy, interactive feedback to your UI with minimal setup."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@stepper",
|
|
||||||
"homepage": "https://francozeta-stepper.vercel.app",
|
|
||||||
"url": "https://francozeta-stepper.vercel.app/{name}.json",
|
|
||||||
"description": "A modern, accessible and composable Stepper component for React and Tailwind CSS. Built for shadcn/ui-style workflows with registry-first distribution."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"links": {
|
"links": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/accordion",
|
"docs": "https://ui.shadcn.com/docs/components/base/accordion",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/accordion-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/accordion-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/accordion.md"
|
"api": "https://base-ui.com/react/components/accordion.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"links": {
|
"links": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/alert-dialog",
|
"docs": "https://ui.shadcn.com/docs/components/base/alert-dialog",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/alert-dialog-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/alert-dialog-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/alert-dialog.md"
|
"api": "https://base-ui.com/react/components/alert-dialog.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "registry/base-luma/ui/alert.tsx",
|
"path": "registry/base-luma/ui/alert.tsx",
|
||||||
"content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/registry/base-luma/lib/utils\"\n\nconst alertVariants = cva(\n \"group/alert relative grid w-full gap-0.5 rounded-2xl border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: \"bg-card text-card-foreground\",\n destructive:\n \"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nfunction Alert({\n className,\n variant,\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof alertVariants>) {\n return (\n <div\n data-slot=\"alert\"\n role=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n {...props}\n />\n )\n}\n\nfunction AlertTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn(\n \"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDescription({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn(\n \"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AlertAction({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-action\"\n className={cn(\"absolute top-2.5 right-3\", className)}\n {...props}\n />\n )\n}\n\nexport { Alert, AlertTitle, AlertDescription, AlertAction }\n",
|
"content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/registry/base-luma/lib/utils\"\n\nconst alertVariants = cva(\n \"group/alert relative grid w-full gap-0.5 rounded-2xl border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: \"bg-card text-card-foreground\",\n destructive:\n \"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nfunction Alert({\n className,\n variant,\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof alertVariants>) {\n return (\n <div\n data-slot=\"alert\"\n role=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n {...props}\n />\n )\n}\n\nfunction AlertTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn(\n \"cn-font-heading font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDescription({\n className,\n ...props\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn(\n \"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AlertAction({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-action\"\n className={cn(\"absolute top-2.5 right-3\", className)}\n {...props}\n />\n )\n}\n\nexport { Alert, AlertTitle, AlertDescription, AlertAction }\n",
|
||||||
"type": "registry:ui"
|
"type": "registry:ui"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
"links": {
|
"links": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/alert",
|
"docs": "https://ui.shadcn.com/docs/components/base/alert",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/alert-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/alert-example.tsx"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "registry:ui"
|
"type": "registry:ui"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"links": {
|
"links": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/aspect-ratio",
|
"docs": "https://ui.shadcn.com/docs/components/base/aspect-ratio",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/aspect-ratio-example.tsx"
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/aspect-ratio-example.tsx"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "registry:ui"
|
"type": "registry:ui"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"links": {
|
"links": {
|
||||||
"docs": "https://ui.shadcn.com/docs/components/base/avatar",
|
"docs": "https://ui.shadcn.com/docs/components/base/avatar",
|
||||||
"examples": "https://ui.shadcn.com/code/apps/v4/registry/bases/base/examples/avatar-example.tsx",
|
"examples": "https://raw.githubusercontent.com/shadcn-ui/ui/refs/heads/main/apps/v4/registry/bases/base/examples/avatar-example.tsx",
|
||||||
"api": "https://base-ui.com/react/components/avatar.md"
|
"api": "https://base-ui.com/react/components/avatar.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||