diff --git a/.changeset/fresh-timers-init.md b/.changeset/fresh-timers-init.md new file mode 100644 index 0000000000..2308e653d9 --- /dev/null +++ b/.changeset/fresh-timers-init.md @@ -0,0 +1,5 @@ +--- +"shadcn": patch +--- + +Ensure `init` only runs template post-init hooks for newly created projects. diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 936c3193e6..4254113f29 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,5 +9,6 @@ "WebFetch(domain:github.com)" ], "deny": [] - } + }, + "outputStyle": "Explanatory" } diff --git a/.github/workflows/prerelease-comment.yml b/.github/workflows/prerelease-comment.yml index 41e7f693a2..238612231b 100644 --- a/.github/workflows/prerelease-comment.yml +++ b/.github/workflows/prerelease-comment.yml @@ -3,7 +3,7 @@ name: Write Beta Release comment on: workflow_run: - workflows: ["Release - Beta"] + workflows: ["Release"] types: - completed @@ -11,12 +11,13 @@ jobs: comment: if: | github.repository_owner == 'shadcn-ui' && - ${{ github.event.workflow_run.conclusion == 'success' }} + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest name: Write comment to the PR steps: - name: "Comment on PR" - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -53,7 +54,7 @@ jobs: ``` - name: "Remove the autorelease label once published" - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml deleted file mode 100644 index d77c975933..0000000000 --- a/.github/workflows/prerelease.yml +++ /dev/null @@ -1,64 +0,0 @@ -# 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ca6aabeb1..ab09eeb067 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,24 +2,81 @@ name: Release +run-name: ${{ github.event_name == 'pull_request' && format('Release Beta - PR {0}', github.event.number) || 'Release Stable' }} + on: + pull_request: + types: [labeled] + branches: + - main push: branches: - main -permissions: - id-token: write - contents: write - pull-requests: write - jobs: - release: - if: ${{ github.repository_owner == 'shadcn-ui' }} - name: Create a PR for release workflow + 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@v3 + 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: + if: ${{ github.event_name == 'push' && github.repository_owner == 'shadcn-ui' }} + name: Create Version PR or Publish Stable Release + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + pull-requests: write + + steps: + - name: Checkout Repo + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3441008c1d..98bca1942d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,6 +39,9 @@ jobs: key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- + - name: Install Bun + uses: oven-sh/setup-bun@v2 + - name: Install dependencies run: pnpm install diff --git a/.gitignore b/.gitignore index c040854c9f..58ebd7be60 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ build # misc .DS_Store +.eslintcache *.pem # debug @@ -43,3 +44,4 @@ tsconfig.tsbuildinfo .notes .playwright-mcp shadcn-workspace +.codex-artifacts diff --git a/apps/v4/app/(app)/(root)/components/appearance-settings.tsx b/apps/v4/app/(app)/(root)/components/appearance-settings.tsx index 216c89f3e0..cf9609833e 100644 --- a/apps/v4/app/(app)/(root)/components/appearance-settings.tsx +++ b/apps/v4/app/(app)/(root)/components/appearance-settings.tsx @@ -1,8 +1,10 @@ "use client" import * as React from "react" -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" +import { IconMinus, IconPlus } from "@tabler/icons-react" + +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" import { Field, FieldContent, @@ -13,11 +15,10 @@ import { FieldSeparator, FieldSet, FieldTitle, -} from "@/examples/radix/ui/field" -import { Input } from "@/examples/radix/ui/input" -import { RadioGroup, RadioGroupItem } from "@/examples/radix/ui/radio-group" -import { Switch } from "@/examples/radix/ui/switch" -import { IconMinus, IconPlus } from "@tabler/icons-react" +} from "@/styles/radix-nova/ui/field" +import { Input } from "@/styles/radix-nova/ui/input" +import { RadioGroup, RadioGroupItem } from "@/styles/radix-nova/ui/radio-group" +import { Switch } from "@/styles/radix-nova/ui/switch" export function AppearanceSettings() { const [gpuCount, setGpuCount] = React.useState(8) diff --git a/apps/v4/app/(app)/(root)/components/button-group-demo.tsx b/apps/v4/app/(app)/(root)/components/button-group-demo.tsx index ccfc877544..e515c5fbda 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-demo.tsx @@ -1,8 +1,20 @@ "use client" import * as React from "react" -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" +import { + ArchiveIcon, + ArrowLeftIcon, + CalendarPlusIcon, + ClockIcon, + ListFilterIcon, + MailCheckIcon, + MoreHorizontalIcon, + TagIcon, + Trash2Icon, +} from "lucide-react" + +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" import { DropdownMenu, DropdownMenuContent, @@ -15,18 +27,7 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from "@/examples/radix/ui/dropdown-menu" -import { - ArchiveIcon, - ArrowLeftIcon, - CalendarPlusIcon, - ClockIcon, - ListFilterIcon, - MailCheckIcon, - MoreHorizontalIcon, - TagIcon, - Trash2Icon, -} from "lucide-react" +} from "@/styles/radix-nova/ui/dropdown-menu" export function ButtonGroupDemo() { const [label, setLabel] = React.useState("personal") diff --git a/apps/v4/app/(app)/(root)/components/button-group-input-group.tsx b/apps/v4/app/(app)/(root)/components/button-group-input-group.tsx index 4fd4a4d964..fa871a5722 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-input-group.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-input-group.tsx @@ -1,20 +1,21 @@ "use client" import * as React from "react" -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" +import { AudioLinesIcon, PlusIcon } from "lucide-react" + +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, -} from "@/examples/radix/ui/input-group" +} from "@/styles/radix-nova/ui/input-group" import { Tooltip, TooltipContent, TooltipTrigger, -} from "@/examples/radix/ui/tooltip" -import { AudioLinesIcon, PlusIcon } from "lucide-react" +} from "@/styles/radix-nova/ui/tooltip" export function ButtonGroupInputGroup() { const [voiceEnabled, setVoiceEnabled] = React.useState(false) diff --git a/apps/v4/app/(app)/(root)/components/button-group-nested.tsx b/apps/v4/app/(app)/(root)/components/button-group-nested.tsx index e612ea740f..0226072797 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-nested.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-nested.tsx @@ -1,9 +1,10 @@ "use client" -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react" +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" + export function ButtonGroupNested() { return ( diff --git a/apps/v4/app/(app)/(root)/components/button-group-popover.tsx b/apps/v4/app/(app)/(root)/components/button-group-popover.tsx index 187b1c8787..e797d11e72 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-popover.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-popover.tsx @@ -1,13 +1,14 @@ -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" +import { BotIcon, ChevronDownIcon } from "lucide-react" + +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" import { Popover, PopoverContent, PopoverTrigger, -} from "@/examples/radix/ui/popover" -import { Separator } from "@/examples/radix/ui/separator" -import { Textarea } from "@/examples/radix/ui/textarea" -import { BotIcon, ChevronDownIcon } from "lucide-react" +} from "@/styles/radix-nova/ui/popover" +import { Separator } from "@/styles/radix-nova/ui/separator" +import { Textarea } from "@/styles/radix-nova/ui/textarea" export function ButtonGroupPopover() { return ( diff --git a/apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx b/apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx index 72b9e968b1..39434bbdaa 100644 --- a/apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx +++ b/apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx @@ -1,10 +1,12 @@ +import { PlusIcon } from "lucide-react" + import { Avatar, AvatarFallback, AvatarGroup, AvatarImage, -} from "@/examples/radix/ui/avatar" -import { Button } from "@/examples/radix/ui/button" +} from "@/styles/radix-nova/ui/avatar" +import { Button } from "@/styles/radix-nova/ui/button" import { Empty, EmptyContent, @@ -12,8 +14,7 @@ import { EmptyHeader, EmptyMedia, EmptyTitle, -} from "@/examples/radix/ui/empty" -import { PlusIcon } from "lucide-react" +} from "@/styles/radix-nova/ui/empty" export function EmptyAvatarGroup() { return ( diff --git a/apps/v4/app/(app)/(root)/components/field-checkbox.tsx b/apps/v4/app/(app)/(root)/components/field-checkbox.tsx index 595cac1f41..8942fbd597 100644 --- a/apps/v4/app/(app)/(root)/components/field-checkbox.tsx +++ b/apps/v4/app/(app)/(root)/components/field-checkbox.tsx @@ -1,5 +1,5 @@ -import { Checkbox } from "@/examples/radix/ui/checkbox" -import { Field, FieldLabel } from "@/examples/radix/ui/field" +import { Checkbox } from "@/styles/radix-nova/ui/checkbox" +import { Field, FieldLabel } from "@/styles/radix-nova/ui/field" export function FieldCheckbox() { return ( diff --git a/apps/v4/app/(app)/(root)/components/field-demo.tsx b/apps/v4/app/(app)/(root)/components/field-demo.tsx index 0f54aa9cf6..a209ee6158 100644 --- a/apps/v4/app/(app)/(root)/components/field-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/field-demo.tsx @@ -1,5 +1,5 @@ -import { Button } from "@/examples/radix/ui/button" -import { Checkbox } from "@/examples/radix/ui/checkbox" +import { Button } from "@/styles/radix-nova/ui/button" +import { Checkbox } from "@/styles/radix-nova/ui/checkbox" import { Field, FieldDescription, @@ -8,8 +8,8 @@ import { FieldLegend, FieldSeparator, FieldSet, -} from "@/examples/radix/ui/field" -import { Input } from "@/examples/radix/ui/input" +} from "@/styles/radix-nova/ui/field" +import { Input } from "@/styles/radix-nova/ui/input" import { Select, SelectContent, @@ -17,8 +17,8 @@ import { SelectItem, SelectTrigger, SelectValue, -} from "@/examples/radix/ui/select" -import { Textarea } from "@/examples/radix/ui/textarea" +} from "@/styles/radix-nova/ui/select" +import { Textarea } from "@/styles/radix-nova/ui/textarea" export function FieldDemo() { return ( diff --git a/apps/v4/app/(app)/(root)/components/field-hear.tsx b/apps/v4/app/(app)/(root)/components/field-hear.tsx index aa21c77d30..0d410f614a 100644 --- a/apps/v4/app/(app)/(root)/components/field-hear.tsx +++ b/apps/v4/app/(app)/(root)/components/field-hear.tsx @@ -1,5 +1,5 @@ -import { Card, CardContent } from "@/examples/radix/ui/card" -import { Checkbox } from "@/examples/radix/ui/checkbox" +import { Card, CardContent } from "@/styles/radix-nova/ui/card" +import { Checkbox } from "@/styles/radix-nova/ui/checkbox" import { Field, FieldDescription, @@ -8,7 +8,7 @@ import { FieldLegend, FieldSet, FieldTitle, -} from "@/examples/radix/ui/field" +} from "@/styles/radix-nova/ui/field" const options = [ { diff --git a/apps/v4/app/(app)/(root)/components/field-slider.tsx b/apps/v4/app/(app)/(root)/components/field-slider.tsx index b2ab12306c..cd44849c6f 100644 --- a/apps/v4/app/(app)/(root)/components/field-slider.tsx +++ b/apps/v4/app/(app)/(root)/components/field-slider.tsx @@ -1,8 +1,13 @@ "use client" import { useState } from "react" -import { Field, FieldDescription, FieldTitle } from "@/examples/radix/ui/field" -import { Slider } from "@/examples/radix/ui/slider" + +import { + Field, + FieldDescription, + FieldTitle, +} from "@/styles/radix-nova/ui/field" +import { Slider } from "@/styles/radix-nova/ui/slider" export function FieldSlider() { const [value, setValue] = useState([200, 800]) diff --git a/apps/v4/app/(app)/(root)/components/index.tsx b/apps/v4/app/(app)/(root)/components/index.tsx index 79640798e6..c00f9c06e1 100644 --- a/apps/v4/app/(app)/(root)/components/index.tsx +++ b/apps/v4/app/(app)/(root)/components/index.tsx @@ -1,4 +1,4 @@ -import { FieldSeparator } from "@/examples/radix/ui/field" +import { FieldSeparator } from "@/styles/radix-nova/ui/field" import { AppearanceSettings } from "./appearance-settings" import { ButtonGroupDemo } from "./button-group-demo" diff --git a/apps/v4/app/(app)/(root)/components/input-group-button.tsx b/apps/v4/app/(app)/(root)/components/input-group-button.tsx index c2ad283fbf..3880a6543f 100644 --- a/apps/v4/app/(app)/(root)/components/input-group-button.tsx +++ b/apps/v4/app/(app)/(root)/components/input-group-button.tsx @@ -1,19 +1,20 @@ "use client" import * as React from "react" +import { IconInfoCircle, IconStar } from "@tabler/icons-react" + import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, -} from "@/examples/radix/ui/input-group" -import { Label } from "@/examples/radix/ui/label" +} from "@/styles/radix-nova/ui/input-group" +import { Label } from "@/styles/radix-nova/ui/label" import { Popover, PopoverContent, PopoverTrigger, -} from "@/examples/radix/ui/popover" -import { IconInfoCircle, IconStar } from "@tabler/icons-react" +} from "@/styles/radix-nova/ui/popover" export function InputGroupButtonExample() { const [isFavorite, setIsFavorite] = React.useState(false) diff --git a/apps/v4/app/(app)/(root)/components/input-group-demo.tsx b/apps/v4/app/(app)/(root)/components/input-group-demo.tsx index 641e523c0a..2e4101f973 100644 --- a/apps/v4/app/(app)/(root)/components/input-group-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/input-group-demo.tsx @@ -1,9 +1,12 @@ +import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react" +import { ArrowUpIcon, Search } from "lucide-react" + import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/examples/radix/ui/dropdown-menu" +} from "@/styles/radix-nova/ui/dropdown-menu" import { InputGroup, InputGroupAddon, @@ -11,15 +14,13 @@ import { InputGroupInput, InputGroupText, InputGroupTextarea, -} from "@/examples/radix/ui/input-group" -import { Separator } from "@/examples/radix/ui/separator" +} from "@/styles/radix-nova/ui/input-group" +import { Separator } from "@/styles/radix-nova/ui/separator" import { Tooltip, TooltipContent, TooltipTrigger, -} from "@/examples/radix/ui/tooltip" -import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react" -import { ArrowUpIcon, Search } from "lucide-react" +} from "@/styles/radix-nova/ui/tooltip" export function InputGroupDemo() { return ( diff --git a/apps/v4/app/(app)/(root)/components/item-demo.tsx b/apps/v4/app/(app)/(root)/components/item-demo.tsx index 2c97c53e99..9f128396c7 100644 --- a/apps/v4/app/(app)/(root)/components/item-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/item-demo.tsx @@ -1,4 +1,6 @@ -import { Button } from "@/examples/radix/ui/button" +import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react" + +import { Button } from "@/styles/radix-nova/ui/button" import { Item, ItemActions, @@ -6,8 +8,7 @@ import { ItemDescription, ItemMedia, ItemTitle, -} from "@/examples/radix/ui/item" -import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react" +} from "@/styles/radix-nova/ui/item" export function ItemDemo() { return ( diff --git a/apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx b/apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx index 3e84b1ffa5..973c41cf3a 100644 --- a/apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx +++ b/apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx @@ -1,8 +1,24 @@ "use client" import { useMemo, useState } from "react" -import { Avatar, AvatarFallback, AvatarImage } from "@/examples/radix/ui/avatar" -import { Badge } from "@/examples/radix/ui/badge" +import { + IconApps, + IconArrowUp, + IconAt, + IconBook, + IconCircleDashedPlus, + IconPaperclip, + IconPlus, + IconWorld, + IconX, +} from "@tabler/icons-react" + +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/styles/radix-nova/ui/avatar" +import { Badge } from "@/styles/radix-nova/ui/badge" import { Command, CommandEmpty, @@ -10,7 +26,7 @@ import { CommandInput, CommandItem, CommandList, -} from "@/examples/radix/ui/command" +} from "@/styles/radix-nova/ui/command" import { DropdownMenu, DropdownMenuCheckboxItem, @@ -23,36 +39,25 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from "@/examples/radix/ui/dropdown-menu" -import { Field, FieldLabel } from "@/examples/radix/ui/field" +} from "@/styles/radix-nova/ui/dropdown-menu" +import { Field, FieldLabel } from "@/styles/radix-nova/ui/field" import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea, -} from "@/examples/radix/ui/input-group" +} from "@/styles/radix-nova/ui/input-group" import { Popover, PopoverContent, PopoverTrigger, -} from "@/examples/radix/ui/popover" -import { Switch } from "@/examples/radix/ui/switch" +} from "@/styles/radix-nova/ui/popover" +import { Switch } from "@/styles/radix-nova/ui/switch" import { Tooltip, TooltipContent, TooltipTrigger, -} from "@/examples/radix/ui/tooltip" -import { - IconApps, - IconArrowUp, - IconAt, - IconBook, - IconCircleDashedPlus, - IconPaperclip, - IconPlus, - IconWorld, - IconX, -} from "@tabler/icons-react" +} from "@/styles/radix-nova/ui/tooltip" const SAMPLE_DATA = { mentionable: [ diff --git a/apps/v4/app/(app)/(root)/components/spinner-badge.tsx b/apps/v4/app/(app)/(root)/components/spinner-badge.tsx index 3bff547115..3c6c7264d5 100644 --- a/apps/v4/app/(app)/(root)/components/spinner-badge.tsx +++ b/apps/v4/app/(app)/(root)/components/spinner-badge.tsx @@ -1,5 +1,5 @@ -import { Badge } from "@/examples/radix/ui/badge" -import { Spinner } from "@/examples/radix/ui/spinner" +import { Badge } from "@/styles/radix-nova/ui/badge" +import { Spinner } from "@/styles/radix-nova/ui/spinner" export function SpinnerBadge() { return ( diff --git a/apps/v4/app/(app)/(root)/components/spinner-empty.tsx b/apps/v4/app/(app)/(root)/components/spinner-empty.tsx index 1ff6b0b8b2..70914e98a7 100644 --- a/apps/v4/app/(app)/(root)/components/spinner-empty.tsx +++ b/apps/v4/app/(app)/(root)/components/spinner-empty.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/examples/radix/ui/button" +import { Button } from "@/styles/radix-nova/ui/button" import { Empty, EmptyContent, @@ -6,8 +6,8 @@ import { EmptyHeader, EmptyMedia, EmptyTitle, -} from "@/examples/radix/ui/empty" -import { Spinner } from "@/examples/radix/ui/spinner" +} from "@/styles/radix-nova/ui/empty" +import { Spinner } from "@/styles/radix-nova/ui/spinner" export function SpinnerEmpty() { return ( diff --git a/apps/v4/app/(app)/(root)/page.tsx b/apps/v4/app/(app)/(root)/page.tsx index 61f8998115..f9bbb62761 100644 --- a/apps/v4/app/(app)/(root)/page.tsx +++ b/apps/v4/app/(app)/(root)/page.tsx @@ -3,15 +3,12 @@ import Image from "next/image" import Link from "next/link" import { Announcement } from "@/components/announcement" -import { ExamplesNav } from "@/components/examples-nav" import { PageActions, PageHeader, PageHeaderDescription, PageHeaderHeading, } 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 { RootComponents } from "./components" @@ -63,11 +60,7 @@ export default function IndexPage() { - - - - -
+
+ + + + + + + + + + + + + Title + Author + Issue + Status + Progress + + + + {ARTICLE_ROWS.map((row) => ( + + +
+

+ {row.title} +

+

+ {row.wordProgress} +

+
+
+ {row.author} + {row.issue} + + + + {row.statusLabel} + + + + + + {(formattedValue) => `${formattedValue}`} + + + +
+ ))} +
+
+
+ + + + + + + + + {[1, 2, 3].map((page) => ( + + + {page} + + + ))} + + + + + + + + + + ) +} diff --git a/apps/v4/app/(app)/(styles)/sera/article-directory/components/preview-header.tsx b/apps/v4/app/(app)/(styles)/sera/article-directory/components/preview-header.tsx new file mode 100644 index 0000000000..e03a2a951d --- /dev/null +++ b/apps/v4/app/(app)/(styles)/sera/article-directory/components/preview-header.tsx @@ -0,0 +1,47 @@ +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 ( +
+
+
+ + + + + + Editorial Dashboard + + + + +

+ Article Directory +

+
+
+ + + +
+
+
+ ) +} diff --git a/apps/v4/app/(app)/(styles)/sera/article-directory/index.tsx b/apps/v4/app/(app)/(styles)/sera/article-directory/index.tsx new file mode 100644 index 0000000000..5413269751 --- /dev/null +++ b/apps/v4/app/(app)/(styles)/sera/article-directory/index.tsx @@ -0,0 +1,16 @@ +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 ( +
+ + +
+ +
+
+ ) +} diff --git a/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/demographics.tsx b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/demographics.tsx new file mode 100644 index 0000000000..3c086c8e04 --- /dev/null +++ b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/demographics.tsx @@ -0,0 +1,56 @@ +"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) { + return ( + + + Demographics + Reader Profile + + + {DEMOGRAPHIC_DATA.map((item) => ( + + {item.age} + + {(formattedValue) => `${formattedValue}`} + + + ))} + + + + + + ) +} diff --git a/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/metrics-grid.tsx b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/metrics-grid.tsx new file mode 100644 index 0000000000..55fe6c1a1e --- /dev/null +++ b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/metrics-grid.tsx @@ -0,0 +1,93 @@ +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) => ( + + ))} + + ) +} + +function MetricCard({ + metric, + className, +}: { + metric: Metric + className: string +}) { + return ( + + + + {metric.label} + + + {metric.value} + + + {metric.trend === "up" ? ( + + ) : ( + + )}{" "} + {metric.comparison}{" "} + {metric.change} + + + + ) +} diff --git a/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/preview-header.tsx b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/preview-header.tsx new file mode 100644 index 0000000000..eb7ecc5c7a --- /dev/null +++ b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/preview-header.tsx @@ -0,0 +1,103 @@ +"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 ( +
+
+
+

+ Audience Analytics +

+
+ Editorial Performance Dashboard +
+
+ + + + } + > + {selectedDateRangeLabel}{" "} + + + + + + {EXPORT_DATE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + + + + +
+
+ ) +} diff --git a/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/top-editorial.tsx b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/top-editorial.tsx new file mode 100644 index 0000000000..e210198bb5 --- /dev/null +++ b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/top-editorial.tsx @@ -0,0 +1,257 @@ +"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 = { + 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 & { + 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 ( + + +
+
+ Top Editorials + Ranked by engagement +
+ + {(["views", "time", "shares"] as const).map((metric) => { + return ( + + {METRIC_LABEL[metric]} + + ) + })} + +
+
+ + + + + # + Title + Published + Page Views + Read Time + Actions + + + + {visibleRows.map((row) => ( + + + {row.rank} + + +
+

+ {row.title} +

+

+ By {row.author} +

+
+
+ {row.published} + {row.pageviews} + {row.avgTime} + + + } + aria-label={`Open actions for ${row.title}`} + > + + + + Edit + Publish + + Delete + + + + +
+ ))} +
+
+
+ + {hasMoreRows ? ( + + ) : null} + +
+ ) +} diff --git a/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/traffic-overview-deferred.tsx b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/traffic-overview-deferred.tsx new file mode 100644 index 0000000000..e8c893380b --- /dev/null +++ b/apps/v4/app/(app)/(styles)/sera/audience-analytics/components/traffic-overview-deferred.tsx @@ -0,0 +1,57 @@ +"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: () => , + } +) + +export function TrafficOverviewDeferred({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ +
+ ) +} + +function TrafficOverviewFallback() { + return ( + + + Traffic Overview + + Traffic for the last 30 days has increased by 12.4% compared to the + previous period. + + + +