snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
All checks were successful
Release / Create Version PR or Publish Stable Release (push) Has been skipped
Release / Publish Prerelease to NPM (push) Has been skipped
Monitor Registries / Monitor registry health (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 22:44:43 +05:00
commit 1416db152c
5727 changed files with 630238 additions and 0 deletions

View File

@@ -0,0 +1,921 @@
import { type Registry } from "shadcn/schema"
export const blocks: Registry["items"] = [
{
name: "preview",
title: "Preview",
type: "registry:block",
registryDependencies: [
"alert-dialog",
"avatar",
"badge",
"button",
"button-group",
"card",
"chart",
"checkbox",
"combobox",
"dropdown-menu",
"empty",
"field",
"input",
"input-group",
"item",
"label",
"popover",
"radio-group",
"select",
"separator",
"sheet",
"slider",
"spinner",
"switch",
"textarea",
"tooltip",
"example",
],
files: [
{
path: "blocks/preview/index.tsx",
type: "registry:block",
},
],
},
{
name: "preview-02",
title: "Preview 02",
type: "registry:block",
dependencies: ["react-qr-code"],
registryDependencies: [
"accordion",
"badge",
"breadcrumb",
"button",
"calendar",
"card",
"chart",
"checkbox",
"combobox",
"dropdown-menu",
"empty",
"field",
"input",
"input-group",
"item",
"label",
"native-select",
"progress",
"radio-group",
"select",
"separator",
"sidebar",
"skeleton",
"slider",
"spinner",
"switch",
"table",
"tabs",
"textarea",
"toggle-group",
],
files: [
{
path: "blocks/preview-02/index.tsx",
type: "registry:block",
},
],
},
{
name: "preview-03",
title: "Preview 03",
type: "registry:block",
registryDependencies: [],
files: [
{
path: "blocks/preview-03/index.tsx",
type: "registry:block",
},
],
},
{
name: "login-01",
title: "Login 01",
description: "A simple login form.",
type: "registry:block",
registryDependencies: ["button", "card", "input", "label", "field"],
files: [
{
path: "blocks/login-01/page.tsx",
target: "app/login/page.tsx",
type: "registry:page",
},
{
path: "blocks/login-01/components/login-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "login"],
},
{
name: "login-02",
title: "Login 02",
description: "A two column login page with a cover image.",
type: "registry:block",
registryDependencies: ["button", "input", "label", "field"],
files: [
{
path: "blocks/login-02/page.tsx",
target: "app/login/page.tsx",
type: "registry:page",
},
{
path: "blocks/login-02/components/login-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "login"],
},
{
name: "login-03",
title: "Login 03",
description: "A login page with a muted background color.",
type: "registry:block",
registryDependencies: ["button", "card", "input", "label", "field"],
files: [
{
path: "blocks/login-03/page.tsx",
target: "app/login/page.tsx",
type: "registry:page",
},
{
path: "blocks/login-03/components/login-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "login"],
},
{
name: "login-04",
title: "Login 04",
description: "A login page with form and image.",
type: "registry:block",
registryDependencies: ["button", "card", "input", "label", "field"],
files: [
{
path: "blocks/login-04/page.tsx",
target: "app/login/page.tsx",
type: "registry:page",
},
{
path: "blocks/login-04/components/login-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "login"],
},
{
name: "login-05",
title: "Login 05",
description: "A simple email-only login page.",
type: "registry:block",
registryDependencies: ["button", "input", "label", "field"],
files: [
{
path: "blocks/login-05/page.tsx",
target: "app/login/page.tsx",
type: "registry:page",
},
{
path: "blocks/login-05/components/login-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "login"],
},
{
name: "signup-01",
title: "Signup 01",
description: "A simple signup form.",
type: "registry:block",
registryDependencies: ["button", "card", "input", "label"],
files: [
{
path: "blocks/signup-01/page.tsx",
target: "app/signup/page.tsx",
type: "registry:page",
},
{
path: "blocks/signup-01/components/signup-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "signup"],
},
{
name: "signup-02",
title: "Signup 02",
description: "A two column signup page with a cover image.",
type: "registry:block",
registryDependencies: ["button", "input", "label", "field"],
files: [
{
path: "blocks/signup-02/page.tsx",
target: "app/signup/page.tsx",
type: "registry:page",
},
{
path: "blocks/signup-02/components/signup-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "signup"],
},
{
name: "signup-03",
title: "Signup 03",
description: "A signup page with a muted background color.",
type: "registry:block",
registryDependencies: ["button", "card", "input", "label", "field"],
files: [
{
path: "blocks/signup-03/page.tsx",
target: "app/signup/page.tsx",
type: "registry:page",
},
{
path: "blocks/signup-03/components/signup-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "signup"],
},
{
name: "signup-04",
title: "Signup 04",
description: "A signup page with form and image.",
type: "registry:block",
registryDependencies: ["button", "card", "input", "label", "field"],
files: [
{
path: "blocks/signup-04/page.tsx",
target: "app/signup/page.tsx",
type: "registry:page",
},
{
path: "blocks/signup-04/components/signup-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "signup"],
},
{
name: "signup-05",
title: "Signup 05",
description: "A simple signup form with social providers.",
type: "registry:block",
registryDependencies: ["button", "input", "label"],
files: [
{
path: "blocks/signup-05/page.tsx",
target: "app/signup/page.tsx",
type: "registry:page",
},
{
path: "blocks/signup-05/components/signup-form.tsx",
type: "registry:component",
},
],
categories: ["authentication", "signup"],
},
{
name: "dashboard-01",
title: "Dashboard 01",
type: "registry:block",
description: "A dashboard with sidebar, charts and data table.",
dependencies: [
"@dnd-kit/core",
"@dnd-kit/modifiers",
"@dnd-kit/sortable",
"@dnd-kit/utilities",
"@tanstack/react-table",
"zod",
],
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"label",
"chart",
"card",
"select",
"tabs",
"table",
"toggle-group",
"badge",
"button",
"checkbox",
"dropdown-menu",
"drawer",
"input",
"avatar",
"sheet",
"sonner",
],
files: [
{
path: "blocks/dashboard-01/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/dashboard-01/data.json",
type: "registry:file",
target: "app/dashboard/data.json",
},
{
path: "blocks/dashboard-01/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/dashboard-01/components/chart-area-interactive.tsx",
type: "registry:component",
},
{
path: "blocks/dashboard-01/components/data-table.tsx",
type: "registry:component",
},
{
path: "blocks/dashboard-01/components/nav-documents.tsx",
type: "registry:component",
},
{
path: "blocks/dashboard-01/components/nav-main.tsx",
type: "registry:component",
},
{
path: "blocks/dashboard-01/components/nav-secondary.tsx",
type: "registry:component",
},
{
path: "blocks/dashboard-01/components/nav-user.tsx",
type: "registry:component",
},
{
path: "blocks/dashboard-01/components/section-cards.tsx",
type: "registry:component",
},
{
path: "blocks/dashboard-01/components/site-header.tsx",
type: "registry:component",
},
],
categories: ["dashboard"],
meta: {
iframeHeight: "1000px",
},
},
{
name: "sidebar-01",
title: "Sidebar 01",
type: "registry:block",
description: "A simple sidebar with navigation grouped by section.",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"label",
"dropdown-menu",
],
files: [
{
path: "blocks/sidebar-01/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-01/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-01/components/search-form.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-01/components/version-switcher.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-02",
title: "Sidebar 02",
description: "A sidebar with collapsible sections.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"label",
"dropdown-menu",
],
files: [
{
path: "blocks/sidebar-02/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-02/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-02/components/search-form.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-02/components/version-switcher.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-03",
title: "Sidebar 03",
description: "A sidebar with submenus.",
type: "registry:block",
registryDependencies: ["sidebar", "breadcrumb"],
files: [
{
path: "blocks/sidebar-03/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-03/components/app-sidebar.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-04",
title: "Sidebar 04",
description: "A floating sidebar with submenus.",
type: "registry:block",
registryDependencies: ["sidebar", "breadcrumb", "separator"],
files: [
{
path: "blocks/sidebar-04/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-04/components/app-sidebar.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-05",
title: "Sidebar 05",
description: "A sidebar with collapsible submenus.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"label",
"collapsible",
],
files: [
{
path: "blocks/sidebar-05/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-05/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-05/components/search-form.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-06",
title: "Sidebar 06",
description: "A sidebar with submenus as dropdowns.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"card",
"dropdown-menu",
],
files: [
{
path: "blocks/sidebar-06/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-06/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-06/components/nav-main.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-06/components/sidebar-opt-in-form.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-07",
title: "Sidebar 07",
type: "registry:block",
description: "A sidebar that collapses to icons.",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"collapsible",
"dropdown-menu",
"avatar",
],
files: [
{
path: "blocks/sidebar-07/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-07/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-07/components/nav-main.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-07/components/nav-projects.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-07/components/nav-user.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-07/components/team-switcher.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-08",
title: "Sidebar 08",
description: "An inset sidebar with secondary navigation.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"collapsible",
"dropdown-menu",
"avatar",
],
files: [
{
path: "blocks/sidebar-08/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-08/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-08/components/nav-main.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-08/components/nav-projects.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-08/components/nav-secondary.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-08/components/nav-user.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-09",
title: "Sidebar 09",
description: "Collapsible nested sidebars.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"collapsible",
"dropdown-menu",
"avatar",
"switch",
"label",
],
files: [
{
path: "blocks/sidebar-09/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-09/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-09/components/nav-user.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-10",
title: "Sidebar 10",
description: "A sidebar in a popover.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"popover",
"collapsible",
"dropdown-menu",
],
files: [
{
path: "blocks/sidebar-10/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-10/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-10/components/nav-actions.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-10/components/nav-favorites.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-10/components/nav-main.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-10/components/nav-secondary.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-10/components/nav-workspaces.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-10/components/team-switcher.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-11",
title: "Sidebar 11",
description: "A sidebar with a collapsible file tree.",
type: "registry:block",
registryDependencies: ["sidebar", "breadcrumb", "separator", "collapsible"],
files: [
{
path: "blocks/sidebar-11/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-11/components/app-sidebar.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-12",
title: "Sidebar 12",
description: "A sidebar with a calendar.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"collapsible",
"calendar",
"dropdown-menu",
"avatar",
],
files: [
{
path: "blocks/sidebar-12/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-12/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-12/components/calendars.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-12/components/date-picker.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-12/components/nav-user.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-13",
title: "Sidebar 13",
description: "A sidebar in a dialog.",
type: "registry:block",
registryDependencies: ["sidebar", "breadcrumb", "button", "dialog"],
files: [
{
path: "blocks/sidebar-13/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-13/components/settings-dialog.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-14",
title: "Sidebar 14",
description: "A sidebar on the right.",
type: "registry:block",
registryDependencies: ["sidebar", "breadcrumb"],
files: [
{
path: "blocks/sidebar-14/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-14/components/app-sidebar.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-15",
title: "Sidebar 15",
description: "A left and right sidebar.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"popover",
"collapsible",
"dropdown-menu",
"calendar",
"avatar",
],
files: [
{
path: "blocks/sidebar-15/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-15/components/calendars.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/date-picker.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/nav-favorites.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/nav-main.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/nav-secondary.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/nav-user.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/nav-workspaces.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/sidebar-left.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/sidebar-right.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-15/components/team-switcher.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
{
name: "sidebar-16",
title: "Sidebar 16",
description: "A sidebar with a sticky site header.",
type: "registry:block",
registryDependencies: [
"sidebar",
"breadcrumb",
"separator",
"collapsible",
"dropdown-menu",
"avatar",
"button",
"label",
],
files: [
{
path: "blocks/sidebar-16/page.tsx",
type: "registry:page",
target: "app/dashboard/page.tsx",
},
{
path: "blocks/sidebar-16/components/app-sidebar.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-16/components/nav-main.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-16/components/nav-projects.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-16/components/nav-secondary.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-16/components/nav-user.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-16/components/search-form.tsx",
type: "registry:component",
},
{
path: "blocks/sidebar-16/components/site-header.tsx",
type: "registry:component",
},
],
categories: ["sidebar", "dashboard"],
},
]

View File

@@ -0,0 +1,281 @@
"use client"
import * as React from "react"
import { NavDocuments } from "@/registry/bases/aria/blocks/dashboard-01/components/nav-documents"
import { NavMain } from "@/registry/bases/aria/blocks/dashboard-01/components/nav-main"
import { NavSecondary } from "@/registry/bases/aria/blocks/dashboard-01/components/nav-secondary"
import { NavUser } from "@/registry/bases/aria/blocks/dashboard-01/components/nav-user"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
navMain: [
{
title: "Dashboard",
url: "#",
icon: (
<IconPlaceholder
lucide="LayoutDashboardIcon"
tabler="IconDashboard"
hugeicons="DashboardSquare01Icon"
phosphor="SquaresFourIcon"
remixicon="RiDashboardLine"
/>
),
},
{
title: "Lifecycle",
url: "#",
icon: (
<IconPlaceholder
lucide="ListIcon"
tabler="IconListDetails"
hugeicons="Menu01Icon"
phosphor="ListIcon"
remixicon="RiListUnordered"
/>
),
},
{
title: "Analytics",
url: "#",
icon: (
<IconPlaceholder
lucide="ChartBarIcon"
tabler="IconChartBar"
hugeicons="ChartHistogramIcon"
phosphor="ChartBarIcon"
remixicon="RiBarChartLine"
/>
),
},
{
title: "Projects",
url: "#",
icon: (
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="Folder01Icon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
),
},
{
title: "Team",
url: "#",
icon: (
<IconPlaceholder
lucide="UsersIcon"
tabler="IconUsers"
hugeicons="UserGroupIcon"
phosphor="UsersIcon"
remixicon="RiGroupLine"
/>
),
},
],
navClouds: [
{
title: "Capture",
icon: (
<IconPlaceholder
lucide="CameraIcon"
tabler="IconCamera"
hugeicons="Camera01Icon"
phosphor="CameraIcon"
remixicon="RiCameraLine"
/>
),
isActive: true,
url: "#",
items: [
{
title: "Active Proposals",
url: "#",
},
{
title: "Archived",
url: "#",
},
],
},
{
title: "Proposal",
icon: (
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileDescription"
hugeicons="File01Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
),
url: "#",
items: [
{
title: "Active Proposals",
url: "#",
},
{
title: "Archived",
url: "#",
},
],
},
{
title: "Prompts",
icon: (
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileAi"
hugeicons="File01Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
),
url: "#",
items: [
{
title: "Active Proposals",
url: "#",
},
{
title: "Archived",
url: "#",
},
],
},
],
navSecondary: [
{
title: "Settings",
url: "#",
icon: (
<IconPlaceholder
lucide="Settings2Icon"
tabler="IconSettings"
hugeicons="Settings05Icon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
),
},
{
title: "Get Help",
url: "#",
icon: (
<IconPlaceholder
lucide="CircleHelpIcon"
tabler="IconHelp"
hugeicons="HelpCircleIcon"
phosphor="QuestionIcon"
remixicon="RiQuestionLine"
/>
),
},
{
title: "Search",
url: "#",
icon: (
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
),
},
],
documents: [
{
name: "Data Library",
url: "#",
icon: (
<IconPlaceholder
lucide="DatabaseIcon"
tabler="IconDatabase"
hugeicons="Database01Icon"
phosphor="DatabaseIcon"
remixicon="RiDatabase2Line"
/>
),
},
{
name: "Reports",
url: "#",
icon: (
<IconPlaceholder
lucide="FileChartColumnIcon"
tabler="IconReport"
hugeicons="Analytics01Icon"
phosphor="ChartLineIcon"
remixicon="RiFileChartLine"
/>
),
},
{
name: "Word Assistant",
url: "#",
icon: (
<IconPlaceholder
lucide="FileIcon"
tabler="IconFileWord"
hugeicons="File01Icon"
phosphor="FileIcon"
remixicon="RiFileLine"
/>
),
},
],
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar collapsible="offcanvas" {...props}>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
href="#"
className="data-[slot=sidebar-menu-button]:p-1.5!"
>
<IconPlaceholder
lucide="CommandIcon"
tabler="IconInnerShadowTop"
hugeicons="CommandIcon"
phosphor="CommandIcon"
remixicon="RiCommandLine"
className="size-5!"
/>
<span className="text-base font-semibold">Acme Inc.</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavDocuments items={data.documents} />
<NavSecondary items={data.navSecondary} className="mt-auto" />
</SidebarContent>
<SidebarFooter>
<NavUser user={data.user} />
</SidebarFooter>
</Sidebar>
)
}

View File

@@ -0,0 +1,300 @@
"use client"
import * as React from "react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import { useIsMobile } from "@/registry/bases/aria/hooks/use-mobile"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/aria/ui/toggle-group"
export const description = "An interactive area chart"
const chartData = [
{ date: "2024-04-01", desktop: 222, mobile: 150 },
{ date: "2024-04-02", desktop: 97, mobile: 180 },
{ date: "2024-04-03", desktop: 167, mobile: 120 },
{ date: "2024-04-04", desktop: 242, mobile: 260 },
{ date: "2024-04-05", desktop: 373, mobile: 290 },
{ date: "2024-04-06", desktop: 301, mobile: 340 },
{ date: "2024-04-07", desktop: 245, mobile: 180 },
{ date: "2024-04-08", desktop: 409, mobile: 320 },
{ date: "2024-04-09", desktop: 59, mobile: 110 },
{ date: "2024-04-10", desktop: 261, mobile: 190 },
{ date: "2024-04-11", desktop: 327, mobile: 350 },
{ date: "2024-04-12", desktop: 292, mobile: 210 },
{ date: "2024-04-13", desktop: 342, mobile: 380 },
{ date: "2024-04-14", desktop: 137, mobile: 220 },
{ date: "2024-04-15", desktop: 120, mobile: 170 },
{ date: "2024-04-16", desktop: 138, mobile: 190 },
{ date: "2024-04-17", desktop: 446, mobile: 360 },
{ date: "2024-04-18", desktop: 364, mobile: 410 },
{ date: "2024-04-19", desktop: 243, mobile: 180 },
{ date: "2024-04-20", desktop: 89, mobile: 150 },
{ date: "2024-04-21", desktop: 137, mobile: 200 },
{ date: "2024-04-22", desktop: 224, mobile: 170 },
{ date: "2024-04-23", desktop: 138, mobile: 230 },
{ date: "2024-04-24", desktop: 387, mobile: 290 },
{ date: "2024-04-25", desktop: 215, mobile: 250 },
{ date: "2024-04-26", desktop: 75, mobile: 130 },
{ date: "2024-04-27", desktop: 383, mobile: 420 },
{ date: "2024-04-28", desktop: 122, mobile: 180 },
{ date: "2024-04-29", desktop: 315, mobile: 240 },
{ date: "2024-04-30", desktop: 454, mobile: 380 },
{ date: "2024-05-01", desktop: 165, mobile: 220 },
{ date: "2024-05-02", desktop: 293, mobile: 310 },
{ date: "2024-05-03", desktop: 247, mobile: 190 },
{ date: "2024-05-04", desktop: 385, mobile: 420 },
{ date: "2024-05-05", desktop: 481, mobile: 390 },
{ date: "2024-05-06", desktop: 498, mobile: 520 },
{ date: "2024-05-07", desktop: 388, mobile: 300 },
{ date: "2024-05-08", desktop: 149, mobile: 210 },
{ date: "2024-05-09", desktop: 227, mobile: 180 },
{ date: "2024-05-10", desktop: 293, mobile: 330 },
{ date: "2024-05-11", desktop: 335, mobile: 270 },
{ date: "2024-05-12", desktop: 197, mobile: 240 },
{ date: "2024-05-13", desktop: 197, mobile: 160 },
{ date: "2024-05-14", desktop: 448, mobile: 490 },
{ date: "2024-05-15", desktop: 473, mobile: 380 },
{ date: "2024-05-16", desktop: 338, mobile: 400 },
{ date: "2024-05-17", desktop: 499, mobile: 420 },
{ date: "2024-05-18", desktop: 315, mobile: 350 },
{ date: "2024-05-19", desktop: 235, mobile: 180 },
{ date: "2024-05-20", desktop: 177, mobile: 230 },
{ date: "2024-05-21", desktop: 82, mobile: 140 },
{ date: "2024-05-22", desktop: 81, mobile: 120 },
{ date: "2024-05-23", desktop: 252, mobile: 290 },
{ date: "2024-05-24", desktop: 294, mobile: 220 },
{ date: "2024-05-25", desktop: 201, mobile: 250 },
{ date: "2024-05-26", desktop: 213, mobile: 170 },
{ date: "2024-05-27", desktop: 420, mobile: 460 },
{ date: "2024-05-28", desktop: 233, mobile: 190 },
{ date: "2024-05-29", desktop: 78, mobile: 130 },
{ date: "2024-05-30", desktop: 340, mobile: 280 },
{ date: "2024-05-31", desktop: 178, mobile: 230 },
{ date: "2024-06-01", desktop: 178, mobile: 200 },
{ date: "2024-06-02", desktop: 470, mobile: 410 },
{ date: "2024-06-03", desktop: 103, mobile: 160 },
{ date: "2024-06-04", desktop: 439, mobile: 380 },
{ date: "2024-06-05", desktop: 88, mobile: 140 },
{ date: "2024-06-06", desktop: 294, mobile: 250 },
{ date: "2024-06-07", desktop: 323, mobile: 370 },
{ date: "2024-06-08", desktop: 385, mobile: 320 },
{ date: "2024-06-09", desktop: 438, mobile: 480 },
{ date: "2024-06-10", desktop: 155, mobile: 200 },
{ date: "2024-06-11", desktop: 92, mobile: 150 },
{ date: "2024-06-12", desktop: 492, mobile: 420 },
{ date: "2024-06-13", desktop: 81, mobile: 130 },
{ date: "2024-06-14", desktop: 426, mobile: 380 },
{ date: "2024-06-15", desktop: 307, mobile: 350 },
{ date: "2024-06-16", desktop: 371, mobile: 310 },
{ date: "2024-06-17", desktop: 475, mobile: 520 },
{ date: "2024-06-18", desktop: 107, mobile: 170 },
{ date: "2024-06-19", desktop: 341, mobile: 290 },
{ date: "2024-06-20", desktop: 408, mobile: 450 },
{ date: "2024-06-21", desktop: 169, mobile: 210 },
{ date: "2024-06-22", desktop: 317, mobile: 270 },
{ date: "2024-06-23", desktop: 480, mobile: 530 },
{ date: "2024-06-24", desktop: 132, mobile: 180 },
{ date: "2024-06-25", desktop: 141, mobile: 190 },
{ date: "2024-06-26", desktop: 434, mobile: 380 },
{ date: "2024-06-27", desktop: 448, mobile: 490 },
{ date: "2024-06-28", desktop: 149, mobile: 200 },
{ date: "2024-06-29", desktop: 103, mobile: 160 },
{ date: "2024-06-30", desktop: 446, mobile: 400 },
]
const chartConfig = {
visitors: {
label: "Visitors",
},
desktop: {
label: "Desktop",
color: "var(--primary)",
},
mobile: {
label: "Mobile",
color: "var(--primary)",
},
} satisfies ChartConfig
export function ChartAreaInteractive() {
const isMobile = useIsMobile()
const [timeRange, setTimeRange] = React.useState("90d")
React.useEffect(() => {
if (isMobile) {
setTimeRange("7d")
}
}, [isMobile])
const filteredData = chartData.filter((item) => {
const date = new Date(item.date)
const referenceDate = new Date("2024-06-30")
let daysToSubtract = 90
if (timeRange === "30d") {
daysToSubtract = 30
} else if (timeRange === "7d") {
daysToSubtract = 7
}
const startDate = new Date(referenceDate)
startDate.setDate(startDate.getDate() - daysToSubtract)
return date >= startDate
})
return (
<Card className="@container/card">
<CardHeader>
<CardTitle>Total Visitors</CardTitle>
<CardDescription>
<span className="hidden @[540px]/card:block">
Total for the last 3 months
</span>
<span className="@[540px]/card:hidden">Last 3 months</span>
</CardDescription>
<CardAction>
<ToggleGroup
selectionMode="single"
selectedKeys={timeRange ? [timeRange] : []}
onSelectionChange={(value) => {
setTimeRange((Array.from(value)[0] as string) ?? "90d")
}}
variant="outline"
className="hidden *:data-[slot=toggle-group-item]:px-4! @[767px]/card:flex"
>
<ToggleGroupItem id="90d">Last 3 months</ToggleGroupItem>
<ToggleGroupItem id="30d">Last 30 days</ToggleGroupItem>
<ToggleGroupItem id="7d">Last 7 days</ToggleGroupItem>
</ToggleGroup>
<Select
aria-label="Select a value"
value={timeRange}
onChange={(value) => {
if (value !== null) {
setTimeRange(String(value))
}
}}
>
<SelectTrigger
className="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
size="sm"
>
<SelectValue />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem id="90d" className="rounded-lg">
Last 3 months
</SelectItem>
<SelectItem id="30d" className="rounded-lg">
Last 30 days
</SelectItem>
<SelectItem id="7d" className="rounded-lg">
Last 7 days
</SelectItem>
</SelectContent>
</Select>
</CardAction>
</CardHeader>
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
<ChartContainer
config={chartConfig}
className="aspect-auto h-[250px] w-full"
>
<AreaChart data={filteredData}>
<defs>
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="var(--color-desktop)"
stopOpacity={1.0}
/>
<stop
offset="95%"
stopColor="var(--color-desktop)"
stopOpacity={0.1}
/>
</linearGradient>
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="var(--color-mobile)"
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor="var(--color-mobile)"
stopOpacity={0.1}
/>
</linearGradient>
</defs>
<CartesianGrid vertical={false} />
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={32}
tickFormatter={(value) => {
const date = new Date(value)
return date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
}}
/>
<ChartTooltip
cursor={false}
content={
<ChartTooltipContent
labelFormatter={(value) => {
return new Date(value).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
}}
indicator="dot"
/>
}
/>
<Area
dataKey="mobile"
type="natural"
fill="url(#fillMobile)"
stroke="var(--color-mobile)"
stackId="a"
/>
<Area
dataKey="desktop"
type="natural"
fill="url(#fillDesktop)"
stroke="var(--color-desktop)"
stackId="a"
/>
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,892 @@
"use client"
import * as React from "react"
import {
columnFilteringFeature,
columnVisibilityFeature,
createColumnHelper,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
tableFeatures,
useTable,
type ColumnFiltersState,
type ColumnVisibilityState,
type SortingState,
} from "@tanstack/react-table"
import {
DropIndicator,
useDragAndDrop,
useListData,
} from "react-aria-components"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import { toast } from "sonner"
import { z } from "zod"
import { useIsMobile } from "@/registry/bases/aria/hooks/use-mobile"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/bases/aria/ui/drawer"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import { Input } from "@/registry/bases/aria/ui/input"
import { Label } from "@/registry/bases/aria/ui/label"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Separator } from "@/registry/bases/aria/ui/separator"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/bases/aria/ui/table"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/bases/aria/ui/tabs"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
// New in v9: declare the features this table uses — anything you don't
// register is tree-shaken out of the bundle.
const features = tableFeatures({
columnFilteringFeature,
columnVisibilityFeature,
rowPaginationFeature,
rowSelectionFeature,
rowSortingFeature,
filteredRowModel: createFilteredRowModel(),
paginatedRowModel: createPaginatedRowModel(),
sortedRowModel: createSortedRowModel(),
})
const columnHelper = createColumnHelper<
typeof features,
z.infer<typeof schema>
>()
export const schema = z.object({
id: z.number(),
header: z.string(),
type: z.string(),
status: z.string(),
target: z.string(),
limit: z.string(),
reviewer: z.string(),
})
// Create a separate component for the drag handle
function DragHandle() {
return (
<Button
slot="drag"
variant="ghost"
size="icon"
className="size-7 text-muted-foreground hover:bg-transparent"
>
<IconPlaceholder
lucide="GripVerticalIcon"
tabler="IconGripVertical"
hugeicons="DragDropVerticalIcon"
phosphor="DotsSixVerticalIcon"
remixicon="RiDraggable"
className="size-3 text-muted-foreground"
/>
</Button>
)
}
const columns = columnHelper.columns([
columnHelper.display({
id: "drag",
header: () => null,
cell: ({ row }) => <DragHandle />,
}),
columnHelper.display({
id: "select",
header: () => (
<div className="flex items-center justify-center">
<Checkbox slot="selection" />
</div>
),
cell: () => (
<div className="flex items-center justify-center">
<Checkbox slot="selection" />
</div>
),
enableSorting: false,
enableHiding: false,
}),
columnHelper.accessor("header", {
header: "Header",
cell: ({ row }) => {
return <TableCellViewer item={row.original} />
},
enableHiding: false,
}),
columnHelper.accessor("type", {
header: "Section Type",
cell: ({ row }) => (
<div className="w-32">
<Badge variant="outline" className="px-1.5 text-muted-foreground">
{row.original.type}
</Badge>
</div>
),
}),
columnHelper.accessor("status", {
header: "Status",
cell: ({ row }) => (
<Badge variant="outline" className="px-1.5 text-muted-foreground">
{row.original.status === "Done" ? (
<IconPlaceholder
lucide="CircleCheckIcon"
tabler="IconCircleCheckFilled"
hugeicons="CheckmarkCircle01Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleFill"
className="fill-green-500 dark:fill-green-400"
/>
) : (
<IconPlaceholder
lucide="LoaderIcon"
tabler="IconLoader"
hugeicons="Loading03Icon"
phosphor="SpinnerIcon"
remixicon="RiLoader4Line"
/>
)}
{row.original.status}
</Badge>
),
}),
columnHelper.accessor("target", {
header: () => <div className="w-full text-right">Target</div>,
cell: ({ row }) => (
<form
onSubmit={(e) => {
e.preventDefault()
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
loading: `Saving ${row.original.header}`,
success: "Done",
error: "Error",
})
}}
>
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
Target
</Label>
<Input
className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
defaultValue={row.original.target}
id={`${row.original.id}-target`}
/>
</form>
),
}),
columnHelper.accessor("limit", {
header: () => <div className="w-full text-right">Limit</div>,
cell: ({ row }) => (
<form
onSubmit={(e) => {
e.preventDefault()
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
loading: `Saving ${row.original.header}`,
success: "Done",
error: "Error",
})
}}
>
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
Limit
</Label>
<Input
className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
defaultValue={row.original.limit}
id={`${row.original.id}-limit`}
/>
</form>
),
}),
columnHelper.accessor("reviewer", {
header: "Reviewer",
cell: ({ row }) => {
const isAssigned = row.original.reviewer !== "Assign reviewer"
if (isAssigned) {
return row.original.reviewer
}
return (
<>
<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
Reviewer
</Label>
<Select placeholder="Assign reviewer">
<SelectTrigger
className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
size="sm"
id={`${row.original.id}-reviewer`}
>
<SelectValue />
</SelectTrigger>
<SelectContent placement="bottom end">
<SelectGroup>
<SelectItem id="Eddie Lake">Eddie Lake</SelectItem>
<SelectItem id="Jamik Tashpulatov">
Jamik Tashpulatov
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</>
)
},
}),
columnHelper.display({
id: "actions",
cell: () => (
<DropdownMenuTrigger>
<Button
variant="ghost"
className="flex size-8 text-muted-foreground aria-expanded:bg-muted"
size="icon"
>
<IconPlaceholder
lucide="EllipsisVerticalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeVerticalIcon"
remixicon="RiMore2Line"
/>
<span className="sr-only">Open menu</span>
</Button>
<DropdownMenu placement="bottom end" className="w-32">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Make a copy</DropdownMenuItem>
<DropdownMenuItem>Favorite</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
),
}),
])
export function DataTable({
data: initialData,
}: {
data: z.infer<typeof schema>[]
}) {
const list = useListData({
initialItems: initialData,
getKey: (item) => String(item.id),
})
const data = list.items
const [rowSelection, setRowSelection] = React.useState({})
const [columnVisibility, setColumnVisibility] =
React.useState<ColumnVisibilityState>({})
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [sorting, setSorting] = React.useState<SortingState>([])
const [pagination, setPagination] = React.useState({
pageIndex: 0,
pageSize: 10,
})
const table = useTable({
features,
data,
columns,
state: {
sorting,
columnVisibility,
rowSelection,
columnFilters,
pagination,
},
getRowId: (row) => row.id.toString(),
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onPaginationChange: setPagination,
})
const { dragAndDropHooks } = useDragAndDrop({
getItems: (keys, items: z.infer<typeof schema>[]) =>
items.map((item) => ({
"text/plain": item.header,
})),
onReorder(e) {
if (e.target.dropPosition === "before") {
list.moveBefore(e.target.key, e.keys)
} else if (e.target.dropPosition === "after") {
list.moveAfter(e.target.key, e.keys)
}
},
renderDropIndicator(target) {
return (
<DropIndicator
target={target}
className="outline-blue-400 data-drop-target:outline-1"
/>
)
},
})
return (
<Tabs
defaultSelectedKey="outline"
className="w-full flex-col justify-start gap-6"
>
<div className="flex items-center justify-between px-4 lg:px-6">
<Label htmlFor="view-selector" className="sr-only">
View
</Label>
{/* <Select placeholder="Select a view" defaultValue="outline">
<SelectTrigger
className="flex w-fit @4xl/main:hidden"
size="sm"
id="view-selector"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="outline">Outline</SelectItem>
<SelectItem id="past-performance">Past Performance</SelectItem>
<SelectItem id="key-personnel">Key Personnel</SelectItem>
<SelectItem id="focus-documents">Focus Documents</SelectItem>
</SelectGroup>
</SelectContent>
</Select> */}
<TabsList className="hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:bg-muted-foreground/30 **:data-[slot=badge]:px-1 @4xl/main:flex">
<TabsTrigger id="outline">Outline</TabsTrigger>
<TabsTrigger id="past-performance">
Past Performance <Badge variant="secondary">3</Badge>
</TabsTrigger>
<TabsTrigger id="key-personnel">
Key Personnel <Badge variant="secondary">2</Badge>
</TabsTrigger>
<TabsTrigger id="focus-documents">Focus Documents</TabsTrigger>
</TabsList>
<div className="flex items-center gap-2">
{/* <DropdownMenuTrigger>
<Button variant="outline" size="sm">
<IconPlaceholder
lucide="Columns3Icon"
tabler="IconLayoutColumns"
hugeicons="LeftToRightListBulletIcon"
phosphor="ColumnsIcon"
remixicon="RiLayoutColumnLine"
data-icon="inline-start"
/>
Columns
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
data-icon="inline-end"
/>
</Button>
<DropdownMenu
align="end"
className="w-32"
selectionMode="multiple"
selectedKeys={
table
.getVisibleFlatColumns()
.filter((column) => column.getCanHide())
.map(column => column.id)
}
onSelectionChange={(keys) => {
table.setColumnVisibility(
Object.fromEntries(
table
.getAllFlatColumns()
.map((c) => [
c.id,
!c.getCanHide() || keys === "all" || keys.has(c.id),
])
)
)
}}
>
{table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" &&
column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuItem
key={column.id}
id={column.id}
className="capitalize"
>
{column.id}
</DropdownMenuItem>
)
})}
</DropdownMenu>
</DropdownMenuTrigger> */}
<Button variant="outline" size="sm">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="Add01Icon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
<span className="hidden lg:inline">Add Section</span>
</Button>
</div>
</div>
<TabsContent
id="outline"
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
>
<div className="overflow-hidden rounded-lg border">
<Table
aria-label="Tasks"
dragAndDropHooks={dragAndDropHooks}
selectionMode="multiple"
onSelectionChange={(selection) => {
if (selection === "all") {
table.toggleAllRowsSelected()
} else {
table.setRowSelection(
Object.fromEntries([...selection].map((key) => [key, true]))
)
}
}}
sortDescriptor={
sorting.length
? {
column: sorting[0].id,
direction: sorting[0].desc ? "descending" : "ascending",
}
: undefined
}
onSortChange={(sortDescriptor) => {
table.setSorting([
{
id: "" + sortDescriptor.column,
desc: sortDescriptor.direction === "descending",
},
])
}}
>
<TableHeader className="sticky top-0 z-10 bg-muted">
{table.getFlatHeaders().map((header) => (
<TableHead
key={header.id}
id={header.id}
isRowHeader={header.index === 1}
allowsSorting={header.column.getCanSort()}
>
{header.isPlaceholder ? null : (
<table.FlexRender header={header} />
)}
</TableHead>
))}
</TableHeader>
<TableBody
className="data-empty:h-24 data-empty:text-center **:data-[slot=table-cell]:first:w-8"
renderEmptyState={() => "No results."}
>
{table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
id={row.id}
value={row.original}
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
<table.FlexRender cell={cell} />
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-between px-4">
<div className="hidden flex-1 text-sm text-muted-foreground lg:flex">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex w-full items-center gap-8 lg:w-fit">
<div className="hidden items-center gap-2 lg:flex">
<Label htmlFor="rows-per-page" className="text-sm font-medium">
Rows per page
</Label>
<Select
aria-label="Rows per page"
placeholder={`${table.state.pagination.pageSize}`}
value={`${table.state.pagination.pageSize}`}
onChange={(value) => {
table.setPageSize(Number(value))
}}
>
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
<SelectValue />
</SelectTrigger>
<SelectContent placement="top">
<SelectGroup>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} id={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="flex w-fit items-center justify-center text-sm font-medium">
Page {table.state.pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
</div>
<div className="ml-auto flex items-center gap-2 lg:ml-0">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onPress={() => table.setPageIndex(0)}
isDisabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<IconPlaceholder
lucide="ChevronsLeftIcon"
tabler="IconChevronsLeft"
hugeicons="ArrowLeftDoubleIcon"
phosphor="CaretDoubleLeftIcon"
remixicon="RiSkipLeftLine"
/>
</Button>
<Button
variant="outline"
className="size-8"
size="icon"
onPress={() => table.previousPage()}
isDisabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<IconPlaceholder
lucide="ChevronLeftIcon"
tabler="IconChevronLeft"
hugeicons="ArrowLeft01Icon"
phosphor="CaretLeftIcon"
remixicon="RiArrowLeftSLine"
/>
</Button>
<Button
variant="outline"
className="size-8"
size="icon"
onPress={() => table.nextPage()}
isDisabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
/>
</Button>
<Button
variant="outline"
className="hidden size-8 lg:flex"
size="icon"
onPress={() => table.setPageIndex(table.getPageCount() - 1)}
isDisabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<IconPlaceholder
lucide="ChevronsRightIcon"
tabler="IconChevronsRight"
hugeicons="ArrowRightDoubleIcon"
phosphor="CaretDoubleRightIcon"
remixicon="RiSkipRightLine"
/>
</Button>
</div>
</div>
</div>
</TabsContent>
<TabsContent id="past-performance" className="flex flex-col px-4 lg:px-6">
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
</TabsContent>
<TabsContent id="key-personnel" className="flex flex-col px-4 lg:px-6">
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
</TabsContent>
<TabsContent id="focus-documents" className="flex flex-col px-4 lg:px-6">
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
</TabsContent>
</Tabs>
)
}
const chartData = [
{
month: "January",
desktop: 186,
mobile: 80,
},
{
month: "February",
desktop: 305,
mobile: 200,
},
{
month: "March",
desktop: 237,
mobile: 120,
},
{
month: "April",
desktop: 73,
mobile: 190,
},
{
month: "May",
desktop: 209,
mobile: 130,
},
{
month: "June",
desktop: 214,
mobile: 140,
},
]
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--primary)",
},
mobile: {
label: "Mobile",
color: "var(--primary)",
},
} satisfies ChartConfig
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
const isMobile = useIsMobile()
return (
<Drawer swipeDirection={isMobile ? "down" : "right"}>
<DrawerTrigger
render={
<Button
variant="link"
className="w-fit px-0 text-left text-foreground"
/>
}
>
{item.header}
</DrawerTrigger>
<DrawerContent>
<DrawerHeader className="gap-1">
<DrawerTitle>{item.header}</DrawerTitle>
<DrawerDescription>
Showing total visitors for the last 6 months
</DrawerDescription>
</DrawerHeader>
<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
{!isMobile && (
<>
<ChartContainer config={chartConfig}>
<AreaChart
accessibilityLayer
data={chartData}
margin={{
left: 0,
right: 10,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
hide
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="dot" />}
/>
<Area
dataKey="mobile"
type="natural"
fill="var(--color-mobile)"
fillOpacity={0.6}
stroke="var(--color-mobile)"
stackId="a"
/>
<Area
dataKey="desktop"
type="natural"
fill="var(--color-desktop)"
fillOpacity={0.4}
stroke="var(--color-desktop)"
stackId="a"
/>
</AreaChart>
</ChartContainer>
<Separator />
<div className="grid gap-2">
<div className="flex gap-2 leading-none font-medium">
Trending up by 5.2% this month{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiArrowUpLine"
className="size-4"
/>
</div>
<div className="text-muted-foreground">
Showing total visitors for the last 6 months. This is just
some random text to test the layout. It spans multiple lines
and should wrap around.
</div>
</div>
<Separator />
</>
)}
<form className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="header">Header</Label>
<Input id="header" defaultValue={item.header} />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="type">Type</Label>
<Select
placeholder="Select a type"
aria-label="Type"
defaultValue={item.type}
>
<SelectTrigger id="type" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="Table of Contents">
Table of Contents
</SelectItem>
<SelectItem id="Executive Summary">
Executive Summary
</SelectItem>
<SelectItem id="Technical Approach">
Technical Approach
</SelectItem>
<SelectItem id="Design">Design</SelectItem>
<SelectItem id="Capabilities">Capabilities</SelectItem>
<SelectItem id="Focus Documents">
Focus Documents
</SelectItem>
<SelectItem id="Narrative">Narrative</SelectItem>
<SelectItem id="Cover Page">Cover Page</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="status">Status</Label>
<Select
aria-label="Status"
placeholder="Select a status"
defaultValue={item.status}
>
<SelectTrigger id="status" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="Done">Done</SelectItem>
<SelectItem id="In Progress">In Progress</SelectItem>
<SelectItem id="Not Started">Not Started</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="target">Target</Label>
<Input id="target" defaultValue={item.target} />
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="limit">Limit</Label>
<Input id="limit" defaultValue={item.limit} />
</div>
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="reviewer">Reviewer</Label>
<Select
aria-label="Reviewer"
placeholder="Select a reviewer"
defaultValue={item.reviewer}
>
<SelectTrigger id="reviewer" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="Eddie Lake">Eddie Lake</SelectItem>
<SelectItem id="Jamik Tashpulatov">
Jamik Tashpulatov
</SelectItem>
<SelectItem id="Emily Whalen">Emily Whalen</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</form>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose render={<Button variant="outline" />}>Done</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
)
}

View File

@@ -0,0 +1,107 @@
"use client"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function NavDocuments({
items,
}: {
items: {
name: string
url: string
icon: React.ReactNode
}[]
}) {
const { isMobile } = useSidebar()
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Documents</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton href={item.url}>
{item.icon}
<span>{item.name}</span>
</SidebarMenuButton>
<DropdownMenuTrigger>
<SidebarMenuAction showOnHover className="aria-expanded:bg-muted">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">More</span>
</SidebarMenuAction>
<DropdownMenu
className="w-24"
placement={isMobile ? "bottom end" : "right top"}
>
<DropdownMenuItem>
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="Folder01Icon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
<span>Open</span>
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare3"
hugeicons="Share01Icon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
<span>Share</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="Trash2Icon"
tabler="IconTrash"
hugeicons="Delete02Icon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
<span>Delete</span>
</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
className="text-sidebar-foreground/70"
/>
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
)
}

View File

@@ -0,0 +1,69 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function NavMain({
items,
}: {
items: {
title: string
url: string
icon?: React.ReactNode
}[]
}) {
return (
<SidebarGroup>
<SidebarGroupContent className="flex flex-col gap-2">
<SidebarMenu>
<SidebarMenuItem className="flex items-center gap-2">
<SidebarMenuButton
tooltip="Quick Create"
className="min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground"
>
<IconPlaceholder
lucide="CirclePlusIcon"
tabler="IconCirclePlusFilled"
hugeicons="PlusSignCircleIcon"
phosphor="PlusCircleIcon"
remixicon="RiAddCircleFill"
/>
<span>Quick Create</span>
</SidebarMenuButton>
<Button
size="icon"
className="size-8 group-data-[collapsible=icon]:opacity-0"
variant="outline"
>
<IconPlaceholder
lucide="MailIcon"
tabler="IconMail"
hugeicons="Mail01Icon"
phosphor="EnvelopeIcon"
remixicon="RiMailLine"
/>
<span className="sr-only">Inbox</span>
</Button>
</SidebarMenuItem>
</SidebarMenu>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton tooltip={item.title}>
{item.icon}
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}

View File

@@ -0,0 +1,39 @@
"use client"
import * as React from "react"
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/bases/aria/ui/sidebar"
export function NavSecondary({
items,
...props
}: {
items: {
title: string
url: string
icon: React.ReactNode
}[]
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
return (
<SidebarGroup {...props}>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton href={item.url}>
{item.icon}
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}

View File

@@ -0,0 +1,128 @@
"use client"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function NavUser({
user,
}: {
user: {
name: string
email: string
avatar: string
}
}) {
const { isMobile } = useSidebar()
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenuTrigger>
<SidebarMenuButton size="lg" className="aria-expanded:bg-muted">
<Avatar className="size-8 rounded-lg grayscale">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs text-foreground/70">
{user.email}
</span>
</div>
<IconPlaceholder
lucide="EllipsisVerticalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeVerticalIcon"
remixicon="RiMore2Line"
className="ml-auto size-4"
/>
</SidebarMenuButton>
<DropdownMenu
className="min-w-56"
placement={isMobile ? "bottom end" : "right bottom"}
offset={4}
>
<DropdownMenuGroup>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="size-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs text-muted-foreground">
{user.email}
</span>
</div>
</div>
</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="CircleUserRoundIcon"
tabler="IconUserCircle"
hugeicons="UserCircle02Icon"
phosphor="UserCircleIcon"
remixicon="RiUserLine"
/>
Account
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="BellIcon"
tabler="IconNotification"
hugeicons="Notification03Icon"
phosphor="BellIcon"
remixicon="RiNotification3Line"
/>
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="Logout01Icon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Log out
</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</SidebarMenuItem>
</SidebarMenu>
)
}

View File

@@ -0,0 +1,159 @@
"use client"
import { Badge } from "@/registry/bases/aria/ui/badge"
import {
Card,
CardAction,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function SectionCards() {
return (
<div className="grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-linear-to-t *:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4 dark:*:data-[slot=card]:bg-card">
<Card className="@container/card">
<CardHeader>
<CardDescription>Total Revenue</CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
$1,250.00
</CardTitle>
<CardAction>
<Badge variant="outline">
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiArrowUpLine"
/>
+12.5%
</Badge>
</CardAction>
</CardHeader>
<CardFooter className="flex-col items-start gap-1.5 text-sm">
<div className="line-clamp-1 flex gap-2 font-medium">
Trending up this month{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiArrowUpLine"
className="size-4"
/>
</div>
<div className="text-muted-foreground">
Visitors for the last 6 months
</div>
</CardFooter>
</Card>
<Card className="@container/card">
<CardHeader>
<CardDescription>New Customers</CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
1,234
</CardTitle>
<CardAction>
<Badge variant="outline">
<IconPlaceholder
lucide="TrendingDownIcon"
tabler="IconTrendingDown"
hugeicons="ChartDownIcon"
phosphor="TrendDownIcon"
remixicon="RiArrowDownLine"
/>
-20%
</Badge>
</CardAction>
</CardHeader>
<CardFooter className="flex-col items-start gap-1.5 text-sm">
<div className="line-clamp-1 flex gap-2 font-medium">
Down 20% this period{" "}
<IconPlaceholder
lucide="TrendingDownIcon"
tabler="IconTrendingDown"
hugeicons="ChartDownIcon"
phosphor="TrendDownIcon"
remixicon="RiArrowDownLine"
className="size-4"
/>
</div>
<div className="text-muted-foreground">
Acquisition needs attention
</div>
</CardFooter>
</Card>
<Card className="@container/card">
<CardHeader>
<CardDescription>Active Accounts</CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
45,678
</CardTitle>
<CardAction>
<Badge variant="outline">
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiArrowUpLine"
/>
+12.5%
</Badge>
</CardAction>
</CardHeader>
<CardFooter className="flex-col items-start gap-1.5 text-sm">
<div className="line-clamp-1 flex gap-2 font-medium">
Strong user retention{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiArrowUpLine"
className="size-4"
/>
</div>
<div className="text-muted-foreground">Engagement exceed targets</div>
</CardFooter>
</Card>
<Card className="@container/card">
<CardHeader>
<CardDescription>Growth Rate</CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
4.5%
</CardTitle>
<CardAction>
<Badge variant="outline">
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiArrowUpLine"
/>
+4.5%
</Badge>
</CardAction>
</CardHeader>
<CardFooter className="flex-col items-start gap-1.5 text-sm">
<div className="line-clamp-1 flex gap-2 font-medium">
Steady performance increase{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiArrowUpLine"
className="size-4"
/>
</div>
<div className="text-muted-foreground">Meets growth projections</div>
</CardFooter>
</Card>
</div>
)
}

View File

@@ -0,0 +1,17 @@
import { Separator } from "@/registry/bases/aria/ui/separator"
import { SidebarTrigger } from "@/registry/bases/aria/ui/sidebar"
export function SiteHeader() {
return (
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
<SidebarTrigger className="-ml-1" />
<Separator
orientation="vertical"
className="mx-2 h-4 data-vertical:self-auto"
/>
<h1 className="text-base font-medium">Documents</h1>
</div>
</header>
)
}

View File

@@ -0,0 +1,614 @@
[
{
"id": 1,
"header": "Cover page",
"type": "Cover page",
"status": "In Process",
"target": "18",
"limit": "5",
"reviewer": "Eddie Lake"
},
{
"id": 2,
"header": "Table of contents",
"type": "Table of contents",
"status": "Done",
"target": "29",
"limit": "24",
"reviewer": "Eddie Lake"
},
{
"id": 3,
"header": "Executive summary",
"type": "Narrative",
"status": "Done",
"target": "10",
"limit": "13",
"reviewer": "Eddie Lake"
},
{
"id": 4,
"header": "Technical approach",
"type": "Narrative",
"status": "Done",
"target": "27",
"limit": "23",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 5,
"header": "Design",
"type": "Narrative",
"status": "In Process",
"target": "2",
"limit": "16",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 6,
"header": "Capabilities",
"type": "Narrative",
"status": "In Process",
"target": "20",
"limit": "8",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 7,
"header": "Integration with existing systems",
"type": "Narrative",
"status": "In Process",
"target": "19",
"limit": "21",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 8,
"header": "Innovation and Advantages",
"type": "Narrative",
"status": "Done",
"target": "25",
"limit": "26",
"reviewer": "Assign reviewer"
},
{
"id": 9,
"header": "Overview of EMR's Innovative Solutions",
"type": "Technical content",
"status": "Done",
"target": "7",
"limit": "23",
"reviewer": "Assign reviewer"
},
{
"id": 10,
"header": "Advanced Algorithms and Machine Learning",
"type": "Narrative",
"status": "Done",
"target": "30",
"limit": "28",
"reviewer": "Assign reviewer"
},
{
"id": 11,
"header": "Adaptive Communication Protocols",
"type": "Narrative",
"status": "Done",
"target": "9",
"limit": "31",
"reviewer": "Assign reviewer"
},
{
"id": 12,
"header": "Advantages Over Current Technologies",
"type": "Narrative",
"status": "Done",
"target": "12",
"limit": "0",
"reviewer": "Assign reviewer"
},
{
"id": 13,
"header": "Past Performance",
"type": "Narrative",
"status": "Done",
"target": "22",
"limit": "33",
"reviewer": "Assign reviewer"
},
{
"id": 14,
"header": "Customer Feedback and Satisfaction Levels",
"type": "Narrative",
"status": "Done",
"target": "15",
"limit": "34",
"reviewer": "Assign reviewer"
},
{
"id": 15,
"header": "Implementation Challenges and Solutions",
"type": "Narrative",
"status": "Done",
"target": "3",
"limit": "35",
"reviewer": "Assign reviewer"
},
{
"id": 16,
"header": "Security Measures and Data Protection Policies",
"type": "Narrative",
"status": "In Process",
"target": "6",
"limit": "36",
"reviewer": "Assign reviewer"
},
{
"id": 17,
"header": "Scalability and Future Proofing",
"type": "Narrative",
"status": "Done",
"target": "4",
"limit": "37",
"reviewer": "Assign reviewer"
},
{
"id": 18,
"header": "Cost-Benefit Analysis",
"type": "Plain language",
"status": "Done",
"target": "14",
"limit": "38",
"reviewer": "Assign reviewer"
},
{
"id": 19,
"header": "User Training and Onboarding Experience",
"type": "Narrative",
"status": "Done",
"target": "17",
"limit": "39",
"reviewer": "Assign reviewer"
},
{
"id": 20,
"header": "Future Development Roadmap",
"type": "Narrative",
"status": "Done",
"target": "11",
"limit": "40",
"reviewer": "Assign reviewer"
},
{
"id": 21,
"header": "System Architecture Overview",
"type": "Technical content",
"status": "In Process",
"target": "24",
"limit": "18",
"reviewer": "Maya Johnson"
},
{
"id": 22,
"header": "Risk Management Plan",
"type": "Narrative",
"status": "Done",
"target": "15",
"limit": "22",
"reviewer": "Carlos Rodriguez"
},
{
"id": 23,
"header": "Compliance Documentation",
"type": "Legal",
"status": "In Process",
"target": "31",
"limit": "27",
"reviewer": "Sarah Chen"
},
{
"id": 24,
"header": "API Documentation",
"type": "Technical content",
"status": "Done",
"target": "8",
"limit": "12",
"reviewer": "Raj Patel"
},
{
"id": 25,
"header": "User Interface Mockups",
"type": "Visual",
"status": "In Process",
"target": "19",
"limit": "25",
"reviewer": "Leila Ahmadi"
},
{
"id": 26,
"header": "Database Schema",
"type": "Technical content",
"status": "Done",
"target": "22",
"limit": "20",
"reviewer": "Thomas Wilson"
},
{
"id": 27,
"header": "Testing Methodology",
"type": "Technical content",
"status": "In Process",
"target": "17",
"limit": "14",
"reviewer": "Assign reviewer"
},
{
"id": 28,
"header": "Deployment Strategy",
"type": "Narrative",
"status": "Done",
"target": "26",
"limit": "30",
"reviewer": "Eddie Lake"
},
{
"id": 29,
"header": "Budget Breakdown",
"type": "Financial",
"status": "In Process",
"target": "13",
"limit": "16",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 30,
"header": "Market Analysis",
"type": "Research",
"status": "Done",
"target": "29",
"limit": "32",
"reviewer": "Sophia Martinez"
},
{
"id": 31,
"header": "Competitor Comparison",
"type": "Research",
"status": "In Process",
"target": "21",
"limit": "19",
"reviewer": "Assign reviewer"
},
{
"id": 32,
"header": "Maintenance Plan",
"type": "Technical content",
"status": "Done",
"target": "16",
"limit": "23",
"reviewer": "Alex Thompson"
},
{
"id": 33,
"header": "User Personas",
"type": "Research",
"status": "In Process",
"target": "27",
"limit": "24",
"reviewer": "Nina Patel"
},
{
"id": 34,
"header": "Accessibility Compliance",
"type": "Legal",
"status": "Done",
"target": "18",
"limit": "21",
"reviewer": "Assign reviewer"
},
{
"id": 35,
"header": "Performance Metrics",
"type": "Technical content",
"status": "In Process",
"target": "23",
"limit": "26",
"reviewer": "David Kim"
},
{
"id": 36,
"header": "Disaster Recovery Plan",
"type": "Technical content",
"status": "Done",
"target": "14",
"limit": "17",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 37,
"header": "Third-party Integrations",
"type": "Technical content",
"status": "In Process",
"target": "25",
"limit": "28",
"reviewer": "Eddie Lake"
},
{
"id": 38,
"header": "User Feedback Summary",
"type": "Research",
"status": "Done",
"target": "20",
"limit": "15",
"reviewer": "Assign reviewer"
},
{
"id": 39,
"header": "Localization Strategy",
"type": "Narrative",
"status": "In Process",
"target": "12",
"limit": "19",
"reviewer": "Maria Garcia"
},
{
"id": 40,
"header": "Mobile Compatibility",
"type": "Technical content",
"status": "Done",
"target": "28",
"limit": "31",
"reviewer": "James Wilson"
},
{
"id": 41,
"header": "Data Migration Plan",
"type": "Technical content",
"status": "In Process",
"target": "19",
"limit": "22",
"reviewer": "Assign reviewer"
},
{
"id": 42,
"header": "Quality Assurance Protocols",
"type": "Technical content",
"status": "Done",
"target": "30",
"limit": "33",
"reviewer": "Priya Singh"
},
{
"id": 43,
"header": "Stakeholder Analysis",
"type": "Research",
"status": "In Process",
"target": "11",
"limit": "14",
"reviewer": "Eddie Lake"
},
{
"id": 44,
"header": "Environmental Impact Assessment",
"type": "Research",
"status": "Done",
"target": "24",
"limit": "27",
"reviewer": "Assign reviewer"
},
{
"id": 45,
"header": "Intellectual Property Rights",
"type": "Legal",
"status": "In Process",
"target": "17",
"limit": "20",
"reviewer": "Sarah Johnson"
},
{
"id": 46,
"header": "Customer Support Framework",
"type": "Narrative",
"status": "Done",
"target": "22",
"limit": "25",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 47,
"header": "Version Control Strategy",
"type": "Technical content",
"status": "In Process",
"target": "15",
"limit": "18",
"reviewer": "Assign reviewer"
},
{
"id": 48,
"header": "Continuous Integration Pipeline",
"type": "Technical content",
"status": "Done",
"target": "26",
"limit": "29",
"reviewer": "Michael Chen"
},
{
"id": 49,
"header": "Regulatory Compliance",
"type": "Legal",
"status": "In Process",
"target": "13",
"limit": "16",
"reviewer": "Assign reviewer"
},
{
"id": 50,
"header": "User Authentication System",
"type": "Technical content",
"status": "Done",
"target": "28",
"limit": "31",
"reviewer": "Eddie Lake"
},
{
"id": 51,
"header": "Data Analytics Framework",
"type": "Technical content",
"status": "In Process",
"target": "21",
"limit": "24",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 52,
"header": "Cloud Infrastructure",
"type": "Technical content",
"status": "Done",
"target": "16",
"limit": "19",
"reviewer": "Assign reviewer"
},
{
"id": 53,
"header": "Network Security Measures",
"type": "Technical content",
"status": "In Process",
"target": "29",
"limit": "32",
"reviewer": "Lisa Wong"
},
{
"id": 54,
"header": "Project Timeline",
"type": "Planning",
"status": "Done",
"target": "14",
"limit": "17",
"reviewer": "Eddie Lake"
},
{
"id": 55,
"header": "Resource Allocation",
"type": "Planning",
"status": "In Process",
"target": "27",
"limit": "30",
"reviewer": "Assign reviewer"
},
{
"id": 56,
"header": "Team Structure and Roles",
"type": "Planning",
"status": "Done",
"target": "20",
"limit": "23",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 57,
"header": "Communication Protocols",
"type": "Planning",
"status": "In Process",
"target": "15",
"limit": "18",
"reviewer": "Assign reviewer"
},
{
"id": 58,
"header": "Success Metrics",
"type": "Planning",
"status": "Done",
"target": "30",
"limit": "33",
"reviewer": "Eddie Lake"
},
{
"id": 59,
"header": "Internationalization Support",
"type": "Technical content",
"status": "In Process",
"target": "23",
"limit": "26",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 60,
"header": "Backup and Recovery Procedures",
"type": "Technical content",
"status": "Done",
"target": "18",
"limit": "21",
"reviewer": "Assign reviewer"
},
{
"id": 61,
"header": "Monitoring and Alerting System",
"type": "Technical content",
"status": "In Process",
"target": "25",
"limit": "28",
"reviewer": "Daniel Park"
},
{
"id": 62,
"header": "Code Review Guidelines",
"type": "Technical content",
"status": "Done",
"target": "12",
"limit": "15",
"reviewer": "Eddie Lake"
},
{
"id": 63,
"header": "Documentation Standards",
"type": "Technical content",
"status": "In Process",
"target": "27",
"limit": "30",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 64,
"header": "Release Management Process",
"type": "Planning",
"status": "Done",
"target": "22",
"limit": "25",
"reviewer": "Assign reviewer"
},
{
"id": 65,
"header": "Feature Prioritization Matrix",
"type": "Planning",
"status": "In Process",
"target": "19",
"limit": "22",
"reviewer": "Emma Davis"
},
{
"id": 66,
"header": "Technical Debt Assessment",
"type": "Technical content",
"status": "Done",
"target": "24",
"limit": "27",
"reviewer": "Eddie Lake"
},
{
"id": 67,
"header": "Capacity Planning",
"type": "Planning",
"status": "In Process",
"target": "21",
"limit": "24",
"reviewer": "Jamik Tashpulatov"
},
{
"id": 68,
"header": "Service Level Agreements",
"type": "Legal",
"status": "Done",
"target": "26",
"limit": "29",
"reviewer": "Assign reviewer"
}
]

View File

@@ -0,0 +1,37 @@
import { AppSidebar } from "@/registry/bases/aria/blocks/dashboard-01/components/app-sidebar"
import { ChartAreaInteractive } from "@/registry/bases/aria/blocks/dashboard-01/components/chart-area-interactive"
import { DataTable } from "@/registry/bases/aria/blocks/dashboard-01/components/data-table"
import { SectionCards } from "@/registry/bases/aria/blocks/dashboard-01/components/section-cards"
import { SiteHeader } from "@/registry/bases/aria/blocks/dashboard-01/components/site-header"
import { SidebarInset, SidebarProvider } from "@/registry/bases/aria/ui/sidebar"
import data from "./data.json"
export default function Page() {
return (
<SidebarProvider
style={
{
"--sidebar-width": "calc(var(--spacing) * 72)",
"--header-height": "calc(var(--spacing) * 12)",
} as React.CSSProperties
}
>
<AppSidebar variant="inset" />
<SidebarInset>
<SiteHeader />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
<SectionCards />
<div className="px-4 lg:px-6">
<ChartAreaInteractive />
</div>
<DataTable data={data} />
</div>
</div>
</div>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,71 @@
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
export function LoginForm({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card>
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<form>
<FieldGroup>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</Field>
<Field>
<div className="flex items-center">
<FieldLabel htmlFor="password">Password</FieldLabel>
<a
href="#"
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password" type="password" required />
</Field>
<Field>
<Button type="submit">Login</Button>
<Button variant="outline" type="button">
Login with Google
</Button>
<FieldDescription className="text-center">
Don&apos;t have an account? <a href="#">Sign up</a>
</FieldDescription>
</Field>
</FieldGroup>
</form>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,11 @@
import { LoginForm } from "@/registry/bases/aria/blocks/login-01/components/login-form"
export default function Page() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<LoginForm />
</div>
</div>
)
}

View File

@@ -0,0 +1,66 @@
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
export function LoginForm({
className,
...props
}: React.ComponentProps<"form">) {
return (
<form className={cn("flex flex-col gap-6", className)} {...props}>
<FieldGroup>
<div className="flex flex-col items-center gap-1 text-center">
<h1 className="text-2xl font-bold">Login to your account</h1>
<p className="text-sm text-balance text-muted-foreground">
Enter your email below to login to your account
</p>
</div>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" placeholder="m@example.com" required />
</Field>
<Field>
<div className="flex items-center">
<FieldLabel htmlFor="password">Password</FieldLabel>
<a
href="#"
className="ml-auto text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password" type="password" required />
</Field>
<Field>
<Button type="submit">Login</Button>
</Field>
<FieldSeparator>Or continue with</FieldSeparator>
<Field>
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
fill="currentColor"
/>
</svg>
Login with GitHub
</Button>
<FieldDescription className="text-center">
Don&apos;t have an account?{" "}
<a href="#" className="underline underline-offset-4">
Sign up
</a>
</FieldDescription>
</Field>
</FieldGroup>
</form>
)
}

View File

@@ -0,0 +1,40 @@
"use client"
import { LoginForm } from "@/registry/bases/aria/blocks/login-02/components/login-form"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function LoginPage() {
return (
<div className="grid min-h-svh lg:grid-cols-2">
<div className="flex flex-col gap-4 p-6 md:p-10">
<div className="flex justify-center gap-2 md:justify-start">
<a href="#" className="flex items-center gap-2 font-medium">
<div className="flex size-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
<IconPlaceholder
lucide="GalleryVerticalEndIcon"
tabler="IconLayoutRows"
hugeicons="LayoutBottomIcon"
phosphor="RowsIcon"
remixicon="RiGalleryLine"
className="size-4"
/>
</div>
Acme Inc.
</a>
</div>
<div className="flex flex-1 items-center justify-center">
<div className="w-full max-w-xs">
<LoginForm />
</div>
</div>
</div>
<div className="relative hidden bg-muted lg:block">
<img
src="/placeholder.svg"
alt="Image"
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
/>
</div>
</div>
)
}

View File

@@ -0,0 +1,96 @@
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
export function LoginForm({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card>
<CardHeader className="text-center">
<CardTitle className="text-xl">Welcome back</CardTitle>
<CardDescription>
Login with your Apple or Google account
</CardDescription>
</CardHeader>
<CardContent>
<form>
<FieldGroup>
<Field>
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
fill="currentColor"
/>
</svg>
Login with Apple
</Button>
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
fill="currentColor"
/>
</svg>
Login with Google
</Button>
</Field>
<FieldSeparator className="*:data-[slot=field-separator-content]:bg-card">
Or continue with
</FieldSeparator>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</Field>
<Field>
<div className="flex items-center">
<FieldLabel htmlFor="password">Password</FieldLabel>
<a
href="#"
className="ml-auto text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password" type="password" required />
</Field>
<Field>
<Button type="submit">Login</Button>
<FieldDescription className="text-center">
Don&apos;t have an account? <a href="#">Sign up</a>
</FieldDescription>
</Field>
</FieldGroup>
</form>
</CardContent>
</Card>
<FieldDescription className="px-6 text-center">
By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
and <a href="#">Privacy Policy</a>.
</FieldDescription>
</div>
)
}

View File

@@ -0,0 +1,27 @@
"use client"
import { LoginForm } from "@/registry/bases/aria/blocks/login-03/components/login-form"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function LoginPage() {
return (
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
<div className="flex w-full max-w-sm flex-col gap-6">
<a href="#" className="flex items-center gap-2 self-center font-medium">
<div className="flex size-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
<IconPlaceholder
lucide="GalleryVerticalEndIcon"
tabler="IconLayoutRows"
hugeicons="LayoutBottomIcon"
phosphor="RowsIcon"
remixicon="RiGalleryLine"
className="size-4"
/>
</div>
Acme Inc.
</a>
<LoginForm />
</div>
</div>
)
}

View File

@@ -0,0 +1,106 @@
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
export function LoginForm({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card className="overflow-hidden p-0">
<CardContent className="grid p-0 md:grid-cols-2">
<form className="p-6 md:p-8">
<FieldGroup>
<div className="flex flex-col items-center gap-2 text-center">
<h1 className="text-2xl font-bold">Welcome back</h1>
<p className="text-balance text-muted-foreground">
Login to your Acme Inc account
</p>
</div>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</Field>
<Field>
<div className="flex items-center">
<FieldLabel htmlFor="password">Password</FieldLabel>
<a
href="#"
className="ml-auto text-sm underline-offset-2 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password" type="password" required />
</Field>
<Field>
<Button type="submit">Login</Button>
</Field>
<FieldSeparator className="*:data-[slot=field-separator-content]:bg-card">
Or continue with
</FieldSeparator>
<Field className="grid grid-cols-3 gap-4">
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
fill="currentColor"
/>
</svg>
<span className="sr-only">Login with Apple</span>
</Button>
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
fill="currentColor"
/>
</svg>
<span className="sr-only">Login with Google</span>
</Button>
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z"
fill="currentColor"
/>
</svg>
<span className="sr-only">Login with Meta</span>
</Button>
</Field>
<FieldDescription className="text-center">
Don&apos;t have an account? <a href="#">Sign up</a>
</FieldDescription>
</FieldGroup>
</form>
<div className="relative hidden bg-muted md:block">
<img
src="/placeholder.svg"
alt="Image"
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
/>
</div>
</CardContent>
</Card>
<FieldDescription className="px-6 text-center">
By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
and <a href="#">Privacy Policy</a>.
</FieldDescription>
</div>
)
}

View File

@@ -0,0 +1,11 @@
import { LoginForm } from "@/registry/bases/aria/blocks/login-04/components/login-form"
export default function LoginPage() {
return (
<div className="flex min-h-svh flex-col items-center justify-center bg-muted p-6 md:p-10">
<div className="w-full max-w-sm md:max-w-4xl">
<LoginForm />
</div>
</div>
)
}

View File

@@ -0,0 +1,87 @@
"use client"
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function LoginForm({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<form>
<FieldGroup>
<div className="flex flex-col items-center gap-2 text-center">
<a
href="#"
className="flex flex-col items-center gap-2 font-medium"
>
<div className="flex size-8 items-center justify-center rounded-md">
<IconPlaceholder
lucide="GalleryVerticalEndIcon"
tabler="IconLayoutRows"
hugeicons="LayoutBottomIcon"
phosphor="RowsIcon"
remixicon="RiGalleryLine"
className="size-6"
/>
</div>
<span className="sr-only">Acme Inc.</span>
</a>
<h1 className="text-xl font-bold">Welcome to Acme Inc.</h1>
<FieldDescription>
Don&apos;t have an account? <a href="#">Sign up</a>
</FieldDescription>
</div>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</Field>
<Field>
<Button type="submit">Login</Button>
</Field>
<FieldSeparator>Or</FieldSeparator>
<Field className="grid gap-4 sm:grid-cols-2">
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
fill="currentColor"
/>
</svg>
Continue with Apple
</Button>
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
fill="currentColor"
/>
</svg>
Continue with Google
</Button>
</Field>
</FieldGroup>
</form>
<FieldDescription className="px-6 text-center">
By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
and <a href="#">Privacy Policy</a>.
</FieldDescription>
</div>
)
}

View File

@@ -0,0 +1,11 @@
import { LoginForm } from "@/registry/bases/aria/blocks/login-05/components/login-form"
export default function LoginPage() {
return (
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-6 md:p-10">
<div className="w-full max-w-sm">
<LoginForm />
</div>
</div>
)
}

View File

@@ -0,0 +1,102 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function AccountAccess() {
return (
<Card>
<CardHeader>
<CardTitle>Account Access</CardTitle>
<CardDescription>
Update your credentials or re-authenticate.
</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="email-address">Email Address</FieldLabel>
<Input
id="email-address"
type="email"
defaultValue="artist@example.com"
/>
</Field>
<Field>
<div className="flex items-center justify-between">
<FieldLabel htmlFor="current-password">
Current Password
</FieldLabel>
<a
href="#"
className="text-xs font-medium tracking-wider text-muted-foreground uppercase hover:text-foreground"
>
Forgot?
</a>
</div>
<Input
id="current-password"
type="password"
defaultValue="password123"
/>
</Field>
</FieldGroup>
</CardContent>
<CardFooter className="flex-col gap-4">
<Button className="w-full">
<IconPlaceholder
lucide="LockKeyholeIcon"
tabler="IconLock"
hugeicons="SquareLock02Icon"
phosphor="LockKeyIcon"
remixicon="RiLockLine"
/>
Update Security
</Button>
<Item variant="muted" href="#">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="AlertCircleIcon"
tabler="IconAlertCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
className="text-destructive"
/>
</ItemMedia>
<ItemContent>
<ItemTitle>Danger Zone</ItemTitle>
<ItemDescription className="line-clamp-1">
Archive account and remove catalog
</ItemDescription>
</ItemContent>
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight01Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
className="size-4"
/>
</Item>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,49 @@
import { Badge } from "@/registry/bases/aria/ui/badge"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Separator } from "@/registry/bases/aria/ui/separator"
export function AlbumCard() {
return (
<Card>
<CardContent className="flex flex-col gap-4">
<div className="relative overflow-hidden rounded-lg">
<img
src="https://images.unsplash.com/photo-1534088568595-a066f410bcda?w=800&h=800&fit=crop"
alt="Synthetic Horizons EP cover art"
className="aspect-square w-full object-cover"
/>
<Badge className="absolute top-3 right-3">$26,033.79</Badge>
</div>
<div className="flex flex-col gap-1">
<CardTitle>Synthetic Horizons EP</CardTitle>
<CardDescription className="text-xs tracking-wider uppercase">
Released Aug 14, 2023
</CardDescription>
</div>
</CardContent>
<CardFooter className="flex-col gap-4">
<Separator />
<div className="grid w-full grid-cols-2 gap-4">
<div className="flex flex-col gap-0.5">
<span className="text-xs tracking-wider text-muted-foreground uppercase">
Tracks
</span>
<span className="text-lg font-medium tabular-nums">6 Tracks</span>
</div>
<div className="flex flex-col gap-0.5">
<span className="line-clamp-1 text-xs tracking-wider text-muted-foreground uppercase">
Cumulative Streams
</span>
<span className="text-lg font-medium tabular-nums">6,198,524</span>
</div>
</div>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,99 @@
"use client"
import { Bar, BarChart, XAxis } from "recharts"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
const activityData = [
{ month: "Jan", amount: 40 },
{ month: "Feb", amount: 55 },
{ month: "Mar", amount: 35 },
{ month: "Apr", amount: 60 },
{ month: "May", amount: 45 },
{ month: "Jun", amount: 50 },
{ month: "Jul", amount: 65 },
{ month: "Aug", amount: 40 },
{ month: "Sep", amount: 55 },
{ month: "Oct", amount: 70 },
{ month: "Nov", amount: 45 },
{ month: "Dec", amount: 80 },
]
const chartConfig = {
amount: {
label: "Activity",
color: "var(--chart-2)",
},
} satisfies ChartConfig
export function CardOverview() {
return (
<div className="grid grid-cols-2 gap-3">
<Card>
<CardContent>
<CardDescription>Card Balance</CardDescription>
<CardTitle className="text-2xl tabular-nums">US$12.94</CardTitle>
<CardDescription className="tabular-nums">
US$11,337.06 Available
</CardDescription>
</CardContent>
</Card>
<Card className="flex flex-col justify-between">
<CardContent className="flex flex-1 flex-col justify-between">
<div className="flex flex-col gap-1">
<CardDescription>Payment Due</CardDescription>
<CardTitle className="text-2xl">1 Apr</CardTitle>
</div>
<Button variant="outline" size="sm" className="mt-3 w-full">
Pay Early
</Button>
</CardContent>
</Card>
<Card className="col-span-2">
<CardContent className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<CardDescription>Yearly Activity</CardDescription>
<Badge variant="secondary">+US$0.25 Daily Cash</Badge>
</div>
<ChartContainer config={chartConfig} className="h-20 w-full">
<BarChart
data={activityData}
margin={{ top: 4, right: 0, bottom: 0, left: 0 }}
>
<XAxis
dataKey="month"
tickLine={false}
tickMargin={4}
axisLine={false}
tickFormatter={(v) => String(v).slice(0, 1)}
className="text-[10px]"
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar
dataKey="amount"
fill="var(--color-amount)"
radius={[3, 3, 0, 0]}
/>
</BarChart>
</ChartContainer>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,47 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/aria/ui/toggle-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function CatalogToolbar() {
return (
<div className="flex items-center gap-3">
<InputGroup className="flex-1">
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupAddon>
<InputGroupInput placeholder="Search releases or catalog..." />
</InputGroup>
<Button>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="Add01Icon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
Upload New Release
</Button>
<ToggleGroup defaultSelectedKeys={["releases"]} variant="outline">
<ToggleGroupItem id="all-tracks">All Tracks</ToggleGroupItem>
<ToggleGroupItem id="releases">Releases</ToggleGroupItem>
<ToggleGroupItem id="top-earners">Top Earners</ToggleGroupItem>
</ToggleGroup>
</div>
)
}

View File

@@ -0,0 +1,59 @@
import { Badge } from "@/registry/bases/aria/ui/badge"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Item, ItemContent } from "@/registry/bases/aria/ui/item"
import { Separator } from "@/registry/bases/aria/ui/separator"
export function ClaimableBalance() {
return (
<Card>
<CardHeader>
<CardDescription>Claimable Balance</CardDescription>
<CardTitle className="text-5xl tabular-nums">$0.00</CardTitle>
<Badge variant="outline">
<span className="size-2 rounded-full bg-yellow-500" />
Pending Setup
</Badge>
</CardHeader>
<CardContent className="flex flex-1 flex-col justify-end">
<Item variant="muted" className="flex-col items-stretch">
<ItemContent className="gap-3">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Net Royalties
</span>
<span className="text-sm font-medium tabular-nums">$0.00</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Processing Fee
</span>
<span className="text-sm font-medium tabular-nums">-$0.00</span>
</div>
<Separator />
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Total Ready to Claim
</span>
<span className="text-sm font-semibold tabular-nums">
$0.00 USD
</span>
</div>
</ItemContent>
</Item>
</CardContent>
<CardFooter>
<CardDescription>
Once your bank is connected, balances over $10.00 are automatically
eligible for monthly distribution on the 15th of each month.
</CardDescription>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,113 @@
"use client"
import { Bar, BarChart, XAxis } from "recharts"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import {
Item,
ItemContent,
ItemDescription,
} from "@/registry/bases/aria/ui/item"
import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params"
const chartData = [
{ month: "Dec", amount: 800 },
{ month: "Jan", amount: 1100 },
{ month: "Feb", amount: 900 },
{ month: "Mar", amount: 1300 },
{ month: "Apr", amount: 750 },
{ month: "May", amount: 1400 },
]
const chartConfig = {
amount: {
label: "Contribution",
color: "var(--chart-2)",
},
} satisfies ChartConfig
export function ContributionHistory() {
const [params] = useDesignSystemSearchParams()
const isRounded = !["lyra", "sera"].includes(params.style)
return (
<Card>
<CardHeader>
<CardTitle>Contribution History</CardTitle>
<CardDescription>Last 6 months of activity</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig} className="h-[200px] w-full">
<BarChart
accessibilityLayer
data={chartData}
margin={{ left: 0, right: 0, top: 8, bottom: 0 }}
>
<XAxis
dataKey="month"
tickLine={false}
tickMargin={8}
axisLine={false}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel className="min-w-40" />}
/>
<Bar
dataKey="amount"
fill="var(--color-amount)"
radius={isRounded ? [6, 6, 0, 0] : 0}
maxBarSize={40}
/>
</BarChart>
</ChartContainer>
</CardContent>
<CardContent>
<div className="grid w-full grid-cols-1 gap-3 md:grid-cols-2">
<Item variant="muted" className="flex-col items-stretch">
<ItemContent className="gap-1">
<ItemDescription className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
Upcoming
</ItemDescription>
<span className="cn-font-heading text-lg font-semibold">
May 25, 2024
</span>
<span className="text-sm text-muted-foreground">
$1,000 scheduled
</span>
</ItemContent>
</Item>
<Item variant="muted" className="flex-col items-stretch">
<ItemContent className="gap-1">
<ItemDescription className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
Auto-Save Plan
</ItemDescription>
<span className="cn-font-heading text-lg font-semibold">
Accelerated
</span>
<span className="text-sm text-muted-foreground">
Recurring weekly
</span>
</ItemContent>
</Item>
</div>
</CardContent>
<CardFooter>
<Button className="w-full">View Full Report</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,58 @@
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
} from "@/registry/bases/aria/ui/card"
import { Item } from "@/registry/bases/aria/ui/item"
import { Label } from "@/registry/bases/aria/ui/label"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function CoverArt() {
return (
<Card>
<CardContent className="flex flex-col gap-3">
<Label
htmlFor="cover-art"
className="text-center text-xs font-normal tracking-wider text-muted-foreground uppercase"
>
Cover Art
</Label>
<Item className="aspect-square" variant="outline">
<label
htmlFor="cover-art"
className="flex size-full cursor-pointer items-center justify-center"
>
<IconPlaceholder
lucide="ImageIcon"
tabler="IconPhoto"
hugeicons="Image01Icon"
phosphor="ImageIcon"
remixicon="RiImageLine"
className="size-10 text-muted-foreground/50"
/>
</label>
</Item>
<input
id="cover-art"
type="file"
accept="image/jpeg,image/png"
className="sr-only"
/>
</CardContent>
<CardFooter className="flex-col gap-2">
<label htmlFor="cover-art" className="w-full">
<Button variant="secondary" className="w-full">
Upload Artwork
</Button>
</label>
<CardDescription className="text-center text-xs">
Minimum 3000 × 3000px
<br />
JPEG or PNG only
</CardDescription>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,142 @@
"use client"
import { Bar, BarChart, XAxis } from "recharts"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import {
Item,
ItemContent,
ItemDescription,
ItemGroup,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const HOLDINGS = [
{
name: "Vanguard VIG",
shares: "450 Shares",
amount: "$1,842.10",
data: [
{ q: "Q1", value: 380 },
{ q: "Q2", value: 420 },
{ q: "Q3", value: 390 },
{ q: "Q4", value: 652 },
],
},
{
name: "S&P 500 VOO",
shares: "112 Shares",
amount: "$928.40",
data: [
{ q: "Q1", value: 180 },
{ q: "Q2", value: 210 },
{ q: "Q3", value: 320 },
{ q: "Q4", value: 218 },
],
},
{
name: "Apple AAPL",
shares: "85 Shares",
amount: "$340.00",
data: [
{ q: "Q1", value: 60 },
{ q: "Q2", value: 70 },
{ q: "Q3", value: 120 },
{ q: "Q4", value: 90 },
],
},
{
name: "Realty Income",
shares: "320 Shares",
amount: "$1,139.50",
data: [
{ q: "Q1", value: 240 },
{ q: "Q2", value: 260 },
{ q: "Q3", value: 280 },
{ q: "Q4", value: 360 },
],
},
]
const miniChartConfig = {
value: {
label: "Dividend",
color: "var(--chart-2)",
},
} satisfies ChartConfig
export function DividendIncome() {
const [params] = useDesignSystemSearchParams()
const isRounded = !["lyra", "sera"].includes(params.style)
return (
<Card>
<CardHeader>
<CardTitle>Q2 Dividend Income</CardTitle>
<CardDescription>
Quarterly dividend payouts across your portfolio holdings.
</CardDescription>
<CardAction>
<Button variant="ghost" size="icon-sm" className="bg-muted">
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
/>
</Button>
</CardAction>
</CardHeader>
<CardContent>
<ItemGroup>
{HOLDINGS.map((holding) => (
<Item key={holding.name} variant="muted">
<ItemContent>
<ItemTitle>{holding.name}</ItemTitle>
<ItemDescription>{holding.shares}</ItemDescription>
</ItemContent>
<ChartContainer
config={miniChartConfig}
className="hidden h-8 w-24 md:block"
>
<BarChart
data={holding.data}
margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar
dataKey="value"
fill="var(--color-value)"
radius={isRounded ? [3, 3, 0, 0] : 0}
/>
</BarChart>
</ChartContainer>
<span className="hidden text-sm font-semibold tabular-nums md:block">
{holding.amount}
</span>
</Item>
))}
</ItemGroup>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,41 @@
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function EmptyConnectBank() {
return (
<Card>
<CardContent>
<Empty className="p-4">
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>Connect Bank</EmptyTitle>
<EmptyDescription>
Link your payout method to receive monthly royalty distributions
automatically.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Set Up Payouts</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,41 @@
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function EmptyDistributeTrack() {
return (
<Card>
<CardContent>
<Empty className="p-4">
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="Add01Icon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>Distribute Track</EmptyTitle>
<EmptyDescription>
Upload your first master to start reaching listeners on Spotify,
Apple Music, and more.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Create Release</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,41 @@
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function EmptyExploreCatalog() {
return (
<Card>
<CardContent>
<Empty className="p-4">
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="AudioLinesIcon"
tabler="IconPlayerRecordFilled"
hugeicons="AudioWave01Icon"
phosphor="RecordIcon"
remixicon="RiRecordCircleLine"
/>
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>Explore Catalog</EmptyTitle>
<EmptyDescription>
Check your ISRC codes, metadata, and visual assets before going
live.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>View Catalog</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,124 @@
"use client"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/registry/bases/aria/ui/accordion"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/bases/aria/ui/tabs"
const GENERAL_QUESTIONS = [
{
q: "How secure is my financial data with Ledger?",
a: "We use bank-level AES-256 encryption, SOC 2 Type II certified infrastructure, and never store your credentials. All connections use read-only access tokens. We are a SEC registered investment advisor.",
},
{
q: "How do I connect my bank or investment accounts?",
a: "Go to Settings > Linked Accounts and search for your institution. We support over 12,000 banks and brokerages via Plaid and MX.",
},
{
q: "Can I export my data for tax purposes?",
a: "Yes. Navigate to Reports > Tax Export to download a CSV or PDF summary of your transactions, dividends, and capital gains for any tax year.",
},
]
const BILLING_QUESTIONS = [
{
q: "What is the difference between Basic and Pro pricing tiers?",
a: "Basic includes budgeting, goal tracking, and up to 3 linked accounts. Pro adds unlimited accounts, dividend tracking, portfolio analysis, and priority support.",
},
{
q: "How do I cancel my subscription?",
a: "Go to Settings > Billing > Manage Plan and click Cancel. Your access continues until the end of your current billing period.",
},
{
q: "Do you offer a free trial?",
a: "Yes. All new accounts start with a 14-day Pro trial. No credit card required.",
},
]
const GOALS_QUESTIONS = [
{
q: "How do I set up a custom financial goal?",
a: "Click New Goal from the Savings Targets card. Choose a category, set a target amount and date, and we'll calculate the monthly contribution needed.",
},
{
q: "Can I track multiple goals at once?",
a: "Yes. Pro accounts can track unlimited goals. Basic accounts support up to 3 active goals.",
},
{
q: "How are monthly contributions calculated?",
a: "We divide the remaining amount by the number of months until your target date, adjusted for your current savings rate and any auto-transfer schedules.",
},
]
function QuestionList({
questions,
}: {
questions: { q: string; a: string }[]
}) {
return (
<Accordion>
{questions.map((item, index) => (
<AccordionItem key={index} id={`question-${index}`}>
<AccordionTrigger>{item.q}</AccordionTrigger>
<AccordionContent>{item.a}</AccordionContent>
</AccordionItem>
))}
</Accordion>
)
}
export function Faq() {
return (
<Card>
<CardContent>
<Tabs defaultSelectedKey="general">
<TabsList className="w-full">
<TabsTrigger id="general" className="flex-1">
General
</TabsTrigger>
<TabsTrigger id="billing" className="flex-1">
Billing
</TabsTrigger>
<TabsTrigger id="goals" className="flex-1">
Goals
</TabsTrigger>
</TabsList>
<TabsContent id="general">
<QuestionList questions={GENERAL_QUESTIONS} />
</TabsContent>
<TabsContent id="billing">
<QuestionList questions={BILLING_QUESTIONS} />
</TabsContent>
<TabsContent id="goals">
<QuestionList questions={GOALS_QUESTIONS} />
</TabsContent>
</Tabs>
</CardContent>
<CardFooter>
<Button variant="outline" className="w-full">
Contact Support
</Button>
<Button variant="link" className="w-full">
Learn More
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,41 @@
import { Badge } from "@/registry/bases/aria/ui/badge"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function FrontDoor() {
return (
<Card>
<CardHeader>
<CardTitle>Front Door</CardTitle>
<CardDescription>Smart Lock Pro</CardDescription>
<CardAction>
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
Locked
<IconPlaceholder
lucide="LockIcon"
tabler="IconLock"
hugeicons="SquareLock02Icon"
phosphor="LockKeyIcon"
remixicon="RiLockLine"
className="size-4"
/>
</div>
</CardAction>
</CardHeader>
<CardContent>
<div className="relative flex aspect-video items-center justify-center overflow-hidden rounded-lg bg-muted bg-[repeating-linear-gradient(45deg,transparent,transparent_10px,var(--border)_10px,var(--border)_11px)]">
<Badge variant="destructive" className="absolute top-2 right-2">
Live
</Badge>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,37 @@
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function IndexInvesting() {
return (
<Card>
<CardHeader>
<CardTitle>Dollar-Cost Averaging</CardTitle>
<CardDescription>
A strategy for building wealth over time.
</CardDescription>
</CardHeader>
<CardContent>
<CardDescription className="mt-3 text-sm leading-relaxed style-sera:mt-0">
<a
href="#"
className="underline underline-offset-4 hover:text-primary"
>
Over time
</a>
, this smooths out the average cost of your investments. When prices
drop, your fixed amount buys more shares. When prices rise, you buy
fewer. The result is a lower average cost per share compared to
lump-sum investing during volatile periods.
</CardDescription>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,191 @@
"use client"
import * as React from "react"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Item,
ItemActions,
ItemContent,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import { Slider } from "@/registry/bases/aria/ui/slider"
import { Switch } from "@/registry/bases/aria/ui/switch"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/aria/ui/toggle-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const SCENES = {
cooking: { brightness: [90], colorTemp: [70], volume: [30], fade: [0] },
dining: { brightness: [50], colorTemp: [40], volume: [20], fade: [60] },
nightlight: { brightness: [15], colorTemp: [20], volume: [0], fade: [80] },
focus: { brightness: [100], colorTemp: [85], volume: [0], fade: [0] },
} as const
export function KitchenIsland() {
const [enabled, setEnabled] = React.useState(true)
const [scene, setScene] = React.useState("cooking")
const [brightness, setBrightness] = React.useState([90])
const [colorTemp, setColorTemp] = React.useState([70])
const [volume, setVolume] = React.useState([30])
const [fade, setFade] = React.useState([0])
const handleSceneChange = (value: string) => {
if (!value) return
setScene(value)
const preset = SCENES[value as keyof typeof SCENES]
setBrightness([...preset.brightness])
setColorTemp([...preset.colorTemp])
setVolume([...preset.volume])
setFade([...preset.fade])
}
return (
<Card>
<CardHeader>
<CardTitle>Kitchen Island</CardTitle>
<CardDescription>Hue Color Ambient</CardDescription>
<CardAction>
<Switch isSelected={enabled} onChange={setEnabled} />
</CardAction>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<span className="sr-only">Scenes</span>
<ToggleGroup
selectedKeys={[scene]}
onSelectionChange={(value) =>
handleSceneChange((Array.from(value)[0] as string) ?? "cooking")
}
variant="outline"
spacing={1}
className="flex-wrap"
>
<ToggleGroupItem id="cooking" isDisabled={!enabled}>
Cooking
</ToggleGroupItem>
<ToggleGroupItem id="dining" isDisabled={!enabled}>
Dining
</ToggleGroupItem>
<ToggleGroupItem id="nightlight" isDisabled={!enabled}>
Nightlight
</ToggleGroupItem>
<ToggleGroupItem id="focus" isDisabled={!enabled}>
Focus
</ToggleGroupItem>
</ToggleGroup>
</div>
<ItemGroup>
<Item size="sm" variant="outline">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="SunIcon"
tabler="IconSun"
hugeicons="Sun03Icon"
phosphor="SunIcon"
remixicon="RiSunLine"
/>
</ItemMedia>
<ItemContent className="flex-row items-center gap-3">
<ItemTitle className="shrink-0">Brightness</ItemTitle>
</ItemContent>
<ItemActions className="flex-1">
<Slider
value={brightness}
onChange={(value) =>
setBrightness(Array.isArray(value) ? [...value] : [value])
}
maxValue={100}
isDisabled={!enabled}
className="w-full"
/>
</ItemActions>
</Item>
<Item size="sm" variant="outline">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="ThermometerIcon"
tabler="IconThermometer"
hugeicons="ThermometerWarmIcon"
phosphor="ThermometerIcon"
remixicon="RiThermometerLine"
/>
</ItemMedia>
<ItemContent className="flex-row items-center gap-3">
<ItemTitle className="shrink-0">Color Temp</ItemTitle>
</ItemContent>
<ItemActions className="flex-1">
<Slider
value={colorTemp}
onChange={(value) =>
setColorTemp(Array.isArray(value) ? [...value] : [value])
}
maxValue={100}
isDisabled={!enabled}
/>
</ItemActions>
</Item>
<Item size="sm" variant="outline">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="Volume2Icon"
tabler="IconVolume"
hugeicons="VolumeHighIcon"
phosphor="SpeakerHighIcon"
remixicon="RiVolumeUpLine"
/>
</ItemMedia>
<ItemContent className="flex-row items-center gap-3">
<ItemTitle className="shrink-0">Volume</ItemTitle>
</ItemContent>
<ItemActions className="flex-1">
<Slider
value={volume}
onChange={(value) =>
setVolume(Array.isArray(value) ? [...value] : [value])
}
maxValue={100}
isDisabled={!enabled}
/>
</ItemActions>
</Item>
<Item size="sm" variant="outline">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="TimerIcon"
tabler="IconClock"
hugeicons="Clock03Icon"
phosphor="TimerIcon"
remixicon="RiTimerLine"
/>
</ItemMedia>
<ItemContent className="flex-row items-center gap-3">
<ItemTitle className="shrink-0">Fade</ItemTitle>
</ItemContent>
<ItemActions className="flex-1">
<Slider
value={fade}
onChange={(value) =>
setFade(Array.isArray(value) ? [...value] : [value])
}
maxValue={100}
isDisabled={!enabled}
/>
</ItemActions>
</Item>
</ItemGroup>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,25 @@
import { Card, CardContent, CardHeader } from "@/registry/bases/aria/ui/card"
import { Skeleton } from "@/registry/bases/aria/ui/skeleton"
export function LoadingCard() {
return (
<Card>
<CardHeader>
<Skeleton className="h-5 w-32" />
<Skeleton className="h-4 w-48" />
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Skeleton className="h-32 w-full rounded-lg" />
<div className="flex flex-col gap-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-4 w-1/2" />
</div>
<div className="flex gap-2">
<Skeleton className="h-9 flex-1 rounded-md" />
<Skeleton className="h-9 flex-1 rounded-md" />
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,54 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
export function NewMilestone() {
return (
<Card>
<CardHeader>
<CardTitle>Set a new milestone</CardTitle>
<CardDescription>
Define your financial target and we&apos;ll help you pace your
savings.
</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="goal-name">Goal Name</FieldLabel>
<Input
id="goal-name"
placeholder="e.g. New Car, Home Downpayment"
/>
</Field>
<div className="grid grid-cols-2 gap-3">
<Field>
<FieldLabel htmlFor="target-amount">Target Amount</FieldLabel>
<Input id="target-amount" defaultValue="$15,000" />
</Field>
<Field>
<FieldLabel htmlFor="target-date">Target Date</FieldLabel>
<Input id="target-date" defaultValue="Dec 2025" />
</Field>
</div>
</FieldGroup>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button className="w-full">Create Goal</Button>
<Button variant="outline" className="w-full">
Cancel
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,107 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
const NOTIFICATIONS = [
{
id: "transactions",
label: "Transaction alerts",
description: "Deposits, withdrawals, and transfers.",
defaultChecked: true,
},
{
id: "security",
label: "Security alerts",
description: "Login attempts and account changes.",
defaultChecked: true,
},
{
id: "goals",
label: "Goal milestones",
description: "Updates at 25%, 50%, 75%, and 100%.",
defaultChecked: false,
},
{
id: "market",
label: "Market updates",
description: "Daily portfolio summary and price alerts.",
defaultChecked: false,
},
]
export function NotificationSettings() {
const [checked, setChecked] = React.useState<Record<string, boolean>>(
Object.fromEntries(NOTIFICATIONS.map((n) => [n.id, n.defaultChecked]))
)
const allChecked = NOTIFICATIONS.every((n) => checked[n.id])
const someChecked = NOTIFICATIONS.some((n) => checked[n.id]) && !allChecked
const handleSelectAll = (value: boolean) => {
setChecked(Object.fromEntries(NOTIFICATIONS.map((n) => [n.id, value])))
}
const handleToggle = (id: string, value: boolean) => {
setChecked((prev) => ({ ...prev, [id]: value }))
}
return (
<Card>
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>
Choose what you want to be notified about.
</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field orientation="horizontal">
<Checkbox
id="notify-all"
isSelected={allChecked}
isIndeterminate={someChecked}
onChange={(value) => handleSelectAll(!!value)}
/>
<FieldContent>
<FieldLabel htmlFor="notify-all">Select all</FieldLabel>
</FieldContent>
</Field>
{NOTIFICATIONS.map((n) => (
<Field key={n.id} orientation="horizontal">
<Checkbox
id={`notify-${n.id}`}
isSelected={checked[n.id]}
onChange={(value) => handleToggle(n.id, !!value)}
/>
<FieldContent>
<FieldLabel htmlFor={`notify-${n.id}`}>{n.label}</FieldLabel>
<FieldDescription>{n.description}</FieldDescription>
</FieldContent>
</Field>
))}
</FieldGroup>
</CardContent>
<CardFooter>
<Button className="w-full">Save Preferences</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,170 @@
"use client"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
} from "@/registry/bases/aria/ui/breadcrumb"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent, CardHeader } from "@/registry/bases/aria/ui/card"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
Item,
ItemContent,
ItemDescription,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function Payments() {
return (
<Card>
<CardHeader className="flex flex-col gap-3">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<DropdownMenuTrigger>
<Button size="icon-sm" variant="ghost">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">Account options</span>
</Button>
<DropdownMenu placement="bottom start">
<DropdownMenuGroup>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Statements</DropdownMenuItem>
<DropdownMenuItem>Documents</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbPage>Payments</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</CardHeader>
<CardContent>
<ItemGroup>
<Item variant="muted" href="#">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="GaugeIcon"
tabler="IconGauge"
hugeicons="Settings01Icon"
phosphor="GaugeIcon"
remixicon="RiDashboardLine"
/>
</ItemMedia>
<ItemContent>
<ItemTitle>Change transfer limit</ItemTitle>
<ItemDescription>
Adjust how much you can send from your balance.
</ItemDescription>
</ItemContent>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="size-4 shrink-0 text-muted-foreground"
/>
</Item>
<Item variant="muted" href="#">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="Calendar03Icon"
phosphor="CalendarIcon"
remixicon="RiCalendarLine"
/>
</ItemMedia>
<ItemContent>
<ItemTitle>Scheduled transfers</ItemTitle>
<ItemDescription>
Set up a transfer to send at a later date.
</ItemDescription>
</ItemContent>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="size-4 shrink-0 text-muted-foreground"
/>
</Item>
<Item variant="muted" href="#">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="RepeatIcon"
tabler="IconRepeat"
hugeicons="RepeatIcon"
phosphor="RepeatIcon"
remixicon="RiRepeatLine"
/>
</ItemMedia>
<ItemContent>
<ItemTitle>Direct Debits</ItemTitle>
<ItemDescription>
Set up and manage regular payments.
</ItemDescription>
</ItemContent>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="size-4 shrink-0 text-muted-foreground"
/>
</Item>
<Item variant="muted" href="#">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="RefreshCwIcon"
tabler="IconRefresh"
hugeicons="RepeatIcon"
phosphor="ArrowsClockwiseIcon"
remixicon="RiRefreshLine"
/>
</ItemMedia>
<ItemContent>
<ItemTitle>Recurring card payments</ItemTitle>
<ItemDescription>
Manage your repeated card transactions.
</ItemDescription>
</ItemContent>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="size-4 shrink-0 text-muted-foreground"
/>
</Item>
</ItemGroup>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,122 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Slider } from "@/registry/bases/aria/ui/slider"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const CURRENCIES = [
{ label: "USD — United States Dollar", value: "usd" },
{ label: "EUR — Euro", value: "eur" },
{ label: "GBP — British Pound", value: "gbp" },
{ label: "JPY — Japanese Yen", value: "jpy" },
]
export function PayoutThreshold() {
const [amount, setAmount] = React.useState([2500])
return (
<Card>
<CardHeader>
<CardTitle>Payout Threshold</CardTitle>
<CardDescription>
Set the minimum balance required before a payout is triggered.
</CardDescription>
<CardAction>
<Button variant="ghost" size="icon-sm" className="bg-muted">
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
/>
</Button>
</CardAction>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="preferred-currency">
Preferred Currency
</FieldLabel>
<Select defaultValue="usd">
<SelectTrigger id="preferred-currency" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{CURRENCIES.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<div className="flex items-baseline justify-between">
<FieldLabel htmlFor="min-payout">
Minimum Payout Amount
</FieldLabel>
<span className="text-2xl font-semibold tabular-nums">
${amount[0].toFixed(2)}
</span>
</div>
<Slider
id="min-payout"
value={amount}
onChange={(value) =>
setAmount(Array.isArray(value) ? [...value] : [value])
}
minValue={50}
maxValue={10000}
step={50}
/>
<div className="flex items-center justify-between">
<FieldDescription>$50 (MIN)</FieldDescription>
<FieldDescription>$10,000 (MAX)</FieldDescription>
</div>
</Field>
<Field>
<FieldLabel htmlFor="payout-notes">Notes</FieldLabel>
<Textarea
id="payout-notes"
placeholder="Add any notes for this payout configuration..."
className="min-h-[100px]"
/>
</Field>
</FieldGroup>
</CardContent>
<CardFooter>
<Button className="w-full">Save Threshold</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,99 @@
"use client"
import { Bar, BarChart, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import { Progress } from "@/registry/bases/aria/ui/progress"
import { Separator } from "@/registry/bases/aria/ui/separator"
import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params"
const chartData = [
{ hour: "6a", usage: 1.2 },
{ hour: "8a", usage: 2.8 },
{ hour: "10a", usage: 3.1 },
{ hour: "12p", usage: 2.4 },
{ hour: "2p", usage: 3.4 },
{ hour: "4p", usage: 2.9 },
{ hour: "6p", usage: 3.8 },
{ hour: "8p", usage: 3.2 },
]
const chartConfig = {
usage: {
label: "Usage (kW)",
color: "var(--chart-2)",
},
} satisfies ChartConfig
export function PowerUsage() {
const [params] = useDesignSystemSearchParams()
const isRounded = !["lyra", "sera"].includes(params.style)
return (
<Card>
<CardHeader>
<CardTitle>Power Usage</CardTitle>
<CardDescription>Whole Home</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<ChartContainer config={chartConfig} className="h-[140px] w-full">
<BarChart
data={chartData}
margin={{ left: 0, right: 0, top: 4, bottom: 0 }}
>
<XAxis
dataKey="hour"
tickLine={false}
tickMargin={6}
axisLine={false}
className="text-xs"
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar
dataKey="usage"
fill="var(--color-usage)"
radius={isRounded ? [4, 4, 0, 0] : 0}
/>
</BarChart>
</ChartContainer>
<Separator />
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-0.5">
<span className="text-sm text-muted-foreground">
Currently Using
</span>
<span className="text-lg font-semibold tabular-nums">3.4 kW</span>
</div>
<div className="flex flex-col gap-0.5">
<span className="text-sm text-muted-foreground">Solar Gen</span>
<span className="text-lg font-semibold text-chart-1 tabular-nums">
+1.2 kW
</span>
</div>
</div>
</CardContent>
<CardFooter className="flex-col items-start gap-1">
<span className="text-sm text-muted-foreground">Battery Level</span>
<div className="flex w-full items-center gap-2">
<Progress value={85} className="flex-1" />
<span className="text-sm font-medium tabular-nums">85%</span>
</div>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,109 @@
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
} from "@/registry/bases/aria/ui/field"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Switch } from "@/registry/bases/aria/ui/switch"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const CURRENCIES = [
{ label: "USD — United States Dollar", value: "usd" },
{ label: "EUR — Euro", value: "eur" },
{ label: "GBP — British Pound", value: "gbp" },
{ label: "JPY — Japanese Yen", value: "jpy" },
]
export function Preferences() {
return (
<Card>
<CardHeader>
<CardTitle>Preferences</CardTitle>
<CardDescription>
Manage your account settings and notifications.
</CardDescription>
<CardAction>
<Button variant="ghost" size="icon-sm" className="bg-muted">
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
/>
</Button>
</CardAction>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="default-currency">Default Currency</FieldLabel>
<Select defaultValue="usd">
<SelectTrigger id="default-currency" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{CURRENCIES.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<FieldSeparator className="-my-4 style-sera:hidden" />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="public-statistics">
Public Statistics
</FieldLabel>
<FieldDescription>
Allow others to see your total stream count and listening
activity
</FieldDescription>
</FieldContent>
<Switch id="public-statistics" defaultSelected />
</Field>
<FieldSeparator className="-my-4 style-sera:hidden" />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="email-notifications">
Email Notifications
</FieldLabel>
<FieldDescription>
Monthly royalty reports and distribution updates
</FieldDescription>
</FieldContent>
<Switch id="email-notifications" defaultSelected />
</Field>
</FieldGroup>
</CardContent>
<CardFooter>
<Button variant="outline">Reset</Button>
<Button className="ml-auto">Save Preferences</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,40 @@
"use client"
import QRCode from "react-qr-code"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
export function QrConnect() {
return (
<Card>
<CardContent className="flex justify-center pt-6">
<div className="rounded-xl border bg-white p-4">
<QRCode
value="https://ledger.app/connect/jd-4829"
size={160}
level="M"
/>
</div>
</CardContent>
<CardHeader className="text-center">
<CardTitle>Scan to connect your mobile device</CardTitle>
<CardDescription>
Open the Ledger mobile app and scan this code to link your device.
</CardDescription>
</CardHeader>
<CardFooter>
<Button variant="secondary" className="w-full">
Got it
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,100 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/bases/aria/ui/radio-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function ReceivingMethod() {
return (
<Card>
<CardHeader>
<CardDescription>Payout Preferences</CardDescription>
<CardTitle>Receiving Method</CardTitle>
<CardAction>
<Button variant="ghost" size="icon-sm" className="bg-muted">
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
/>
</Button>
</CardAction>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="account-holder">
Account Holder Name
</FieldLabel>
<Input
id="account-holder"
defaultValue="Synthetic Horizons Music LLC"
/>
</Field>
<FieldSet>
<FieldLegend variant="label">Receiving Method</FieldLegend>
<RadioGroup
defaultValue="bank"
className="grid grid-cols-1 items-start gap-3 md:grid-cols-2 style-sera:grid-cols-1"
>
<FieldLabel htmlFor="method-bank">
<Field orientation="horizontal" className="pb-2.5">
<RadioGroupItem value="bank" id="method-bank" />
<FieldContent>
<FieldTitle>Bank Transfer</FieldTitle>
<FieldDescription>SWIFT / IBAN</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
<FieldLabel htmlFor="method-paypal">
<Field orientation="horizontal" className="pb-2.5">
<RadioGroupItem value="paypal" id="method-paypal" />
<FieldContent>
<FieldTitle>PayPal</FieldTitle>
<FieldDescription className="line-clamp-1">
Instant Payout
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
</RadioGroup>
</FieldSet>
<Field>
<FieldLabel htmlFor="iban">IBAN / Account Number</FieldLabel>
<Input id="iban" placeholder="DE89 3704 0044 ...." />
</Field>
</FieldGroup>
</CardContent>
<CardFooter>
<Button className="w-full" isDisabled>
Save Payout Settings
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,305 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/bases/aria/ui/table"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function RecentTransactions() {
return (
<Card>
<CardHeader>
<CardTitle>Recent Transactions</CardTitle>
<CardDescription>Your latest account activity.</CardDescription>
<CardAction>
<Button variant="outline" size="sm">
View All
</Button>
</CardAction>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableHead />
<TableHead isRowHeader>Merchant</TableHead>
<TableHead>Date</TableHead>
<TableHead>Amount</TableHead>
<TableHead />
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="w-10">
<div className="flex size-10 items-center justify-center rounded-lg bg-muted">
<IconPlaceholder
className="size-4 shrink-0"
lucide="CoffeeIcon"
tabler="IconCoffee"
hugeicons="CoffeeIcon"
phosphor="CoffeeIcon"
remixicon="RiCupLine"
/>
</div>
</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium">Blue Bottle Coffee</span>
<span className="text-sm text-muted-foreground">
Food & Drink
</span>
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
Today, 10:24 AM
</TableCell>
<TableCell className="text-right">
<span className="text-sm font-semibold tabular-nums">
-$6.50
</span>
</TableCell>
<TableCell className="w-8">
<DropdownMenuTrigger>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMore2Line"
/>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>View details</DropdownMenuItem>
<DropdownMenuItem>Add note</DropdownMenuItem>
<DropdownMenuItem>Categorize</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Dispute</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="w-10">
<div className="flex size-10 items-center justify-center rounded-lg bg-muted">
<IconPlaceholder
className="size-4 shrink-0"
lucide="ShoppingCartIcon"
tabler="IconShoppingCart"
hugeicons="ShoppingCart01Icon"
phosphor="ShoppingCartIcon"
remixicon="RiShoppingCartLine"
/>
</div>
</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium">Whole Foods Market</span>
<span className="text-sm text-muted-foreground">
Groceries
</span>
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
Yesterday
</TableCell>
<TableCell className="text-right">
<span className="text-sm font-semibold tabular-nums">
-$142.30
</span>
</TableCell>
<TableCell className="w-8">
<DropdownMenuTrigger>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMore2Line"
/>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>View details</DropdownMenuItem>
<DropdownMenuItem>Add note</DropdownMenuItem>
<DropdownMenuItem>Categorize</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Dispute</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="w-10">
<div className="flex size-10 items-center justify-center rounded-lg bg-muted">
<IconPlaceholder
className="size-4 shrink-0"
lucide="WalletIcon"
tabler="IconWallet"
hugeicons="Wallet01Icon"
phosphor="WalletIcon"
remixicon="RiWalletLine"
/>
</div>
</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium">Stripe Payout</span>
<span className="text-sm text-muted-foreground">Income</span>
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
Oct 12
</TableCell>
<TableCell className="text-right">
<span className="text-sm font-semibold text-emerald-500 tabular-nums">
+$4,200.00
</span>
</TableCell>
<TableCell className="w-8">
<DropdownMenuTrigger>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMore2Line"
/>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>View details</DropdownMenuItem>
<DropdownMenuItem>Add note</DropdownMenuItem>
<DropdownMenuItem>Categorize</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Dispute</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="w-10">
<div className="flex size-10 items-center justify-center rounded-lg bg-muted">
<IconPlaceholder
className="size-4 shrink-0"
lucide="CarIcon"
tabler="IconCar"
hugeicons="Car01Icon"
phosphor="CarIcon"
remixicon="RiCarLine"
/>
</div>
</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium">Uber Technologies</span>
<span className="text-sm text-muted-foreground">
Transport
</span>
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
Oct 11
</TableCell>
<TableCell className="text-right">
<span className="text-sm font-semibold tabular-nums">
-$24.10
</span>
</TableCell>
<TableCell className="w-8">
<DropdownMenuTrigger>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMore2Line"
/>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>View details</DropdownMenuItem>
<DropdownMenuItem>Add note</DropdownMenuItem>
<DropdownMenuItem>Categorize</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Dispute</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="w-10">
<div className="flex size-10 items-center justify-center rounded-lg bg-muted">
<IconPlaceholder
className="size-4 shrink-0"
lucide="TvIcon"
tabler="IconDeviceTv"
hugeicons="Tv01Icon"
phosphor="TelevisionIcon"
remixicon="RiTvLine"
/>
</div>
</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium">Netflix Subscription</span>
<span className="text-sm text-muted-foreground">
Entertainment
</span>
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
Oct 10
</TableCell>
<TableCell className="text-right">
<span className="text-sm font-semibold tabular-nums">
-$19.99
</span>
</TableCell>
<TableCell className="w-8">
<DropdownMenuTrigger>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMore2Line"
/>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>View details</DropdownMenuItem>
<DropdownMenuItem>Add note</DropdownMenuItem>
<DropdownMenuItem>Categorize</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Dispute</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,119 @@
"use client"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Card, CardContent, CardHeader } from "@/registry/bases/aria/ui/card"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import {
Item,
ItemContent,
ItemDescription,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/aria/ui/toggle-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const HOLDINGS = [
{
ticker: "VOO",
name: "Vanguard S&P 500 ETF",
type: "ETF",
added: "Jan 2021",
shares: "112",
value: "$48,230.40",
},
{
ticker: "VIG",
name: "Vanguard Dividend Appreciation",
type: "ETF",
added: "Mar 2022",
shares: "450",
value: "$26,033.79",
},
{
ticker: "AAPL",
name: "Apple Inc.",
type: "Stock",
added: "Nov 2020",
shares: "85",
value: "$18,488.90",
},
{
ticker: "O",
name: "Realty Income Corp",
type: "REIT",
added: "Jun 2023",
shares: "320",
value: "$15,136.59",
},
]
export function ReleaseCatalog() {
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between gap-3">
<InputGroup className="max-w-sm">
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupAddon>
<InputGroupInput placeholder="Search holdings or tickers..." />
</InputGroup>
<ToggleGroup
defaultSelectedKeys={["etfs"]}
variant="outline"
spacing={1}
>
<ToggleGroupItem id="stocks">Stocks</ToggleGroupItem>
<ToggleGroupItem id="etfs">ETFs</ToggleGroupItem>
<ToggleGroupItem id="reits">REITs</ToggleGroupItem>
</ToggleGroup>
</div>
</CardHeader>
<CardContent>
<ItemGroup>
{HOLDINGS.map((holding) => (
<Item key={holding.ticker} variant="muted">
<ItemMedia>
<div className="flex size-12 items-center justify-center rounded-lg border text-sm font-semibold">
{holding.ticker}
</div>
</ItemMedia>
<ItemContent>
<ItemTitle>{holding.name}</ItemTitle>
<ItemDescription className="text-xs tracking-wider uppercase">
{holding.shares} Shares · {holding.added}
</ItemDescription>
</ItemContent>
<div className="flex shrink-0 items-center gap-6">
<Badge variant="outline">{holding.type}</Badge>
<div className="flex flex-col items-end gap-0.5">
<span className="text-xs tracking-wider text-muted-foreground uppercase">
Value
</span>
<span className="font-medium tabular-nums">
{holding.value}
</span>
</div>
</div>
</Item>
))}
</ItemGroup>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,81 @@
"use client"
import * as React from "react"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Slider } from "@/registry/bases/aria/ui/slider"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/aria/ui/toggle-group"
export function RollerShades() {
const [position, setPosition] = React.useState([50])
const preset =
position[0] <= 10 ? "open" : position[0] >= 90 ? "closed" : "half"
return (
<Card>
<CardHeader>
<CardTitle>Living Room</CardTitle>
<CardDescription>Roller Shades</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex h-32 flex-col overflow-hidden rounded-lg border bg-muted">
<div
className="bg-muted-foreground transition-all duration-300"
style={{ height: `${position[0]}%` }}
/>
</div>
<div className="flex items-center gap-3">
<span className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
Open
</span>
<Slider
value={position}
onChange={(value) =>
setPosition(Array.isArray(value) ? [...value] : [value])
}
maxValue={100}
className="flex-1"
/>
<span className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
Close
</span>
</div>
</CardContent>
<CardFooter>
<ToggleGroup
selectedKeys={[preset]}
onSelectionChange={(value) => {
const v = Array.from(value)[0]
if (v === "open") setPosition([0])
if (v === "half") setPosition([50])
if (v === "closed") setPosition([100])
}}
variant="outline"
spacing={1}
className="w-full"
>
<ToggleGroupItem id="open" className="flex-1">
Open
</ToggleGroupItem>
<ToggleGroupItem id="half" className="flex-1">
Half
</ToggleGroupItem>
<ToggleGroupItem id="closed" className="flex-1">
Closed
</ToggleGroupItem>
</ToggleGroup>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,106 @@
"use client"
import { Label, Pie, PieChart } from "recharts"
import { Card, CardContent, CardFooter } from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import { Separator } from "@/registry/bases/aria/ui/separator"
const chartData = [
{ name: "saved", value: 24000, fill: "var(--color-saved)" },
{ name: "remaining", value: 6000, fill: "var(--color-remaining)" },
]
const chartConfig = {
saved: {
label: "Saved",
color: "var(--chart-2)",
},
remaining: {
label: "Remaining",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export function SavingsProgress() {
return (
<Card>
<CardContent>
<ChartContainer
config={chartConfig}
className="mx-auto aspect-square max-h-[220px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Pie
data={chartData}
dataKey="value"
nameKey="name"
innerRadius={70}
outerRadius={95}
strokeWidth={0}
startAngle={90}
endAngle={-270}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) - 12}
className="fill-foreground text-2xl font-bold"
>
$24,000
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 12}
className="fill-muted-foreground text-xs"
>
80% of $30,000
</tspan>
</text>
)
}
}}
/>
</Pie>
</PieChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col gap-0">
<div className="flex w-full items-center justify-between py-3">
<span className="text-sm text-muted-foreground">
Projected Finish
</span>
<span className="text-sm font-semibold">October 2024</span>
</div>
<Separator />
<div className="flex w-full items-center justify-between py-3">
<span className="text-sm text-muted-foreground">Monthly Average</span>
<span className="text-sm font-semibold tabular-nums">$1,250</span>
</div>
<Separator />
<div className="flex w-full items-center justify-between py-3">
<span className="text-sm text-muted-foreground">Top Contributor</span>
<span className="text-sm font-semibold">Auto-Transfer</span>
</div>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,154 @@
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/registry/bases/aria/ui/input-group"
import {
Item,
ItemContent,
ItemDescription,
ItemFooter,
ItemGroup,
} from "@/registry/bases/aria/ui/item"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/aria/ui/native-select"
import { Progress } from "@/registry/bases/aria/ui/progress"
export function SavingsTargets() {
return (
<div className="grid grid-cols-2 gap-(--gap)">
<Card>
<CardHeader>
<CardTitle>Savings Targets</CardTitle>
<CardDescription>Active milestones for 2024</CardDescription>
<CardAction>
<Button variant="outline" size="sm">
New Goal
</Button>
</CardAction>
</CardHeader>
<CardContent>
<ItemGroup className="gap-3">
<Item variant="muted" className="flex-col items-stretch">
<ItemContent className="gap-3">
<ItemDescription className="cn-font-heading text-xs font-medium tracking-wider text-muted-foreground uppercase">
Retirement
</ItemDescription>
<span className="text-3xl font-semibold tabular-nums">
$420,000
</span>
<Progress value={65} />
</ItemContent>
<ItemFooter>
<span className="text-sm text-muted-foreground">
65% achieved
</span>
<span className="text-sm font-medium tabular-nums">
$273,000
</span>
</ItemFooter>
</Item>
<Item variant="muted" className="flex-col items-stretch">
<ItemContent className="gap-3">
<ItemDescription className="cn-font-heading text-xs font-medium tracking-wider text-muted-foreground uppercase">
Real Estate
</ItemDescription>
<span className="text-3xl font-semibold tabular-nums">
$85,000
</span>
<Progress value={32} />
</ItemContent>
<ItemFooter>
<span className="text-sm text-muted-foreground">
32% achieved
</span>
<span className="text-sm font-medium tabular-nums">
$27,200
</span>
</ItemFooter>
</Item>
</ItemGroup>
</CardContent>
<CardFooter>
<CardDescription className="text-center">
You have not met your targets for this year.
</CardDescription>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Buy Investment</CardTitle>
</CardHeader>
<CardContent className="flex flex-1 flex-col gap-3">
<FieldGroup className="flex-1">
<Field>
<FieldLabel htmlFor="invest-amount">Amount to Invest</FieldLabel>
<InputGroup>
<InputGroupAddon>
<InputGroupText>$</InputGroupText>
</InputGroupAddon>
<InputGroupInput id="invest-amount" defaultValue="1,000.00" />
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="invest-type">Order Type</FieldLabel>
<NativeSelect id="invest-type" defaultValue="market">
<NativeSelectOption value="market">
Market Order
</NativeSelectOption>
<NativeSelectOption value="limit">
Limit Order
</NativeSelectOption>
<NativeSelectOption value="stop">Stop Order</NativeSelectOption>
</NativeSelect>
<FieldDescription>
Market orders execute at the current price.
</FieldDescription>
</Field>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Estimated Shares
</span>
<span className="text-sm font-semibold tabular-nums">1.95</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Buying Power
</span>
<span className="text-sm font-semibold tabular-nums">
$12,450.00
</span>
</div>
</div>
</FieldGroup>
</CardContent>
<CardFooter className="flex-col gap-3">
<Button className="w-full">Review Order</Button>
<CardDescription className="text-center">
Trades are typically executed within minutes during market hours.
</CardDescription>
</CardFooter>
</Card>
</div>
)
}

View File

@@ -0,0 +1,285 @@
"use client"
import { Card } from "@/registry/bases/aria/ui/card"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarSeparator,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function SidebarNav() {
return (
<div className="grid grid-cols-2 items-start gap-6">
<Card className="overflow-hidden py-0">
<SidebarProvider className="min-h-0">
<Sidebar collapsible="none" className="w-full bg-transparent">
<SidebarContent className="gap-0">
<SidebarGroup className="pb-1">
<SidebarGroupLabel>Overview</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton isActive>
<IconPlaceholder
lucide="LayoutDashboardIcon"
tabler="IconLayoutDashboard"
hugeicons="DashboardSquare01Icon"
phosphor="SquaresFourIcon"
remixicon="RiDashboardLine"
/>
Dashboard
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="ArrowLeftRightIcon"
tabler="IconArrowsLeftRight"
hugeicons="ArrowDataTransferHorizontalIcon"
phosphor="ArrowsLeftRightIcon"
remixicon="RiArrowLeftRightLine"
/>
Transactions
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="AnalyticsUpIcon"
phosphor="TrendUpIcon"
remixicon="RiLineChartLine"
/>
Investments
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="Building2Icon"
tabler="IconBuildingBank"
hugeicons="BankIcon"
phosphor="BankIcon"
remixicon="RiBankLine"
/>
Accounts
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="PieChartIcon"
tabler="IconChartPie"
hugeicons="PieChartIcon"
phosphor="ChartPieIcon"
remixicon="RiPieChartLine"
/>
Spending
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarSeparator className="w-auto!" />
<SidebarGroup className="pt-1">
<SidebarGroupLabel>Planning</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="TargetIcon"
tabler="IconTarget"
hugeicons="Target02Icon"
phosphor="TargetIcon"
remixicon="RiFocus3Line"
/>
Goals
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="WalletIcon"
tabler="IconWallet"
hugeicons="Wallet01Icon"
phosphor="WalletIcon"
remixicon="RiWalletLine"
/>
Budget
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="FileBarChartIcon"
tabler="IconReportAnalytics"
hugeicons="ChartBarLineIcon"
phosphor="ChartBarIcon"
remixicon="RiBarChartLine"
/>
Reports
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileText"
hugeicons="File02Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
Documents
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
</Card>
<Card className="overflow-hidden py-0">
<SidebarProvider className="min-h-0">
<Sidebar collapsible="none" className="w-full bg-transparent">
<SidebarContent className="gap-0">
<SidebarGroup className="pb-1">
<SidebarGroupLabel>Account</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
Profile
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton isActive>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="Notification03Icon"
phosphor="BellIcon"
remixicon="RiBellLine"
/>
Notifications
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="ShieldIcon"
tabler="IconShield"
hugeicons="ShieldIcon"
phosphor="ShieldIcon"
remixicon="RiShieldLine"
/>
Security
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="PaintbrushIcon"
tabler="IconPalette"
hugeicons="PaintBoardIcon"
phosphor="PaletteIcon"
remixicon="RiPaletteLine"
/>
Appearance
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarSeparator className="w-auto!" />
<SidebarGroup className="pt-1">
<SidebarGroupLabel>Support</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="CircleHelpIcon"
tabler="IconHelp"
hugeicons="HelpCircleIcon"
phosphor="QuestionIcon"
remixicon="RiQuestionLine"
/>
Help Center
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="MessageSquareIcon"
tabler="IconMessage"
hugeicons="Message01Icon"
phosphor="ChatIcon"
remixicon="RiChat1Line"
/>
Contact Us
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="BookOpenIcon"
tabler="IconBook"
hugeicons="BookOpen02Icon"
phosphor="BookOpenIcon"
remixicon="RiBookOpenLine"
/>
Documentation
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton>
<IconPlaceholder
lucide="ActivityIcon"
tabler="IconActivity"
hugeicons="ActivityIcon"
phosphor="ActivityIcon"
remixicon="RiPulseLine"
/>
Status
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
</Card>
</div>
)
}

View File

@@ -0,0 +1,107 @@
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function SocialLinks() {
return (
<Card>
<CardHeader>
<CardTitle>Social Links</CardTitle>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="spotify-url">Spotify Artist URL</FieldLabel>
<InputGroup>
<InputGroupAddon>
<IconPlaceholder
lucide="CirclePlusIcon"
tabler="IconCirclePlus"
hugeicons="PlusSignCircleIcon"
phosphor="PlusCircleIcon"
remixicon="RiAddCircleLine"
/>
</InputGroupAddon>
<InputGroupInput
id="spotify-url"
defaultValue="spotify.com/artist/3j...2k"
/>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="instagram-handle">Instagram Handle</FieldLabel>
<InputGroup>
<InputGroupAddon>
<IconPlaceholder
lucide="CameraIcon"
tabler="IconCamera"
hugeicons="Camera01Icon"
phosphor="CameraIcon"
remixicon="RiCameraLine"
/>
</InputGroupAddon>
<InputGroupInput
id="instagram-handle"
defaultValue="@julianduryea_music"
/>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="soundcloud-url">SoundCloud URL</FieldLabel>
<InputGroup>
<InputGroupAddon>
<IconPlaceholder
lucide="CloudIcon"
tabler="IconCloud"
hugeicons="CloudUploadIcon"
phosphor="CloudIcon"
remixicon="RiCloudLine"
/>
</InputGroupAddon>
<InputGroupInput
id="soundcloud-url"
placeholder="soundcloud.com/username"
/>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="website-url">Website</FieldLabel>
<InputGroup>
<InputGroupAddon>
<IconPlaceholder
lucide="GlobeIcon"
tabler="IconWorld"
hugeicons="Globe02Icon"
phosphor="GlobeIcon"
remixicon="RiGlobalLine"
/>
</InputGroupAddon>
<InputGroupInput
id="website-url"
placeholder="https://yoursite.com"
/>
</InputGroup>
</Field>
</FieldGroup>
</CardContent>
<CardFooter className="justify-end gap-2 style-sera:justify-center">
<Button variant="secondary" className="style-sera:flex-1">
Discard
</Button>
<Button className="style-sera:flex-1">Save Changes</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,143 @@
"use client"
import * as React from "react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from "@/registry/bases/aria/ui/combobox"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Separator } from "@/registry/bases/aria/ui/separator"
const TICKERS = ["VOO", "VIG", "AAPL", "MSFT", "GOOGL", "AMZN", "TSLA"]
const CHART_DATA: Record<string, { month: string; price: number }[]> = {
VOO: [
{ month: "Jan", price: 412 },
{ month: "Feb", price: 438 },
{ month: "Mar", price: 395 },
{ month: "Apr", price: 450 },
{ month: "May", price: 420 },
{ month: "Jun", price: 462 },
],
AAPL: [
{ month: "Jan", price: 185 },
{ month: "Feb", price: 210 },
{ month: "Mar", price: 172 },
{ month: "Apr", price: 198 },
{ month: "May", price: 178 },
{ month: "Jun", price: 215 },
],
}
const DEFAULT_DATA = [
{ month: "Jan", price: 100 },
{ month: "Feb", price: 118 },
{ month: "Mar", price: 95 },
{ month: "Apr", price: 125 },
{ month: "May", price: 108 },
{ month: "Jun", price: 130 },
]
const chartConfig = {
price: {
label: "Price",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export function StockPerformance() {
const [ticker, setTicker] = React.useState("VOO")
const data = CHART_DATA[ticker] ?? DEFAULT_DATA
return (
<Card>
<CardHeader>
<CardTitle>Stock Performance</CardTitle>
<CardDescription>6-month price history.</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<FieldGroup>
<Field>
<FieldLabel htmlFor="ticker-select">Ticker</FieldLabel>
<Combobox
selectedKey={ticker}
onSelectionChange={(key) => {
if (key !== null) setTicker(String(key))
}}
>
<ComboboxInput
id="ticker-select"
placeholder="Search ticker..."
/>
<ComboboxContent>
<ComboboxEmpty>No tickers found.</ComboboxEmpty>
<ComboboxList>
{TICKERS.map((item) => (
<ComboboxItem key={item} id={item}>
{item}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxContent>
</Combobox>
</Field>
</FieldGroup>
<Separator className="style-sera:hidden" />
<ChartContainer config={chartConfig} className="h-[200px] w-full">
<AreaChart
accessibilityLayer
data={data}
margin={{ left: 0, right: 0, top: 8, bottom: 0 }}
>
<defs>
<linearGradient id="fillPrice" x1="0" y1="0" x2="0" y2="1">
<stop
offset="0%"
stopColor="var(--color-price)"
stopOpacity={0.3}
/>
<stop
offset="100%"
stopColor="var(--color-price)"
stopOpacity={0.05}
/>
</linearGradient>
</defs>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Area
type="monotone"
dataKey="price"
stroke="var(--color-price)"
strokeWidth={2}
fill="url(#fillPrice)"
/>
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,35 @@
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import { Spinner } from "@/registry/bases/aria/ui/spinner"
export function SyncingState() {
return (
<Card>
<CardContent className="p-0">
<Empty className="p-4">
<EmptyHeader>
<EmptyMedia variant="icon">
<Spinner />
</EmptyMedia>
<EmptyTitle>Syncing your accounts</EmptyTitle>
<EmptyDescription>
We&apos;re pulling in your latest transactions. This usually takes
a few seconds.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline">Cancel</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,138 @@
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/registry/bases/aria/ui/input-group"
import { Item, ItemContent } from "@/registry/bases/aria/ui/item"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Separator } from "@/registry/bases/aria/ui/separator"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const FROM_ACCOUNTS = [
{ label: "Main Checking (··8402) — $12,450.00", value: "checking" },
{ label: "Business (··7731) — $8,920.00", value: "business" },
]
const TO_ACCOUNTS = [
{ label: "High Yield Savings (··1192) — $42,100.00", value: "savings" },
{ label: "Investment (··3349) — $18,200.00", value: "investment" },
]
export function TransferFunds() {
return (
<Card>
<CardHeader>
<CardTitle>Transfer Funds</CardTitle>
<CardDescription>
Move money between your connected accounts.
</CardDescription>
<CardAction>
<Button variant="ghost" size="icon-sm" className="bg-muted">
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
/>
</Button>
</CardAction>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="transfer-amount">
Amount to Transfer
</FieldLabel>
<InputGroup>
<InputGroupAddon>
<InputGroupText>$</InputGroupText>
</InputGroupAddon>
<InputGroupInput id="transfer-amount" defaultValue="1,200.00" />
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="from-account">From Account</FieldLabel>
<Select defaultValue="checking">
<SelectTrigger id="from-account" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{FROM_ACCOUNTS.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="to-account">To Account</FieldLabel>
<Select defaultValue="savings">
<SelectTrigger id="to-account" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{TO_ACCOUNTS.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Item variant="muted" className="flex-col items-stretch">
<ItemContent className="gap-3">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Estimated arrival
</span>
<span className="text-sm font-medium">Today, Apr 14</span>
</div>
<Separator />
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Transaction fee
</span>
<span className="text-sm font-medium tabular-nums">$0.00</span>
</div>
<Separator />
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Total amount</span>
<span className="text-sm font-semibold tabular-nums">
$1,200.00
</span>
</div>
</ItemContent>
</Item>
</FieldGroup>
</CardContent>
<CardFooter>
<Button className="w-full">Confirm Transfer</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,75 @@
"use client"
import * as React from "react"
import {
getLocalTimeZone,
today,
type CalendarDate,
} from "@internationalized/date"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Calendar } from "@/registry/bases/aria/ui/calendar"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Item,
ItemContent,
ItemDescription,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
export function UpcomingPayments() {
const [date, setDate] = React.useState<CalendarDate | undefined>(
today(getLocalTimeZone())
)
return (
<Card>
<CardHeader>
<CardTitle>Upcoming Payments</CardTitle>
<CardDescription>
Select a date to view scheduled payments.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Item variant="outline" className="justify-center">
<Calendar
value={date}
onChange={setDate}
className="w-full [--cell-size:--spacing(8)] md:[--cell-size:--spacing(10)] style-sera:md:[--cell-size:--spacing(9)]"
/>
</Item>
<ItemGroup className="w-full">
<Item variant="muted">
<ItemContent>
<ItemTitle>Netflix Subscription</ItemTitle>
<ItemDescription>Apr 15, 2024</ItemDescription>
</ItemContent>
<Badge variant="secondary">$19.99</Badge>
</Item>
<Item variant="muted">
<ItemContent>
<ItemTitle>Rent Payment</ItemTitle>
<ItemDescription>Apr 1, 2024</ItemDescription>
</ItemContent>
<Badge variant="secondary">$2,400.00</Badge>
</Item>
<Item variant="muted">
<ItemContent>
<ItemTitle>Auto Insurance</ItemTitle>
<ItemDescription>Apr 22, 2024</ItemDescription>
</ItemContent>
<Badge variant="secondary">$186.00</Badge>
</Item>
</ItemGroup>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,98 @@
import { AccountAccess } from "@/registry/bases/aria/blocks/preview-02/cards/account-access"
import { CardOverview } from "@/registry/bases/aria/blocks/preview-02/cards/card-overview"
import { ClaimableBalance } from "@/registry/bases/aria/blocks/preview-02/cards/claimable-balance"
import { ContributionHistory } from "@/registry/bases/aria/blocks/preview-02/cards/contribution-history"
import { CoverArt } from "@/registry/bases/aria/blocks/preview-02/cards/cover-art"
import { DividendIncome } from "@/registry/bases/aria/blocks/preview-02/cards/dividend-income"
import { EmptyConnectBank } from "@/registry/bases/aria/blocks/preview-02/cards/empty-connect-bank"
import { EmptyDistributeTrack } from "@/registry/bases/aria/blocks/preview-02/cards/empty-distribute-track"
import { EmptyExploreCatalog } from "@/registry/bases/aria/blocks/preview-02/cards/empty-explore-catalog"
import { Faq } from "@/registry/bases/aria/blocks/preview-02/cards/faq"
import { FrontDoor } from "@/registry/bases/aria/blocks/preview-02/cards/front-door"
import { IndexInvesting } from "@/registry/bases/aria/blocks/preview-02/cards/index-investing"
import { KitchenIsland } from "@/registry/bases/aria/blocks/preview-02/cards/kitchen-island"
import { LoadingCard } from "@/registry/bases/aria/blocks/preview-02/cards/loading-card"
import { NewMilestone } from "@/registry/bases/aria/blocks/preview-02/cards/new-milestone"
import { NotificationSettings } from "@/registry/bases/aria/blocks/preview-02/cards/notification-settings"
import { Payments } from "@/registry/bases/aria/blocks/preview-02/cards/payments"
import { PayoutThreshold } from "@/registry/bases/aria/blocks/preview-02/cards/payout-threshold"
import { PowerUsage } from "@/registry/bases/aria/blocks/preview-02/cards/power-usage"
import { Preferences } from "@/registry/bases/aria/blocks/preview-02/cards/preferences"
import { QrConnect } from "@/registry/bases/aria/blocks/preview-02/cards/qr-connect"
import { ReceivingMethod } from "@/registry/bases/aria/blocks/preview-02/cards/receiving-method"
import { RecentTransactions } from "@/registry/bases/aria/blocks/preview-02/cards/recent-transactions"
import { ReleaseCatalog } from "@/registry/bases/aria/blocks/preview-02/cards/release-catalog"
import { RollerShades } from "@/registry/bases/aria/blocks/preview-02/cards/roller-shades"
import { SavingsProgress } from "@/registry/bases/aria/blocks/preview-02/cards/savings-progress"
import { SavingsTargets } from "@/registry/bases/aria/blocks/preview-02/cards/savings-targets"
import { SidebarNav } from "@/registry/bases/aria/blocks/preview-02/cards/sidebar-nav"
import { SocialLinks } from "@/registry/bases/aria/blocks/preview-02/cards/social-links"
import { StockPerformance } from "@/registry/bases/aria/blocks/preview-02/cards/stock-performance"
import { SyncingState } from "@/registry/bases/aria/blocks/preview-02/cards/syncing-state"
import { TransferFunds } from "@/registry/bases/aria/blocks/preview-02/cards/transfer-funds"
import { UpcomingPayments } from "@/registry/bases/aria/blocks/preview-02/cards/upcoming-payments"
export default function Preview02Example() {
return (
<div className="overflow-x-auto overflow-y-hidden bg-muted contain-[paint] [--gap:--spacing(4)] 3xl:[--gap:--spacing(12)] md:[--gap:--spacing(10)] dark:bg-background style-lyra:md:[--gap:--spacing(6)] style-mira:md:[--gap:--spacing(6)]">
<div className="flex w-full min-w-max justify-center">
<div
className="grid w-[2400px] grid-cols-7 items-start gap-(--gap) bg-muted p-(--gap) md:w-[3000px] dark:bg-background style-lyra:md:w-[2600px] style-mira:md:w-[2600px] *:[div]:gap-(--gap)"
data-slot="capture-target"
>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<ContributionHistory />
<EmptyDistributeTrack />
<QrConnect />
<DividendIncome />
<IndexInvesting />
<SyncingState />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<PayoutThreshold />
<ClaimableBalance />
<Preferences />
<SavingsProgress />
<KitchenIsland />
</div>
<div className="col-span-2 flex flex-col p-1 [contain-intrinsic-size:760px_1200px] [content-visibility:auto]">
<SavingsTargets />
<RecentTransactions />
<div className="grid grid-cols-2 items-start gap-(--gap)">
<div className="flex flex-col gap-(--gap)">
<SidebarNav />
<Faq />
</div>
<div className="flex flex-col gap-(--gap)">
<Payments />
<FrontDoor />
</div>
</div>
<ReleaseCatalog />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<AccountAccess />
<CardOverview />
<TransferFunds />
<CoverArt />
<LoadingCard />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<ReceivingMethod />
<PowerUsage />
<EmptyConnectBank />
<UpcomingPayments />
<RollerShades />
</div>
<div className="flex flex-col p-1 [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<StockPerformance />
<EmptyExploreCatalog />
<NewMilestone />
<SocialLinks />
<NotificationSettings />
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,3 @@
export default function Preview03() {
return <div>Preview 03</div>
}

View File

@@ -0,0 +1,105 @@
"use client"
import { Alert, AlertDescription } from "@/registry/bases/aria/ui/alert"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Item,
ItemContent,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
// Agent feature descriptions.
const agentFeatures = [
{
id: "code-reviews",
content: (
<>
<strong>Code reviews</strong> with full codebase context to catch{" "}
<strong>hard-to-find</strong> bugs.
</>
),
},
{
id: "code-suggestions",
content: (
<>
<strong>Code suggestions</strong> validated in sandboxes before you
merge.
</>
),
},
{
id: "root-cause",
content: (
<>
<strong>Root-cause analysis</strong> for production issues with
deployment context.{" "}
<Badge variant="secondary" className="bg-chart-1 text-chart-5">
Requires Observability Plus
</Badge>
</>
),
},
]
export function ActivateAgentDialog() {
return (
<Card>
<CardHeader>
<CardTitle>Ship faster & safer with Vercel Agent</CardTitle>
<CardDescription>
Your use is subject to Vercel&apos;s{" "}
<a href="#">Public Beta Agreement</a> and{" "}
<a href="#">AI Product Terms</a>.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<ItemGroup className="gap-0">
{agentFeatures.map((feature) => (
<Item key={feature.id} size="xs" className="px-0">
<ItemMedia variant="icon" className="self-start">
<IconPlaceholder
lucide="CheckCircle2Icon"
tabler="IconCircleCheckFilled"
hugeicons="CheckmarkCircle02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
className="size-5 fill-primary text-primary-foreground"
/>
</ItemMedia>
<ItemContent>
<ItemTitle className="inline leading-relaxed font-normal text-muted-foreground *:[strong]:font-medium *:[strong]:text-foreground">
{feature.content}
</ItemTitle>
</ItemContent>
</Item>
))}
</ItemGroup>
<Alert>
<AlertDescription>
Pro teams get $100 in Vercel Agent trial credit for 2 weeks after
activation.
</AlertDescription>
</Alert>
</CardContent>
<CardFooter className="justify-end gap-2">
<Button variant="outline" className="style-sera:hidden">
Cancel
</Button>
<Button className="style-sera:w-full">Enable with $100 credits</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,77 @@
"use client"
import { Area, AreaChart } from "recharts"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
// Monthly visitor data for the area chart.
const chartData = [
{ month: "January", visitors: 186 },
{ month: "February", visitors: 305 },
{ month: "March", visitors: 237 },
{ month: "April", visitors: 73 },
{ month: "May", visitors: 209 },
{ month: "June", visitors: 214 },
]
// Chart configuration for the visitors dataset.
const chartConfig = {
visitors: {
label: "Visitors",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export function AnalyticsCard() {
return (
<Card className="mx-auto w-full max-w-sm data-[size=sm]:pb-0" size="sm">
<CardHeader>
<CardTitle>Analytics</CardTitle>
<CardDescription>
418.2K Visitors <Badge>+10%</Badge>
</CardDescription>
<CardAction>
<Button variant="outline" size="sm">
View Analytics
</Button>
</CardAction>
</CardHeader>
<ChartContainer config={chartConfig} className="aspect-[1/0.35]">
<AreaChart
accessibilityLayer
data={chartData}
margin={{
left: 0,
right: 0,
}}
>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" hideLabel />}
/>
<Area
dataKey="visitors"
type="linear"
fill="var(--color-visitors)"
fillOpacity={0.4}
stroke="var(--color-visitors)"
/>
</AreaChart>
</ChartContainer>
</Card>
)
}

View File

@@ -0,0 +1,32 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
export function AnomalyAlert() {
return (
<Card>
<CardContent>
<Empty className="h-48">
<EmptyHeader>
<EmptyTitle>Get alerted for anomalies</EmptyTitle>
<EmptyDescription>
Automatically monitor your projects for anomalies and get
notified.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Upgrade to Observability Plus</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,119 @@
"use client"
import * as React from "react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Combobox,
ComboboxChip,
ComboboxChipList,
ComboboxChips,
ComboboxChipsInput,
ComboboxContent,
ComboboxEmpty,
ComboboxItem,
ComboboxList,
} from "@/registry/bases/aria/ui/combobox"
import { Tooltip, TooltipTrigger } from "@/registry/bases/aria/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
// Users available for assignment.
const users = [
"shadcn",
"maxleiter",
"evilrabbit",
"pranathip",
"jorgezreik",
"shuding",
"rauchg",
]
export function AssignIssue() {
return (
<Card className="w-full max-w-sm" size="sm">
<CardHeader className="border-b">
<CardTitle className="text-sm">Assign Issue</CardTitle>
<CardDescription className="text-sm">
Select users to assign to this issue.
</CardDescription>
<CardAction>
<TooltipTrigger>
<Button variant="outline" size="icon-xs">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</Button>
<Tooltip>Add user</Tooltip>
</TooltipTrigger>
</CardAction>
</CardHeader>
<CardContent>
<Combobox
selectionMode="multiple"
defaultValue={[users[0]]}
allowsEmptyCollection
>
<ComboboxChips>
<ComboboxChipList<{ username: string }>>
{(value: { username: string }) => (
<ComboboxChip id={value.username}>
<Avatar className="size-4">
<AvatarImage
src={`https://github.com/${value.username}.png`}
alt={value.username}
/>
<AvatarFallback>{value.username.charAt(0)}</AvatarFallback>
</Avatar>
{value.username}
</ComboboxChip>
)}
</ComboboxChipList>
<ComboboxChipsInput placeholder="Select a item..." />
</ComboboxChips>
<ComboboxContent>
<ComboboxList
renderEmptyState={() => (
<ComboboxEmpty>No users found.</ComboboxEmpty>
)}
>
{users.map((username) => (
<ComboboxItem
key={username}
id={username}
value={{ username }}
textValue={username}
>
<Avatar className="size-5">
<AvatarImage
src={`https://github.com/${username}.png`}
alt={username}
/>
<AvatarFallback>{username.charAt(0)}</AvatarFallback>
</Avatar>
{username}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxContent>
</Combobox>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,131 @@
"use client"
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params"
const barChartData = [
{ month: "January", desktop: 186, mobile: 80 },
{ month: "February", desktop: 305, mobile: 200 },
{ month: "March", desktop: 237, mobile: 120 },
{ month: "April", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "June", desktop: 214, mobile: 140 },
]
const barChartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig
const desktopTotal = barChartData.reduce((sum, item) => sum + item.desktop, 0)
const mobileTotal = barChartData.reduce((sum, item) => sum + item.mobile, 0)
const desktopDelta = Math.round(
((desktopTotal - mobileTotal) / mobileTotal) * 100
)
const desktopDeltaPrefix = desktopDelta > 0 ? "+" : ""
export function BarChartCard() {
const [params] = useDesignSystemSearchParams()
const isRounded = !["lyra", "sera"].includes(params.style)
return (
<Card>
<CardHeader>
<CardTitle className="text-lg">Traffic channels</CardTitle>
<CardDescription className="line-clamp-2 text-sm leading-snug">
Monthly desktop and mobile traffic for the last six months, compare
volume and mix across platforms and devices at a glance.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4 pt-0">
<ChartContainer
config={barChartConfig}
className="max-h-[180px] w-full"
>
<BarChart
accessibilityLayer
data={barChartData}
margin={{ left: 0, right: 0, top: 8, bottom: 0 }}
>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<XAxis
dataKey="month"
tickLine={false}
tickMargin={8}
axisLine={false}
tickFormatter={(value) => String(value).slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="dashed" />}
/>
<ChartLegend content={<ChartLegendContent />} />
<Bar
dataKey="desktop"
fill="var(--color-desktop)"
radius={isRounded ? [6, 6, 0, 0] : 0}
/>
<Bar
dataKey="mobile"
fill="var(--color-mobile)"
radius={isRounded ? [6, 6, 0, 0] : 0}
/>
</BarChart>
</ChartContainer>
<div className="grid w-full grid-cols-3 divide-x divide-border/60">
<div className="px-2 text-center">
<div className="text-[0.65rem] text-muted-foreground uppercase">
Desktop
</div>
<div className="text-sm font-medium tabular-nums">
{desktopTotal.toLocaleString()}
</div>
</div>
<div className="px-2 text-center">
<div className="text-[0.65rem] text-muted-foreground uppercase">
Mobile
</div>
<div className="text-sm font-medium tabular-nums">
{mobileTotal.toLocaleString()}
</div>
</div>
<div className="px-2 text-center">
<div className="text-[0.65rem] text-muted-foreground uppercase">
Mix Delta
</div>
<div className="text-sm font-medium tabular-nums">
{desktopDeltaPrefix}
{desktopDelta}%
</div>
</div>
</div>
</CardContent>
<CardFooter>
<Button className="w-full">View report</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,513 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
type AgentState =
| "connecting"
| "initializing"
| "listening"
| "speaking"
| "thinking"
type AnimationState = AgentState | undefined
interface AudioAnalyserOptions {
fftSize?: number
smoothingTimeConstant?: number
minDecibels?: number
maxDecibels?: number
}
interface MultiBandVolumeOptions {
bands?: number
loPass?: number
hiPass?: number
updateInterval?: number
analyserOptions?: AudioAnalyserOptions
}
const multibandDefaults: MultiBandVolumeOptions = {
bands: 5,
loPass: 100,
hiPass: 600,
updateInterval: 32,
analyserOptions: { fftSize: 2048 },
}
const normalizeDb = (value: number) => {
if (value === -Infinity) {
return 0
}
const minDb = -100
const maxDb = -10
const db = 1 - (Math.max(minDb, Math.min(maxDb, value)) * -1) / 100
return Math.sqrt(db)
}
function createAudioAnalyser(
mediaStream: MediaStream,
options: AudioAnalyserOptions = {}
) {
const audioContext = new (window.AudioContext ||
(window as unknown as { webkitAudioContext: typeof AudioContext })
.webkitAudioContext)()
const source = audioContext.createMediaStreamSource(mediaStream)
const analyser = audioContext.createAnalyser()
if (options.fftSize) {
analyser.fftSize = options.fftSize
}
if (options.smoothingTimeConstant !== undefined) {
analyser.smoothingTimeConstant = options.smoothingTimeConstant
}
if (options.minDecibels !== undefined) {
analyser.minDecibels = options.minDecibels
}
if (options.maxDecibels !== undefined) {
analyser.maxDecibels = options.maxDecibels
}
source.connect(analyser)
const cleanup = () => {
source.disconnect()
audioContext.close()
}
return { analyser, audioContext, cleanup }
}
function useMultibandVolume(
mediaStream?: MediaStream | null,
options: MultiBandVolumeOptions = {}
) {
const bands = options.bands ?? multibandDefaults.bands ?? 5
const loPass = options.loPass ?? multibandDefaults.loPass ?? 100
const hiPass = options.hiPass ?? multibandDefaults.hiPass ?? 600
const updateInterval =
options.updateInterval ?? multibandDefaults.updateInterval ?? 32
const fftSize =
options.analyserOptions?.fftSize ??
multibandDefaults.analyserOptions?.fftSize
const smoothingTimeConstant =
options.analyserOptions?.smoothingTimeConstant ??
multibandDefaults.analyserOptions?.smoothingTimeConstant
const minDecibels =
options.analyserOptions?.minDecibels ??
multibandDefaults.analyserOptions?.minDecibels
const maxDecibels =
options.analyserOptions?.maxDecibels ??
multibandDefaults.analyserOptions?.maxDecibels
const [frequencyBands, setFrequencyBands] = React.useState<number[]>(() =>
new Array(bands).fill(0)
)
const bandsRef = React.useRef<number[]>(new Array(bands).fill(0))
const frameId = React.useRef<number | undefined>(undefined)
React.useEffect(() => {
if (!mediaStream) {
const emptyBands = new Array(bands).fill(0)
bandsRef.current = emptyBands
setFrequencyBands(emptyBands)
return
}
const { analyser, cleanup } = createAudioAnalyser(mediaStream, {
fftSize,
smoothingTimeConstant,
minDecibels,
maxDecibels,
})
const bufferLength = analyser.frequencyBinCount
const dataArray = new Float32Array(bufferLength)
const sliceStart = loPass
const sliceEnd = hiPass
const sliceLength = sliceEnd - sliceStart
const chunkSize = Math.ceil(sliceLength / bands)
let lastUpdate = 0
const updateVolume = (timestamp: number) => {
if (timestamp - lastUpdate >= updateInterval) {
analyser.getFloatFrequencyData(dataArray)
const chunks = new Array(bands)
for (let i = 0; i < bands; i++) {
let sum = 0
let count = 0
const startIdx = sliceStart + i * chunkSize
const endIdx = Math.min(sliceStart + (i + 1) * chunkSize, sliceEnd)
for (let j = startIdx; j < endIdx; j++) {
sum += normalizeDb(dataArray[j])
count++
}
chunks[i] = count > 0 ? sum / count : 0
}
let hasChanged = false
for (let i = 0; i < chunks.length; i++) {
if (Math.abs(chunks[i] - bandsRef.current[i]) > 0.01) {
hasChanged = true
break
}
}
if (hasChanged) {
bandsRef.current = chunks
setFrequencyBands(chunks)
}
lastUpdate = timestamp
}
frameId.current = requestAnimationFrame(updateVolume)
}
frameId.current = requestAnimationFrame(updateVolume)
return () => {
cleanup()
if (frameId.current) {
cancelAnimationFrame(frameId.current)
}
}
}, [
bands,
fftSize,
hiPass,
loPass,
maxDecibels,
mediaStream,
minDecibels,
smoothingTimeConstant,
updateInterval,
])
return frequencyBands
}
const generateConnectingSequenceBar = (columns: number): number[][] => {
const seq = []
for (let x = 0; x < columns; x++) {
seq.push([x, columns - 1 - x])
}
return seq
}
const generateListeningSequenceBar = (columns: number): number[][] => {
const center = Math.floor(columns / 2)
const noIndex = -1
return [[center], [noIndex]]
}
const useBarAnimator = (
state: AnimationState,
columns: number,
interval: number
): number[] => {
const indexRef = React.useRef(0)
const [currentFrame, setCurrentFrame] = React.useState<number[]>([])
const animationFrameId = React.useRef<number | null>(null)
const sequence = React.useMemo(() => {
if (state === "thinking" || state === "listening") {
return generateListeningSequenceBar(columns)
} else if (state === "connecting" || state === "initializing") {
return generateConnectingSequenceBar(columns)
} else if (state === undefined || state === "speaking") {
return [new Array(columns).fill(0).map((_, idx) => idx)]
} else {
return [[]]
}
}, [state, columns])
React.useEffect(() => {
indexRef.current = 0
setCurrentFrame(sequence[0] || [])
}, [sequence])
React.useEffect(() => {
let startTime = performance.now()
const animate = (time: DOMHighResTimeStamp) => {
const timeElapsed = time - startTime
if (timeElapsed >= interval) {
indexRef.current = (indexRef.current + 1) % sequence.length
setCurrentFrame(sequence[indexRef.current] || [])
startTime = time
}
animationFrameId.current = requestAnimationFrame(animate)
}
animationFrameId.current = requestAnimationFrame(animate)
return () => {
if (animationFrameId.current !== null) {
cancelAnimationFrame(animationFrameId.current)
}
}
}, [interval, sequence])
return currentFrame
}
// Memoized Bar component to prevent unnecessary re-renders.
const Bar = React.memo<{
heightPct: number
isHighlighted: boolean
state?: AgentState
}>(({ heightPct, isHighlighted, state }) => (
<div
data-highlighted={isHighlighted}
className={cn(
"max-w-[12px] min-w-[8px] flex-1 transition-all duration-150",
"rounded-full",
"bg-border data-[highlighted=true]:bg-chart-3",
state === "speaking" && "bg-chart-3",
state === "thinking" && isHighlighted && "animate-pulse"
)}
style={{
height: `${heightPct}%`,
animationDuration: state === "thinking" ? "300ms" : undefined,
}}
/>
))
Bar.displayName = "Bar"
const BarVisualizerComponent = React.forwardRef<
HTMLDivElement,
{
state?: AgentState
barCount?: number
mediaStream?: MediaStream | null
minHeight?: number
maxHeight?: number
demo?: boolean
centerAlign?: boolean
} & React.HTMLAttributes<HTMLDivElement>
>(
(
{
state,
barCount = 15,
mediaStream,
minHeight = 20,
maxHeight = 100,
demo = false,
centerAlign = false,
className,
style,
...props
},
ref
) => {
const realVolumeBands = useMultibandVolume(mediaStream, {
bands: barCount,
loPass: 100,
hiPass: 200,
})
const fakeVolumeBandsRef = React.useRef<number[]>(
new Array(barCount).fill(0.2)
)
const [fakeVolumeBands, setFakeVolumeBands] = React.useState<number[]>(() =>
new Array(barCount).fill(0.2)
)
const fakeAnimationRef = React.useRef<number | undefined>(undefined)
React.useEffect(() => {
if (!demo) {
return
}
if (state !== "speaking" && state !== "listening") {
const bands = new Array(barCount).fill(0.2)
fakeVolumeBandsRef.current = bands
setFakeVolumeBands(bands)
return
}
let lastUpdate = 0
const updateInterval = 50
const startTime = Date.now() / 1000
const updateFakeVolume = (timestamp: number) => {
if (timestamp - lastUpdate >= updateInterval) {
const time = Date.now() / 1000 - startTime
const newBands = new Array(barCount)
for (let i = 0; i < barCount; i++) {
const waveOffset = i * 0.5
const baseVolume = Math.sin(time * 2 + waveOffset) * 0.3 + 0.5
const randomNoise = Math.random() * 0.2
newBands[i] = Math.max(0.1, Math.min(1, baseVolume + randomNoise))
}
let hasChanged = false
for (let i = 0; i < barCount; i++) {
if (Math.abs(newBands[i] - fakeVolumeBandsRef.current[i]) > 0.05) {
hasChanged = true
break
}
}
if (hasChanged) {
fakeVolumeBandsRef.current = newBands
setFakeVolumeBands(newBands)
}
lastUpdate = timestamp
}
fakeAnimationRef.current = requestAnimationFrame(updateFakeVolume)
}
fakeAnimationRef.current = requestAnimationFrame(updateFakeVolume)
return () => {
if (fakeAnimationRef.current) {
cancelAnimationFrame(fakeAnimationRef.current)
}
}
}, [demo, state, barCount])
const volumeBands = React.useMemo(
() => (demo ? fakeVolumeBands : realVolumeBands),
[demo, fakeVolumeBands, realVolumeBands]
)
const highlightedIndices = useBarAnimator(
state,
barCount,
state === "connecting"
? 2000 / barCount
: state === "thinking"
? 150
: state === "listening"
? 500
: 1000
)
return (
<div
ref={ref}
data-state={state}
className={cn(
"relative flex justify-center gap-1.5",
centerAlign ? "items-center" : "items-end",
"h-32 w-full overflow-hidden rounded-lg bg-muted p-4",
className
)}
style={{
...style,
}}
{...props}
>
{volumeBands.map((volume, index) => {
const heightPct = Math.min(
maxHeight,
Math.max(minHeight, volume * 100 + 5)
)
const isHighlighted = highlightedIndices?.includes(index) ?? false
return (
<Bar
key={index}
heightPct={heightPct}
isHighlighted={isHighlighted}
state={state}
/>
)
})}
</div>
)
}
)
BarVisualizerComponent.displayName = "BarVisualizerComponent"
const BarVisualizer = React.memo(
BarVisualizerComponent,
(prevProps, nextProps) => {
return (
prevProps.state === nextProps.state &&
prevProps.barCount === nextProps.barCount &&
prevProps.mediaStream === nextProps.mediaStream &&
prevProps.minHeight === nextProps.minHeight &&
prevProps.maxHeight === nextProps.maxHeight &&
prevProps.demo === nextProps.demo &&
prevProps.centerAlign === nextProps.centerAlign &&
prevProps.className === nextProps.className &&
JSON.stringify(prevProps.style) === JSON.stringify(nextProps.style)
)
}
)
BarVisualizer.displayName = "BarVisualizer"
export function BarVisualizerCard() {
const [state, setState] = React.useState<AgentState>("speaking")
return (
<Card>
<CardHeader>
<CardTitle>Audio Frequency Visualizer</CardTitle>
<CardDescription>
Real-time frequency band visualization with animated state transitions
</CardDescription>
</CardHeader>
<CardContent>
<BarVisualizer
state={state}
demo={true}
barCount={20}
minHeight={15}
maxHeight={90}
className="h-40 max-w-full"
/>
</CardContent>
<CardFooter className="gap-2">
<Button
size="sm"
variant={state === "connecting" ? "default" : "outline"}
onClick={() => setState("connecting")}
>
Connecting
</Button>
<Button
size="sm"
variant={state === "listening" ? "default" : "outline"}
onClick={() => setState("listening")}
>
Listening
</Button>
<Button
size="sm"
variant={state === "speaking" ? "default" : "outline"}
onClick={() => setState("speaking")}
>
Speaking
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,56 @@
"use client"
import {
Alert,
AlertDescription,
AlertTitle,
} from "@/registry/bases/aria/ui/alert"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/aria/ui/toggle-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function BookAppointment() {
return (
<Card>
<CardHeader>
<CardTitle>Book Appointment</CardTitle>
<CardDescription>Dr. Sarah Chen · Cardiology</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<FieldGroup>
<Field>
<FieldLabel>Available on March 18, 2026</FieldLabel>
<ToggleGroup spacing={2} defaultSelectedKeys={["slot-0"]}>
{["9:00 AM", "10:30 AM", "11:00 AM", "1:30 PM"].map(
(time, index) => (
<ToggleGroupItem key={time} id={`slot-${index}`}>
{time}
</ToggleGroupItem>
)
)}
</ToggleGroup>
</Field>
</FieldGroup>
<Alert>
<AlertTitle>New patient?</AlertTitle>
<AlertDescription>Please arrive 15 minutes early.</AlertDescription>
</Alert>
</CardContent>
<CardFooter>
<Button className="w-full">Book Appointment</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,379 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import { Separator } from "@/registry/bases/aria/ui/separator"
import { Spinner } from "@/registry/bases/aria/ui/spinner"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/bases/aria/ui/tabs"
import { Tooltip, TooltipTrigger } from "@/registry/bases/aria/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function CodespacesCard() {
const [isCreatingCodespace, setIsCreatingCodespace] = React.useState(false)
return (
<Card>
<CardContent>
<Tabs defaultSelectedKey="codespaces">
<TabsList className="w-full">
<TabsTrigger id="codespaces">Codespaces</TabsTrigger>
<TabsTrigger id="local">Local</TabsTrigger>
</TabsList>
<TabsContent id="codespaces">
<Item size="sm" className="px-1 pt-2">
<ItemContent>
<ItemTitle>Codespaces</ItemTitle>
<ItemDescription>Your workspaces in the cloud</ItemDescription>
</ItemContent>
<ItemActions>
<TooltipTrigger>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</Button>
<Tooltip placement="bottom">
Create a codespace on main
</Tooltip>
</TooltipTrigger>
<DropdownMenuTrigger>
<Button variant="ghost" size="icon-sm">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
</Button>
<DropdownMenu placement="bottom end" className="w-56">
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
New with options...
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ContainerIcon"
tabler="IconBox"
hugeicons="CubeIcon"
phosphor="CubeIcon"
remixicon="RiBox1Line"
/>
Configure container
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ZapIcon"
tabler="IconBolt"
hugeicons="ZapIcon"
phosphor="LightningIcon"
remixicon="RiFlashlightLine"
/>
Set up prebuilds
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="ServerIcon"
tabler="IconServer"
hugeicons="ServerStackIcon"
phosphor="HardDrivesIcon"
remixicon="RiHardDriveLine"
/>
Manage codespaces
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare2"
hugeicons="Share03Icon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
Share deep link
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
What are codespaces?
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</ItemActions>
</Item>
<Separator className="-mx-2 my-2 w-auto!" />
<Empty className="p-4">
<EmptyHeader>
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="ServerIcon"
tabler="IconServer"
hugeicons="ServerStackIcon"
phosphor="HardDrivesIcon"
remixicon="RiHardDriveLine"
/>
</EmptyMedia>
<EmptyTitle>No codespaces</EmptyTitle>
<EmptyDescription>
You don&apos;t have any codespaces with this repository
checked out
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button
size="sm"
onClick={() => {
setIsCreatingCodespace(true)
setTimeout(() => {
setIsCreatingCodespace(false)
}, 2000)
}}
isDisabled={isCreatingCodespace}
>
{isCreatingCodespace ? (
<Spinner data-icon="inline-start" />
) : (
""
)}
Create Codespace
</Button>
<a
href="#learn-more"
className="text-xs text-muted-foreground underline underline-offset-4"
>
Learn more about codespaces
</a>
</EmptyContent>
</Empty>
<Separator className="-mx-2 my-2 w-auto!" />
<div className="p-1.5 text-xs text-muted-foreground">
Codespace usage for this repository is paid for by{" "}
<span className="font-medium">shadcn</span>.
</div>
</TabsContent>
<TabsContent id="local">
<Item size="sm" className="hidden p-0">
<ItemContent>
<ItemTitle className="gap-2">
<IconPlaceholder
lucide="TerminalIcon"
tabler="IconTerminal"
hugeicons="ComputerTerminal01Icon"
phosphor="TerminalIcon"
remixicon="RiTerminalBoxLine"
className="size-4"
/>
Clone
</ItemTitle>
</ItemContent>
<ItemActions>
<TooltipTrigger>
<Button variant="ghost" size="icon">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</Button>
<Tooltip placement="left">
Which remote URL should I use?
</Tooltip>
</TooltipTrigger>
</ItemActions>
</Item>
<Tabs defaultSelectedKey="https">
<TabsList
variant="line"
className="w-full justify-start border-b *:[button]:flex-0"
>
<TabsTrigger id="https">HTTPS</TabsTrigger>
<TabsTrigger id="ssh">SSH</TabsTrigger>
<TabsTrigger id="cli">GitHub CLI</TabsTrigger>
</TabsList>
<div className="rounded-md border bg-muted/30 p-2">
<TabsContent id="https">
<Field className="gap-2">
<FieldLabel htmlFor="https-url" className="sr-only">
HTTPS URL
</FieldLabel>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupButton variant="ghost" size="icon-xs">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
<InputGroupInput
id="https-url"
defaultValue="https://github.com/shadcn-ui/ui.git"
readOnly
/>
</InputGroup>
<FieldDescription>
Clone using the web URL.
</FieldDescription>
</Field>
</TabsContent>
<TabsContent id="ssh">
<Field className="gap-2">
<FieldLabel htmlFor="ssh-url" className="sr-only">
SSH URL
</FieldLabel>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupButton variant="ghost" size="icon-xs">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
<InputGroupInput
id="ssh-url"
defaultValue="git@github.com:shadcn-ui/ui.git"
readOnly
/>
</InputGroup>
<FieldDescription>
Use a password-protected SSH key.
</FieldDescription>
</Field>
</TabsContent>
<TabsContent id="cli">
<Field className="gap-2">
<FieldLabel htmlFor="cli-command" className="sr-only">
CLI Command
</FieldLabel>
<InputGroup>
<InputGroupAddon align="inline-end">
<InputGroupButton variant="ghost" size="icon-xs">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
<InputGroupInput
id="cli-command"
defaultValue="gh repo clone shadcn-ui/ui"
readOnly
/>
</InputGroup>
<FieldDescription>
Work fast with our official CLI.{" "}
<a href="#learn-more">Learn more</a>
</FieldDescription>
</Field>
</TabsContent>
</div>
</Tabs>
<Separator className="-mx-2 my-2 w-auto!" />
<div className="flex flex-col">
<Button
variant="ghost"
size="sm"
className="justify-start gap-1.5"
>
<IconPlaceholder
lucide="MonitorIcon"
tabler="IconDeviceDesktop"
hugeicons="ComputerIcon"
phosphor="MonitorIcon"
remixicon="RiComputerLine"
data-icon="inline-start"
/>
Open with GitHub Desktop
</Button>
<Button
variant="ghost"
size="sm"
className="justify-start gap-1.5"
>
<IconPlaceholder
lucide="DownloadIcon"
tabler="IconDownload"
hugeicons="DownloadIcon"
phosphor="DownloadIcon"
remixicon="RiDownloadLine"
data-icon="inline-start"
/>
Download ZIP
</Button>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,65 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/registry/bases/aria/ui/field"
export function ContributionsActivity() {
return (
<Card>
<CardHeader>
<CardTitle>Contributions & Activity</CardTitle>
<CardDescription>
Manage your contributions and activity visibility.
</CardDescription>
</CardHeader>
<CardContent>
<form id="contributions-activity">
<FieldGroup>
<FieldSet>
<FieldLegend className="sr-only">
Contributions & activity
</FieldLegend>
<FieldGroup>
<Field orientation="horizontal">
<Checkbox id="activity-private-profile" />
<FieldContent>
<FieldLabel htmlFor="activity-private-profile">
Make profile private and hide activity
</FieldLabel>
<FieldDescription>
Enabling this will hide your contributions and activity
from your GitHub profile and from social features like
followers, stars, feeds, leaderboards and releases.
</FieldDescription>
</FieldContent>
</Field>
</FieldGroup>
</FieldSet>
</FieldGroup>
</form>
</CardContent>
<CardFooter>
<Button form="contributions-activity" className="style-sera:w-full">
Save Changes
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,65 @@
"use client"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import { Badge } from "@/registry/bases/aria/ui/badge"
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
// GitHub usernames displayed as contributor avatars.
const usernames = [
"shadcn",
"vercel",
"nextjs",
"tailwindlabs",
"typescript-lang",
"eslint",
"prettier",
"babel",
"webpack",
"rollup",
"parcel",
"vite",
"react",
"vue",
"angular",
"solid",
]
export function Contributors() {
return (
<Card className="max-w-sm">
<CardHeader>
<CardTitle>
Contributors <Badge variant="secondary">312</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{usernames.map((username) => (
<Avatar key={username} className="grayscale">
<AvatarImage
src={`https://github.com/${username}.png`}
alt={username}
/>
<AvatarFallback>{username.charAt(0)}</AvatarFallback>
</Avatar>
))}
</div>
</CardContent>
<CardFooter>
<a href="#" className="text-sm underline underline-offset-4">
+ 810 contributors
</a>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,43 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
export function EnvironmentVariables() {
return (
<Card>
<CardHeader>
<CardTitle>Environment Variables</CardTitle>
<CardDescription>Production · 8 variables</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-2">
{[
{ key: "DATABASE_URL", masked: true },
{ key: "NEXT_PUBLIC_API", masked: false },
{ key: "STRIPE_SECRET", masked: true },
].map((env) => (
<div
key={env.key}
className="flex items-center gap-2 rounded-md px-2.5 py-2 font-mono text-xs ring ring-border"
>
<span className="font-medium">{env.key}</span>
<span className="ml-auto text-muted-foreground">
{env.masked ? "••••••••" : "https://api.example.com"}
</span>
</div>
))}
</CardContent>
<CardFooter>
<Button variant="outline">Edit</Button>
<Button className="ml-auto">Deploy</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,70 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent, CardFooter } from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/aria/ui/native-select"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
export function FeedbackForm() {
return (
<Card>
<CardContent>
<form id="feedback-form">
<FieldGroup>
<Field>
<FieldLabel htmlFor="topic">Topic</FieldLabel>
<NativeSelect id="topic">
<NativeSelectOption value="">Select a topic</NativeSelectOption>
<NativeSelectOption value="ai">AI</NativeSelectOption>
<NativeSelectOption value="accounts-and-access-controls">
Accounts and Access Controls
</NativeSelectOption>
<NativeSelectOption value="billing">Billing</NativeSelectOption>
<NativeSelectOption value="cdn">
CDN (Firewall, Caching)
</NativeSelectOption>
<NativeSelectOption value="ci-cd">
CI/CD (Builds, Deployments, Environment Variables)
</NativeSelectOption>
<NativeSelectOption value="dashboard-interface">
Dashboard Interface (Navigation, UI Issues)
</NativeSelectOption>
<NativeSelectOption value="domains">Domains</NativeSelectOption>
<NativeSelectOption value="frameworks">
Frameworks
</NativeSelectOption>
<NativeSelectOption value="marketplace-and-integrations">
Marketplace and Integrations
</NativeSelectOption>
<NativeSelectOption value="observability">
Observability (Observability, Logs, Monitoring)
</NativeSelectOption>
<NativeSelectOption value="storage">Storage</NativeSelectOption>
</NativeSelect>
</Field>
<Field>
<FieldLabel htmlFor="feedback">Feedback</FieldLabel>
<Textarea
id="feedback"
placeholder="Your feedback helps us improve..."
/>
</Field>
</FieldGroup>
</form>
</CardContent>
<CardFooter>
<Button
type="submit"
form="feedback-form"
className="style-sera:w-full"
>
Submit
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,50 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function FileUpload() {
return (
<Card>
<CardHeader>
<CardTitle>File Upload</CardTitle>
<CardDescription>Drag and drop or browse</CardDescription>
</CardHeader>
<CardContent>
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="UploadCloudIcon"
tabler="IconCloudUpload"
hugeicons="CloudUploadIcon"
phosphor="CloudArrowUpIcon"
remixicon="RiUploadCloudLine"
/>
</EmptyMedia>
<EmptyTitle>Upload files</EmptyTitle>
<EmptyDescription>PNG, JPG, PDF up to 10MB</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Browse Files</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,79 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/aria/ui/native-select"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
export function GithubProfile() {
return (
<Card className="mx-auto w-full max-w-md">
<CardHeader>
<CardTitle>Profile</CardTitle>
<CardDescription>Manage your profile information.</CardDescription>
</CardHeader>
<CardContent>
<form id="profile">
<FieldGroup>
<Field>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" placeholder="Taylor" />
<FieldDescription>
Your name may appear around GitHub where you contribute or are
mentioned. You can remove it at any time.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="email">Public Email</FieldLabel>
<NativeSelect id="email">
<NativeSelectOption value="taylor@example.com">
taylor@example.com
</NativeSelectOption>
<NativeSelectOption value="work@example.com">
work@example.com
</NativeSelectOption>
</NativeSelect>
<FieldDescription>
You can manage verified email addresses in your{" "}
<a href="#email-settings">email settings</a>.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="bio">Bio</FieldLabel>
<Textarea
id="bio"
placeholder="Tell us a little bit about yourself"
/>
<FieldDescription>
You can <span>@mention</span> other users and organizations to
link to them.
</FieldDescription>
</Field>
</FieldGroup>
</form>
</CardContent>
<CardFooter>
<Button form="profile" className="style-sera:w-full">
Save Profile
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,146 @@
"use client"
import { type IconLibraryName } from "shadcn/icons"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const PREVIEW_ICONS = [
{
lucide: "CopyIcon",
tabler: "IconCopy",
hugeicons: "Copy01Icon",
phosphor: "CopyIcon",
remixicon: "RiFileCopyLine",
},
{
lucide: "CircleAlertIcon",
tabler: "IconExclamationCircle",
hugeicons: "AlertCircleIcon",
phosphor: "WarningCircleIcon",
remixicon: "RiErrorWarningLine",
},
{
lucide: "TrashIcon",
tabler: "IconTrash",
hugeicons: "Delete02Icon",
phosphor: "TrashIcon",
remixicon: "RiDeleteBinLine",
},
{
lucide: "ShareIcon",
tabler: "IconShare",
hugeicons: "Share03Icon",
phosphor: "ShareIcon",
remixicon: "RiShareLine",
},
{
lucide: "ShoppingBagIcon",
tabler: "IconShoppingBag",
hugeicons: "ShoppingBag01Icon",
phosphor: "BagIcon",
remixicon: "RiShoppingBagLine",
},
{
lucide: "MoreHorizontalIcon",
tabler: "IconDots",
hugeicons: "MoreHorizontalCircle01Icon",
phosphor: "DotsThreeIcon",
remixicon: "RiMoreLine",
},
{
lucide: "Loader2Icon",
tabler: "IconLoader",
hugeicons: "Loading03Icon",
phosphor: "SpinnerIcon",
remixicon: "RiLoaderLine",
},
{
lucide: "PlusIcon",
tabler: "IconPlus",
hugeicons: "PlusSignIcon",
phosphor: "PlusIcon",
remixicon: "RiAddLine",
},
{
lucide: "MinusIcon",
tabler: "IconMinus",
hugeicons: "MinusSignIcon",
phosphor: "MinusIcon",
remixicon: "RiSubtractLine",
},
{
lucide: "ArrowLeftIcon",
tabler: "IconArrowLeft",
hugeicons: "ArrowLeft02Icon",
phosphor: "ArrowLeftIcon",
remixicon: "RiArrowLeftLine",
},
{
lucide: "ArrowRightIcon",
tabler: "IconArrowRight",
hugeicons: "ArrowRight02Icon",
phosphor: "ArrowRightIcon",
remixicon: "RiArrowRightLine",
},
{
lucide: "CheckIcon",
tabler: "IconCheck",
hugeicons: "Tick02Icon",
phosphor: "CheckIcon",
remixicon: "RiCheckLine",
},
{
lucide: "ChevronDownIcon",
tabler: "IconChevronDown",
hugeicons: "ArrowDown01Icon",
phosphor: "CaretDownIcon",
remixicon: "RiArrowDownSLine",
},
{
lucide: "ChevronRightIcon",
tabler: "IconChevronRight",
hugeicons: "ArrowRight01Icon",
phosphor: "CaretRightIcon",
remixicon: "RiArrowRightSLine",
},
{
lucide: "SearchIcon",
tabler: "IconSearch",
hugeicons: "Search01Icon",
phosphor: "MagnifyingGlassIcon",
remixicon: "RiSearchLine",
},
{
lucide: "SettingsIcon",
tabler: "IconSettings",
hugeicons: "Settings01Icon",
phosphor: "GearIcon",
remixicon: "RiSettingsLine",
},
] satisfies Record<IconLibraryName, string>[]
export function IconPreviewGrid() {
return (
<Card>
<CardContent>
<div className="grid grid-cols-8 place-items-center gap-4">
{PREVIEW_ICONS.map((icon, index) => (
<div
key={index}
className="flex size-8 items-center justify-center rounded-md ring ring-border style-sera:rounded-none *:[svg]:size-4"
>
<IconPlaceholder
lucide={icon.lucide}
tabler={icon.tabler}
hugeicons={icon.hugeicons}
phosphor={icon.phosphor}
remixicon={icon.remixicon}
/>
</div>
))}
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,100 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Field, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Separator } from "@/registry/bases/aria/ui/separator"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function InviteTeam() {
return (
<Card>
<CardHeader>
<CardTitle>Invite Team</CardTitle>
<CardDescription>Add members to your workspace</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
{[
{ email: "alex@example.com", role: "Editor" },
{ email: "sam@example.com", role: "Viewer" },
].map((invite) => (
<div key={invite.email} className="flex items-center gap-2">
<Input defaultValue={invite.email} className="flex-1" />
<Select defaultValue={invite.role.toLowerCase()}>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent placement="bottom end">
<SelectGroup>
<SelectItem id="admin">Admin</SelectItem>
<SelectItem id="editor">Editor</SelectItem>
<SelectItem id="viewer">Viewer</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
))}
</div>
<Button variant="outline">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-start"
/>
Add another
</Button>
<Separator />
<Field>
<FieldLabel htmlFor="invite-link">Or share invite link</FieldLabel>
<InputGroup>
<InputGroupInput
id="invite-link"
defaultValue="https://app.co/invite/x8f2k"
readOnly
/>
<InputGroupAddon align="inline-end">
<InputGroupButton size="icon-xs" aria-label="Copy link">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</CardContent>
<CardFooter>
<Button className="w-full">Send Invites</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,112 @@
"use client"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/bases/aria/ui/table"
const INVOICE_ITEMS = [
{ item: "Design System License", qty: 1, unitPrice: 499 },
{ item: "Priority Support", qty: 12, unitPrice: 99 },
{ item: "Custom Components", qty: 3, unitPrice: 250 },
] as const
const subtotal = INVOICE_ITEMS.reduce(
(sum, row) => sum + row.qty * row.unitPrice,
0
)
const tax = 0
const totalDue = subtotal + tax
function formatCurrency(value: number) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 2,
}).format(value)
}
export function Invoice() {
return (
<Card>
<CardHeader>
<CardTitle>Invoice #INV-2847</CardTitle>
<CardDescription>Due March 30, 2026</CardDescription>
<CardAction>
<Badge variant="secondary">Pending</Badge>
</CardAction>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableHead isRowHeader>Item</TableHead>
<TableHead className="text-right">Qty</TableHead>
<TableHead className="text-right">Rate</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableHeader>
<TableBody>
{INVOICE_ITEMS.map((row) => (
<TableRow key={row.item}>
<TableCell>{row.item}</TableCell>
<TableCell className="text-right tabular-nums">
{row.qty}
</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(row.unitPrice)}
</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(row.qty * row.unitPrice)}
</TableCell>
</TableRow>
))}
<TableRow>
<TableCell colSpan={3} className="text-right">
Subtotal
</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(subtotal)}
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={3} className="text-right">
Tax
</TableCell>
<TableCell className="text-right tabular-nums">$0.00</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={3} className="text-right">
Total Due
</TableCell>
<TableCell className="text-right tabular-nums">
{formatCurrency(totalDue)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
<CardFooter>
<Button variant="outline" size="sm">
Download PDF
</Button>
<Button size="sm" className="ml-auto">
Pay Now
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,556 @@
"use client"
import * as React from "react"
import { useEffect, useRef, useState } from "react"
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
const LiveWaveform = ({
active = false,
processing = false,
deviceId,
barWidth = 3,
barGap = 1,
barRadius = 1.5,
barColor,
fadeEdges = true,
fadeWidth = 24,
barHeight: baseBarHeight = 4,
height = 64,
sensitivity = 1,
smoothingTimeConstant = 0.8,
fftSize = 256,
historySize = 60,
updateRate = 30,
mode = "static",
onError,
onStreamReady,
onStreamEnd,
className,
...props
}: React.HTMLAttributes<HTMLDivElement> & {
active?: boolean
processing?: boolean
deviceId?: string
barWidth?: number
barHeight?: number
barGap?: number
barRadius?: number
barColor?: string
fadeEdges?: boolean
fadeWidth?: number
height?: string | number
sensitivity?: number
smoothingTimeConstant?: number
fftSize?: number
historySize?: number
updateRate?: number
mode?: "scrolling" | "static"
onError?: (error: Error) => void
onStreamReady?: (stream: MediaStream) => void
onStreamEnd?: () => void
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const historyRef = useRef<number[]>([])
const analyserRef = useRef<AnalyserNode | null>(null)
const audioContextRef = useRef<AudioContext | null>(null)
const streamRef = useRef<MediaStream | null>(null)
const animationRef = useRef<number>(0)
const lastUpdateRef = useRef<number>(0)
const processingAnimationRef = useRef<number | null>(null)
const lastActiveDataRef = useRef<number[]>([])
const transitionProgressRef = useRef(0)
const staticBarsRef = useRef<number[]>([])
const needsRedrawRef = useRef(true)
const gradientCacheRef = useRef<CanvasGradient | null>(null)
const lastWidthRef = useRef(0)
const heightStyle = typeof height === "number" ? `${height}px` : height
// Handle canvas resizing.
useEffect(() => {
const canvas = canvasRef.current
const container = containerRef.current
if (!canvas || !container) return
const resizeObserver = new ResizeObserver(() => {
const rect = container.getBoundingClientRect()
const dpr = window.devicePixelRatio || 1
canvas.width = rect.width * dpr
canvas.height = rect.height * dpr
canvas.style.width = `${rect.width}px`
canvas.style.height = `${rect.height}px`
const ctx = canvas.getContext("2d")
if (ctx) {
ctx.scale(dpr, dpr)
}
gradientCacheRef.current = null
lastWidthRef.current = rect.width
needsRedrawRef.current = true
})
resizeObserver.observe(container)
return () => resizeObserver.disconnect()
}, [])
useEffect(() => {
if (processing && !active) {
let time = 0
transitionProgressRef.current = 0
const animateProcessing = () => {
time += 0.03
transitionProgressRef.current = Math.min(
1,
transitionProgressRef.current + 0.02
)
const processingData = []
const barCount = Math.floor(
(containerRef.current?.getBoundingClientRect().width || 200) /
(barWidth + barGap)
)
if (mode === "static") {
const halfCount = Math.floor(barCount / 2)
for (let i = 0; i < barCount; i++) {
const normalizedPosition = (i - halfCount) / halfCount
const centerWeight = 1 - Math.abs(normalizedPosition) * 0.4
const wave1 = Math.sin(time * 1.5 + normalizedPosition * 3) * 0.25
const wave2 = Math.sin(time * 0.8 - normalizedPosition * 2) * 0.2
const wave3 = Math.cos(time * 2 + normalizedPosition) * 0.15
const combinedWave = wave1 + wave2 + wave3
const processingValue = (0.2 + combinedWave) * centerWeight
let finalValue = processingValue
if (
lastActiveDataRef.current.length > 0 &&
transitionProgressRef.current < 1
) {
const lastDataIndex = Math.min(
i,
lastActiveDataRef.current.length - 1
)
const lastValue = lastActiveDataRef.current[lastDataIndex] || 0
finalValue =
lastValue * (1 - transitionProgressRef.current) +
processingValue * transitionProgressRef.current
}
processingData.push(Math.max(0.05, Math.min(1, finalValue)))
}
} else {
for (let i = 0; i < barCount; i++) {
const normalizedPosition = (i - barCount / 2) / (barCount / 2)
const centerWeight = 1 - Math.abs(normalizedPosition) * 0.4
const wave1 = Math.sin(time * 1.5 + i * 0.15) * 0.25
const wave2 = Math.sin(time * 0.8 - i * 0.1) * 0.2
const wave3 = Math.cos(time * 2 + i * 0.05) * 0.15
const combinedWave = wave1 + wave2 + wave3
const processingValue = (0.2 + combinedWave) * centerWeight
let finalValue = processingValue
if (
lastActiveDataRef.current.length > 0 &&
transitionProgressRef.current < 1
) {
const lastDataIndex = Math.floor(
(i / barCount) * lastActiveDataRef.current.length
)
const lastValue = lastActiveDataRef.current[lastDataIndex] || 0
finalValue =
lastValue * (1 - transitionProgressRef.current) +
processingValue * transitionProgressRef.current
}
processingData.push(Math.max(0.05, Math.min(1, finalValue)))
}
}
if (mode === "static") {
staticBarsRef.current = processingData
} else {
historyRef.current = processingData
}
needsRedrawRef.current = true
processingAnimationRef.current =
requestAnimationFrame(animateProcessing)
}
animateProcessing()
return () => {
if (processingAnimationRef.current) {
cancelAnimationFrame(processingAnimationRef.current)
}
}
} else if (!active && !processing) {
const hasData =
mode === "static"
? staticBarsRef.current.length > 0
: historyRef.current.length > 0
if (hasData) {
let fadeProgress = 0
const fadeToIdle = () => {
fadeProgress += 0.03
if (fadeProgress < 1) {
if (mode === "static") {
staticBarsRef.current = staticBarsRef.current.map(
(value) => value * (1 - fadeProgress)
)
} else {
historyRef.current = historyRef.current.map(
(value) => value * (1 - fadeProgress)
)
}
needsRedrawRef.current = true
requestAnimationFrame(fadeToIdle)
} else {
if (mode === "static") {
staticBarsRef.current = []
} else {
historyRef.current = []
}
}
}
fadeToIdle()
}
}
}, [processing, active, barWidth, barGap, mode])
// Handle microphone setup and teardown.
useEffect(() => {
if (!active) {
if (streamRef.current) {
streamRef.current.getTracks().forEach((track) => track.stop())
streamRef.current = null
onStreamEnd?.()
}
if (
audioContextRef.current &&
audioContextRef.current.state !== "closed"
) {
audioContextRef.current.close()
audioContextRef.current = null
}
if (animationRef.current) {
cancelAnimationFrame(animationRef.current)
animationRef.current = 0
}
return
}
const setupMicrophone = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: deviceId
? {
deviceId: { exact: deviceId },
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
}
: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
})
streamRef.current = stream
onStreamReady?.(stream)
const AudioContextConstructor =
window.AudioContext ||
(window as unknown as { webkitAudioContext: typeof AudioContext })
.webkitAudioContext
const audioContext = new AudioContextConstructor()
const analyser = audioContext.createAnalyser()
analyser.fftSize = fftSize
analyser.smoothingTimeConstant = smoothingTimeConstant
const source = audioContext.createMediaStreamSource(stream)
source.connect(analyser)
audioContextRef.current = audioContext
analyserRef.current = analyser
historyRef.current = []
} catch (error) {
onError?.(error as Error)
}
}
setupMicrophone()
return () => {
if (streamRef.current) {
streamRef.current.getTracks().forEach((track) => track.stop())
streamRef.current = null
onStreamEnd?.()
}
if (
audioContextRef.current &&
audioContextRef.current.state !== "closed"
) {
audioContextRef.current.close()
audioContextRef.current = null
}
if (animationRef.current) {
cancelAnimationFrame(animationRef.current)
animationRef.current = 0
}
}
}, [
active,
deviceId,
fftSize,
smoothingTimeConstant,
onError,
onStreamReady,
onStreamEnd,
])
// Animation loop.
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext("2d")
if (!ctx) return
let rafId: number
const animate = (currentTime: number) => {
const rect = canvas.getBoundingClientRect()
if (active && currentTime - lastUpdateRef.current > updateRate) {
lastUpdateRef.current = currentTime
if (analyserRef.current) {
const dataArray = new Uint8Array(
analyserRef.current.frequencyBinCount
)
analyserRef.current.getByteFrequencyData(dataArray)
if (mode === "static") {
const startFreq = Math.floor(dataArray.length * 0.05)
const endFreq = Math.floor(dataArray.length * 0.4)
const relevantData = dataArray.slice(startFreq, endFreq)
const barCount = Math.floor(rect.width / (barWidth + barGap))
const halfCount = Math.floor(barCount / 2)
const newBars: number[] = []
for (let i = halfCount - 1; i >= 0; i--) {
const dataIndex = Math.floor(
(i / halfCount) * relevantData.length
)
const value = Math.min(
1,
(relevantData[dataIndex] / 255) * sensitivity
)
newBars.push(Math.max(0.05, value))
}
for (let i = 0; i < halfCount; i++) {
const dataIndex = Math.floor(
(i / halfCount) * relevantData.length
)
const value = Math.min(
1,
(relevantData[dataIndex] / 255) * sensitivity
)
newBars.push(Math.max(0.05, value))
}
staticBarsRef.current = newBars
lastActiveDataRef.current = newBars
} else {
let sum = 0
const startFreq = Math.floor(dataArray.length * 0.05)
const endFreq = Math.floor(dataArray.length * 0.4)
const relevantData = dataArray.slice(startFreq, endFreq)
for (let i = 0; i < relevantData.length; i++) {
sum += relevantData[i]
}
const average = (sum / relevantData.length / 255) * sensitivity
historyRef.current.push(Math.min(1, Math.max(0.05, average)))
lastActiveDataRef.current = [...historyRef.current]
if (historyRef.current.length > historySize) {
historyRef.current.shift()
}
}
needsRedrawRef.current = true
}
}
if (!needsRedrawRef.current && !active) {
rafId = requestAnimationFrame(animate)
return
}
needsRedrawRef.current = active
ctx.clearRect(0, 0, rect.width, rect.height)
const computedBarColor =
barColor ||
(() => {
const style = getComputedStyle(canvas)
const color = style.color
return color || "#000"
})()
const step = barWidth + barGap
const barCount = Math.floor(rect.width / step)
const centerY = rect.height / 2
if (mode === "static") {
const dataToRender = processing
? staticBarsRef.current
: active
? staticBarsRef.current
: staticBarsRef.current.length > 0
? staticBarsRef.current
: []
for (let i = 0; i < barCount && i < dataToRender.length; i++) {
const value = dataToRender[i] || 0.1
const x = i * step
const bh = Math.max(baseBarHeight, value * rect.height * 0.8)
const y = centerY - bh / 2
ctx.fillStyle = computedBarColor
ctx.globalAlpha = 0.4 + value * 0.6
if (barRadius > 0) {
ctx.beginPath()
ctx.roundRect(x, y, barWidth, bh, barRadius)
ctx.fill()
} else {
ctx.fillRect(x, y, barWidth, bh)
}
}
} else {
for (let i = 0; i < barCount && i < historyRef.current.length; i++) {
const dataIndex = historyRef.current.length - 1 - i
const value = historyRef.current[dataIndex] || 0.1
const x = rect.width - (i + 1) * step
const bh = Math.max(baseBarHeight, value * rect.height * 0.8)
const y = centerY - bh / 2
ctx.fillStyle = computedBarColor
ctx.globalAlpha = 0.4 + value * 0.6
if (barRadius > 0) {
ctx.beginPath()
ctx.roundRect(x, y, barWidth, bh, barRadius)
ctx.fill()
} else {
ctx.fillRect(x, y, barWidth, bh)
}
}
}
if (fadeEdges && fadeWidth > 0 && rect.width > 0) {
if (!gradientCacheRef.current || lastWidthRef.current !== rect.width) {
const gradient = ctx.createLinearGradient(0, 0, rect.width, 0)
const fadePercent = Math.min(0.3, fadeWidth / rect.width)
gradient.addColorStop(0, "rgba(255,255,255,1)")
gradient.addColorStop(fadePercent, "rgba(255,255,255,0)")
gradient.addColorStop(1 - fadePercent, "rgba(255,255,255,0)")
gradient.addColorStop(1, "rgba(255,255,255,1)")
gradientCacheRef.current = gradient
lastWidthRef.current = rect.width
}
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = gradientCacheRef.current
ctx.fillRect(0, 0, rect.width, rect.height)
ctx.globalCompositeOperation = "source-over"
}
ctx.globalAlpha = 1
rafId = requestAnimationFrame(animate)
}
rafId = requestAnimationFrame(animate)
return () => {
if (rafId) {
cancelAnimationFrame(rafId)
}
}
}, [
active,
processing,
sensitivity,
updateRate,
historySize,
barWidth,
baseBarHeight,
barGap,
barRadius,
barColor,
fadeEdges,
fadeWidth,
mode,
])
return (
<div
className={cn("relative h-full w-full", className)}
ref={containerRef}
style={{ height: heightStyle }}
aria-label={
active
? "Live audio waveform"
: processing
? "Processing audio"
: "Audio waveform idle"
}
role="img"
{...props}
>
{!active && !processing && (
<div className="absolute top-1/2 right-0 left-0 -translate-y-1/2 border-t-2 border-dotted border-muted-foreground/20" />
)}
<canvas
className="block h-full w-full"
ref={canvasRef}
aria-hidden="true"
/>
</div>
)
}
export function LiveWaveformCard() {
const [active, setActive] = useState(false)
const [processing, setProcessing] = useState(true)
const [mode, setMode] = useState<"static" | "scrolling">("static")
const handleToggleActive = () => {
setActive(!active)
if (!active) {
setProcessing(false)
}
}
const handleToggleProcessing = () => {
setProcessing(!processing)
if (!processing) {
setActive(false)
}
}
return (
<Card>
<CardHeader>
<CardTitle>Live Audio Waveform</CardTitle>
<CardDescription>
Real-time microphone input visualization with audio reactivity
</CardDescription>
</CardHeader>
<CardContent>
<LiveWaveform
active={active}
processing={processing}
height={80}
barWidth={3}
barGap={2}
mode={mode}
fadeEdges={true}
barColor="gray"
historySize={120}
/>
</CardContent>
<CardFooter className="gap-2">
<Button
size="sm"
variant={active ? "default" : "outline"}
onClick={handleToggleActive}
>
{active ? "Stop" : "Start"} Listening
</Button>
<Button
size="sm"
variant={processing ? "default" : "outline"}
onClick={handleToggleProcessing}
>
{processing ? "Stop" : "Start"} Processing
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setMode(mode === "static" ? "scrolling" : "static")}
>
{mode === "static" ? "Static" : "Scrolling"}
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,63 @@
"use client"
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
export function NoTeamMembers() {
return (
<Card>
<CardContent>
<Empty className="h-56 border">
<EmptyHeader>
<EmptyMedia>
<AvatarGroup className="grayscale">
<Avatar size="lg">
<AvatarImage
src="https://github.com/shadcn.png"
alt="@shadcn"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</EmptyMedia>
<EmptyTitle>No Team Members</EmptyTitle>
<EmptyDescription>
Invite your team to collaborate on this project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">Invite Members</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,54 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { Kbd } from "@/registry/bases/aria/ui/kbd"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function NotFound() {
return (
<Card>
<CardContent>
<Empty className="h-72">
<EmptyHeader>
<EmptyTitle>404 - Not Found</EmptyTitle>
<EmptyDescription>
The page you&apos;re looking for doesn&apos;t exist. Try searching
for what you need below.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<Button variant="link">Go to homepage</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,50 @@
"use client"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function ObservabilityCard() {
return (
<Card className="relative w-full max-w-md overflow-hidden pt-0">
<div className="absolute inset-0 z-30 aspect-video bg-primary opacity-50 mix-blend-color" />
<img
src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="Photo by mymind on Unsplash"
title="Photo by mymind on Unsplash"
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
/>
<CardHeader>
<CardTitle>Observability Plus is replacing Monitoring</CardTitle>
<CardDescription>
Switch to the improved way to explore your data, with natural
language. Monitoring will no longer be available on the Pro plan in
November, 2025
</CardDescription>
</CardHeader>
<CardFooter>
<Button>
Create Query{" "}
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-end"
/>
</Button>
<Badge variant="secondary" className="ml-auto">
Warning
</Badge>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,147 @@
"use client"
import { Label, Pie, PieChart } from "recharts"
import { Badge } from "@/registry/bases/aria/ui/badge"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
import { Progress } from "@/registry/bases/aria/ui/progress"
const pieChartData = [
{ browser: "chrome", visitors: 275, fill: "var(--color-chrome)" },
{ browser: "safari", visitors: 200, fill: "var(--color-safari)" },
{ browser: "firefox", visitors: 287, fill: "var(--color-firefox)" },
{ browser: "edge", visitors: 173, fill: "var(--color-edge)" },
]
const pieChartConfig = {
visitors: {
label: "Visitors",
},
chrome: {
label: "Chrome",
color: "var(--chart-1)",
},
safari: {
label: "Safari",
color: "var(--chart-2)",
},
firefox: {
label: "Firefox",
color: "var(--chart-3)",
},
edge: {
label: "Edge",
color: "var(--chart-4)",
},
} satisfies ChartConfig
export function PieChartCard() {
const totalVisitors = pieChartData.reduce(
(sum, item) => sum + item.visitors,
0
)
const topBrowser = pieChartData.reduce((max, item) =>
item.visitors > max.visitors ? item : max
)
const topBrowserShare = Math.round(
(topBrowser.visitors / totalVisitors) * 100
)
const topBrowserLabel =
pieChartConfig[topBrowser.browser as keyof typeof pieChartConfig]?.label ??
"Top"
return (
<Card>
<CardHeader className="pb-0">
<CardTitle>Browser Share</CardTitle>
<CardDescription>January - June 2026</CardDescription>
<CardAction>
<Badge variant="outline">{topBrowserLabel}</Badge>
</CardAction>
</CardHeader>
<CardContent className="pt-0">
<ChartContainer
config={pieChartConfig}
className="mx-auto aspect-square max-h-[190px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Pie
data={pieChartData}
dataKey="visitors"
nameKey="browser"
innerRadius={50}
strokeWidth={5}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
fill="red"
>
<tspan
x={viewBox.cx}
y={viewBox.cy - 16}
className="fill-foreground text-2xl font-bold"
>
{totalVisitors.toLocaleString()}
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 4}
className="fill-muted-foreground text-xs"
>
Visitors
</tspan>
</text>
)
}
}}
/>
</Pie>
<ChartLegend
content={<ChartLegendContent nameKey="browser" />}
className="translate-y-2"
/>
</PieChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col items-stretch gap-2">
<div className="flex items-center text-xs">
<span className="font-medium">{topBrowserLabel}</span>
<span className="ml-auto text-muted-foreground tabular-nums">
{topBrowserShare}%
</span>
</div>
<Progress
value={topBrowserShare}
className="**:data-[slot=progress-indicator]:bg-chart-3"
/>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,97 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
export function ReportBug() {
return (
<Card>
<CardHeader>
<CardTitle>Report Bug</CardTitle>
<CardDescription>Help us fix issues faster.</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="bug-title">Title</FieldLabel>
<Input
id="bug-title"
placeholder="Brief description of the issue"
/>
</Field>
<div className="grid grid-cols-2 gap-3">
<Field>
<FieldLabel htmlFor="bug-severity">Severity</FieldLabel>
<Select defaultValue="medium">
<SelectTrigger id="bug-severity" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="critical">Critical</SelectItem>
<SelectItem id="high">High</SelectItem>
<SelectItem id="medium">Medium</SelectItem>
<SelectItem id="low">Low</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="bug-component">Component</FieldLabel>
<Select defaultValue="dashboard">
<SelectTrigger id="bug-component" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="dashboard">Dashboard</SelectItem>
<SelectItem id="auth">Auth</SelectItem>
<SelectItem id="api">API</SelectItem>
<SelectItem id="billing">Billing</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
</div>
<Field>
<FieldLabel htmlFor="bug-steps">Steps to reproduce</FieldLabel>
<Textarea
id="bug-steps"
placeholder="1. Go to&#10;2. Click on&#10;3. Observe..."
className="min-h-24 resize-none"
/>
</Field>
</FieldGroup>
</CardContent>
<CardFooter>
<Field
orientation="horizontal"
className="justify-end style-sera:justify-center"
>
<Button variant="outline" className="style-sera:flex-1">
Attach File
</Button>
<Button className="style-sera:flex-1">Submit Bug</Button>
</Field>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,101 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
export function ShippingAddress() {
return (
<Card>
<CardHeader>
<CardTitle>Shipping Address</CardTitle>
<CardDescription>Where should we deliver?</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="shipping-street">Street address</FieldLabel>
<Input id="shipping-street" placeholder="123 Main Street" />
</Field>
<Field>
<FieldLabel htmlFor="shipping-apt">Apt / Suite</FieldLabel>
<Input id="shipping-apt" placeholder="Apt 4B" />
</Field>
<FieldGroup className="grid grid-cols-2">
<Field>
<FieldLabel htmlFor="shipping-city">City</FieldLabel>
<Input id="shipping-city" placeholder="San Francisco" />
</Field>
<Field>
<FieldLabel htmlFor="shipping-state">State</FieldLabel>
<Select defaultValue="CA">
<SelectTrigger id="shipping-state" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="CA">California</SelectItem>
<SelectItem id="NY">New York</SelectItem>
<SelectItem id="TX">Texas</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
</FieldGroup>
<FieldGroup className="grid grid-cols-2">
<Field>
<FieldLabel htmlFor="shipping-zip">ZIP Code</FieldLabel>
<Input id="shipping-zip" placeholder="94102" />
</Field>
<Field>
<FieldLabel htmlFor="shipping-country">Country</FieldLabel>
<Select defaultValue="US">
<SelectTrigger id="shipping-country" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="US">United States</SelectItem>
<SelectItem id="CA">Canada</SelectItem>
<SelectItem id="UK">United Kingdom</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
</FieldGroup>
<Field orientation="horizontal">
<Checkbox id="shipping-save" defaultSelected />
<FieldLabel htmlFor="shipping-save" className="font-normal">
Save as default address
</FieldLabel>
</Field>
</FieldGroup>
</CardContent>
<CardFooter>
<Button variant="outline" size="sm">
Cancel
</Button>
<Button size="sm" className="ml-auto">
Save Address
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,55 @@
import * as React from "react"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Item,
ItemActions,
ItemGroup,
ItemHeader,
ItemSeparator,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import { Kbd } from "@/registry/bases/aria/ui/kbd"
const shortcuts = [
{ label: "Search", keys: ["⌘", "K"] },
{ label: "Quick Actions", keys: ["⌘", "J"] },
{ label: "New File", keys: ["⌘", "N"] },
{ label: "Save", keys: ["⌘", "S"] },
{ label: "Toggle Sidebar", keys: ["⌘", "B"] },
] as const
export function Shortcuts() {
return (
<Card>
<CardContent>
<div className="flex flex-col gap-3">
<div className="text-sm font-medium">Shortcuts</div>
<ItemGroup className="gap-2 text-muted-foreground" data-size="xs">
{shortcuts.map(({ label, keys }, i) => (
<React.Fragment key={label}>
{i > 0 && <ItemSeparator />}
<Item
variant="default"
size="xs"
className="border-0 px-0 py-0"
>
<ItemHeader>
<ItemTitle className="font-normal">{label}</ItemTitle>
<ItemActions>
<div className="flex gap-1">
{keys.map((key) => (
<Kbd key={key}>{key}</Kbd>
))}
</div>
</ItemActions>
</ItemHeader>
</Item>
</React.Fragment>
))}
</ItemGroup>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,29 @@
"use client"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import { Skeleton } from "@/registry/bases/aria/ui/skeleton"
export function SkeletonLoading() {
return (
<Card>
<CardContent className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<Skeleton className="size-10 rounded-full" />
<div className="flex flex-1 flex-col gap-2">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-1/2" />
</div>
</div>
<div className="flex flex-col gap-2">
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-4/5" />
</div>
<div className="flex gap-2">
<Skeleton className="h-8 w-20" />
<Skeleton className="h-8 w-20" />
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,104 @@
"use client"
import { Bar, BarChart } from "recharts"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
const sleepChartData = [
{ hour: "10pm", deep: 0, light: 30, rem: 0 },
{ hour: "11pm", deep: 20, light: 10, rem: 0 },
{ hour: "12am", deep: 40, light: 0, rem: 10 },
{ hour: "1am", deep: 30, light: 5, rem: 15 },
{ hour: "2am", deep: 10, light: 20, rem: 30 },
{ hour: "3am", deep: 25, light: 10, rem: 20 },
{ hour: "4am", deep: 15, light: 25, rem: 10 },
{ hour: "5am", deep: 5, light: 35, rem: 15 },
{ hour: "6am", deep: 0, light: 20, rem: 25 },
]
const sleepChartConfig = {
deep: {
label: "Deep",
color: "var(--chart-1)",
},
light: {
label: "Light",
color: "var(--chart-2)",
},
rem: {
label: "REM",
color: "var(--chart-3)",
},
} satisfies ChartConfig
export function SleepReport() {
return (
<Card>
<CardHeader>
<CardTitle>Sleep Report</CardTitle>
<CardDescription>Last night · 7h 24m</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-3">
<ChartContainer config={sleepChartConfig} className="h-32 w-full">
<BarChart
accessibilityLayer
data={sleepChartData}
margin={{ left: 0, right: 0, top: 0, bottom: 0 }}
barSize={16}
>
<Bar
dataKey="deep"
stackId="a"
fill="var(--color-deep)"
radius={0}
/>
<Bar
dataKey="light"
stackId="a"
fill="var(--color-light)"
radius={0}
/>
<Bar
dataKey="rem"
stackId="a"
fill="var(--color-rem)"
radius={[2, 2, 0, 0]}
/>
</BarChart>
</ChartContainer>
<div className="grid grid-cols-4 gap-2">
{[
{ label: "Deep", value: "2h 10m" },
{ label: "Light", value: "3h 48m" },
{ label: "REM", value: "1h 26m" },
{ label: "Score", value: "84" },
].map((s) => (
<div key={s.label} className="text-center">
<div className="text-sm font-medium tabular-nums">{s.value}</div>
<div className="text-xs text-muted-foreground">{s.label}</div>
</div>
))}
</div>
</CardContent>
<CardFooter>
<Badge variant="outline">Good</Badge>
<Button variant="outline" size="sm" className="ml-auto">
Details
</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,80 @@
"use client"
import * as React from "react"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import { STYLES } from "@/registry/styles"
import { FONTS } from "@/app/(create)/lib/fonts"
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
export function StyleOverview() {
const [params] = useDesignSystemSearchParams()
const currentFont = React.useMemo(
() => FONTS.find((font) => font.value === params.font),
[params.font]
)
const currentFontHeading = React.useMemo(
() => FONTS.find((font) => font.value === params.fontHeading),
[params.fontHeading]
)
const currentStyle = React.useMemo(
() => STYLES.find((style) => style.name === params.style),
[params.style]
)
return (
<Card>
<CardContent className="flex flex-col gap-6 style-lyra:gap-4 style-mira:gap-4">
<div className="flex flex-col gap-1">
<div className="cn-font-heading text-2xl font-medium style-lyra:text-lg style-mira:text-lg style-sera:text-lg style-sera:font-semibold style-sera:tracking-wide style-sera:uppercase">
{currentStyle?.title} -{" "}
{currentFontHeading?.name &&
currentFontHeading.name !== currentFont?.name
? currentFontHeading.name
: currentFont?.name}
</div>
<div className="line-clamp-2 text-base text-muted-foreground style-lyra:text-sm style-mira:text-sm style-sera:text-sm style-sera:leading-relaxed">
Designers love packing quirky glyphs into test phrases. This is a
preview of the typography styles.
</div>
</div>
<div className="grid grid-cols-6 gap-3">
{[
"--background",
"--foreground",
"--primary",
"--secondary",
"--muted",
"--accent",
"--border",
"--chart-1",
"--chart-2",
"--chart-3",
"--chart-4",
"--chart-5",
].map((variant) => (
<div
key={variant}
className="flex flex-col flex-wrap items-center gap-2"
>
<div
className="relative aspect-square w-full rounded-lg bg-(--color) after:absolute after:inset-0 after:rounded-lg after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten style-sera:rounded-none style-sera:after:rounded-none"
style={
{
"--color": `var(${variant})`,
} as React.CSSProperties
}
/>
<div className="hidden max-w-14 truncate font-mono text-[0.60rem] md:block style-lyra:max-w-10 style-mira:max-w-10">
{variant}
</div>
</div>
))}
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,127 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent, CardFooter } from "@/registry/bases/aria/ui/card"
import {
Dialog,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/ui/dialog"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
import { FONTS } from "@/app/(create)/lib/fonts"
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
export function TypographySpecimen() {
const [params] = useDesignSystemSearchParams()
const currentFont = React.useMemo(
() => FONTS.find((font) => font.value === params.font),
[params.font]
)
const currentFontHeading = React.useMemo(
() => FONTS.find((font) => font.value === params.fontHeading),
[params.fontHeading]
)
const headingLabel =
currentFontHeading?.name && currentFontHeading.name !== currentFont?.name
? currentFontHeading.name
: "Inherit"
const bodyLabel = currentFont?.name ?? "Default"
return (
<Card>
<CardContent className="flex flex-col gap-2">
<div className="text-xs font-medium text-muted-foreground uppercase">
{headingLabel} - {bodyLabel}
</div>
<p className="cn-font-heading text-2xl font-medium style-sera:text-lg style-sera:font-semibold style-sera:tracking-wider style-sera:uppercase">
Designing with rhythm and hierarchy.
</p>
<p className="text-sm leading-relaxed text-muted-foreground">
A strong body style keeps long-form content readable and balances the
visual weight of headings.
</p>
<p className="text-sm leading-relaxed text-muted-foreground">
Thoughtful spacing and cadence help paragraphs scan quickly without
feeling dense.
</p>
</CardContent>
<CardFooter>
<DialogTrigger>
<Button variant="outline" className="w-full">
Share Feedback
</Button>
<Dialog>
<DialogHeader>
<DialogTitle>Share Feedback</DialogTitle>
<DialogDescription>
Let us know how we can improve your experience.
</DialogDescription>
</DialogHeader>
<FieldGroup>
<div className="grid grid-cols-2 gap-3">
<Field>
<FieldLabel htmlFor="feedback-name">Name</FieldLabel>
<Input id="feedback-name" placeholder="Your name" />
</Field>
<Field>
<FieldLabel htmlFor="feedback-email">Email</FieldLabel>
<Input
id="feedback-email"
type="email"
placeholder="you@example.com"
/>
</Field>
</div>
<Field>
<FieldLabel htmlFor="feedback-category">Category</FieldLabel>
<Select defaultValue="general">
<SelectTrigger id="feedback-category" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem id="general">General</SelectItem>
<SelectItem id="bug">Bug Report</SelectItem>
<SelectItem id="feature">Feature Request</SelectItem>
<SelectItem id="improvement">Improvement</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="feedback-message">Message</FieldLabel>
<Textarea
id="feedback-message"
placeholder="Tell us what's on your mind..."
className="min-h-24 resize-none"
/>
</Field>
</FieldGroup>
<DialogFooter>
<DialogClose variant="outline">Cancel</DialogClose>
<Button>Submit</Button>
</DialogFooter>
</Dialog>
</DialogTrigger>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,199 @@
"use client"
import * as React from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/bases/aria/ui/alert-dialog"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import { ButtonGroup } from "@/registry/bases/aria/ui/button-group"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import { Field, FieldGroup } from "@/registry/bases/aria/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/registry/bases/aria/ui/input-group"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/bases/aria/ui/radio-group"
import { Slider } from "@/registry/bases/aria/ui/slider"
import { Switch } from "@/registry/bases/aria/ui/switch"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function UIElements() {
const [sliderValue, setSliderValue] = React.useState<number[]>([500])
const handleSliderValueChange = React.useCallback(
(value: number | readonly number[]) => {
if (typeof value === "number") {
setSliderValue([value])
} else {
setSliderValue([...value])
}
},
[]
)
return (
<Card className="w-full">
<CardContent className="flex flex-col gap-6">
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-2">
<Button>Button</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
</div>
<Item variant="outline">
<ItemContent>
<ItemTitle>Two-factor authentication</ItemTitle>
<ItemDescription className="text-pretty xl:hidden 2xl:block">
Verify via email or phone number.
</ItemDescription>
</ItemContent>
<ItemActions className="hidden md:flex">
<Button size="sm" variant="secondary">
Enable
</Button>
</ItemActions>
</Item>
</div>
<Slider
value={sliderValue}
onChange={handleSliderValueChange}
maxValue={1000}
minValue={0}
step={10}
className="flex-1"
aria-label="Slider"
/>
<FieldGroup>
<Field>
<InputGroup>
<InputGroupInput placeholder="Name" />
<InputGroupAddon align="inline-end">
<InputGroupText>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
<Field className="flex-1">
<Textarea placeholder="Message" className="resize-none" />
</Field>
</FieldGroup>
<div className="flex items-center gap-2">
<div className="flex gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<RadioGroup defaultValue="apple" className="ml-auto flex w-fit gap-3">
<RadioGroupItem value="apple" />
<RadioGroupItem value="banana" />
</RadioGroup>
<div className="flex gap-3">
<Checkbox defaultSelected />
<Checkbox />
</div>
</div>
<div className="flex items-center gap-4">
<AlertDialogTrigger>
<Button variant="outline">
<span className="hidden md:flex style-sera:md:hidden">
Alert Dialog
</span>
<span className="flex md:hidden style-sera:md:flex">Dialog</span>
</Button>
<AlertDialog size="sm">
<AlertDialogHeader>
<AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
<AlertDialogDescription>
Do you want to allow the USB accessory to connect to this
device and your data?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
<AlertDialogAction>Allow</AlertDialogAction>
</AlertDialogFooter>
</AlertDialog>
</AlertDialogTrigger>
<ButtonGroup>
<Button variant="outline">
<span className="style-sera:hidden">Button Group</span>
<span className="hidden style-sera:block">Group</span>
</Button>
<DropdownMenuTrigger>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="ChevronUpIcon"
tabler="IconChevronUp"
hugeicons="ArrowUp01Icon"
phosphor="CaretUpIcon"
remixicon="RiArrowUpSLine"
/>
</Button>
<DropdownMenu placement="top end" className="w-fit">
<DropdownMenuGroup>
<DropdownMenuLabel>Quick Actions</DropdownMenuLabel>
<DropdownMenuItem>Mute Conversation</DropdownMenuItem>
<DropdownMenuItem>Mark as Read</DropdownMenuItem>
<DropdownMenuItem>Block User</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Conversation</DropdownMenuLabel>
<DropdownMenuItem>Share Conversation</DropdownMenuItem>
<DropdownMenuItem>Copy Conversation</DropdownMenuItem>
<DropdownMenuItem>Report Conversation</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</ButtonGroup>
<Switch defaultSelected className="ml-auto" />
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,134 @@
"use client"
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Item,
ItemActions,
ItemContent,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
// Usage items with percentage and value.
const items = [
{
name: "Edge Requests",
value: "$1.83K",
percentage: 67.34,
},
{
name: "Fast Data Transfer",
percentage: 52.18,
value: "$952.51",
},
{
name: "Monitoring data points",
percentage: 89.42,
value: "$901.20",
},
{
name: "Web Analytics Events",
percentage: 45.67,
value: "$603.71",
},
{
name: "ISR Writes",
percentage: 26.23,
value: "524.52K / 2M",
},
{
name: "Function Duration",
percentage: 5.11,
value: "5.11 GB Hrs / 1K GB Hrs",
},
]
// Circular gauge SVG component for displaying a percentage.
function CircularGauge({ percentage }: { percentage: number }) {
const normalizedPercentage = Math.min(Math.max(percentage, 0), 100)
const circumference = 2 * Math.PI * 42.5
const strokePercent = (normalizedPercentage / 100) * circumference
return (
<svg
aria-hidden="true"
fill="none"
height="16"
width="16"
strokeWidth="2"
viewBox="0 0 100 100"
className="-rotate-90"
>
<circle
cx="50"
cy="50"
r="42.5"
strokeWidth="12"
strokeDashoffset="0"
strokeLinecap="round"
strokeLinejoin="round"
className="opacity-20"
stroke="currentColor"
style={{
strokeDasharray: `${circumference} ${circumference}`,
}}
/>
<circle
cx="50"
cy="50"
r="42.5"
strokeWidth="12"
strokeDashoffset="0"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
className="transition-all duration-300"
style={{
strokeDasharray: `${strokePercent} ${circumference}`,
}}
/>
</svg>
)
}
export function UsageCard() {
return (
<Card className="w-full max-w-sm gap-4">
<CardHeader>
<CardTitle className="px-1 text-sm">
5 days remaining in cycle
</CardTitle>
</CardHeader>
<CardContent>
<ItemGroup className="gap-0">
{items.map((item) => (
<Item
href="#"
key={item.name}
size="xs"
className="px-0 group-hover/item-group:bg-transparent"
>
<ItemMedia variant="icon" className="text-primary">
<CircularGauge percentage={item.percentage} />
</ItemMedia>
<ItemContent className="inline-block truncate">
<ItemTitle className="inline">{item.name}</ItemTitle>
</ItemContent>
<ItemActions>
<span className="font-mono text-xs font-medium text-muted-foreground tabular-nums">
{item.value}
</span>
</ItemActions>
</Item>
))}
</ItemGroup>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,91 @@
"use client"
import { Area, AreaChart, XAxis } from "recharts"
import { Badge } from "@/registry/bases/aria/ui/badge"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/ui/chart"
const areaChartData = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 },
]
const areaChartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
} satisfies ChartConfig
const latestVisitors = areaChartData[areaChartData.length - 1]?.desktop ?? 0
const previousVisitors =
areaChartData[areaChartData.length - 2]?.desktop ?? latestVisitors
const trendPercent =
previousVisitors === 0
? 0
: Math.round(((latestVisitors - previousVisitors) / previousVisitors) * 100)
const trendPrefix = trendPercent > 0 ? "+" : ""
export function Visitors() {
return (
<Card className="pb-0">
<CardHeader>
<CardTitle>Visitors</CardTitle>
<CardDescription>Last 6 months </CardDescription>
<CardAction>
<Badge variant={trendPercent >= 0 ? "secondary" : "destructive"}>
{trendPrefix}
{trendPercent}% vs last month
</Badge>
</CardAction>
</CardHeader>
<CardContent className="px-0">
<ChartContainer config={areaChartConfig} className="h-48 w-full">
<AreaChart
accessibilityLayer
data={areaChartData}
margin={{ left: 0, right: 0, top: 6, bottom: 0 }}
>
<XAxis
dataKey="month"
tickLine={false}
hide
axisLine={false}
tickMargin={6}
tickFormatter={(value) => String(value).slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/>
<Area
dataKey="desktop"
type="natural"
fill="var(--color-desktop)"
fillOpacity={0.15}
stroke="var(--color-desktop)"
strokeWidth={2}
/>
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,59 @@
"use client"
import * as React from "react"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
const FITNESS_WEEKLY_LOAD = [
{ day: "M", load: 84 },
{ day: "T", load: 52 },
{ day: "W", load: 73 },
{ day: "T", load: 66 },
{ day: "F", load: 91 },
{ day: "S", load: 48 },
{ day: "S", load: 61 },
]
export function WeeklyFitnessSummary() {
return (
<Card>
<CardHeader>
<CardTitle>Weekly Fitness Summary</CardTitle>
<CardDescription>Calories and workout load by day</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="grid grid-cols-7 gap-1.5">
{FITNESS_WEEKLY_LOAD.map((day, index) => (
<div
key={`${day.day}-${index}`}
className="rounded-md p-1.5 text-center ring ring-border"
>
<div className="text-sm text-muted-foreground">{day.day}</div>
<div className="relative mt-1 h-16 overflow-hidden rounded-sm bg-muted">
<div
className="absolute inset-x-0 bottom-0 rounded-sm bg-chart-3"
style={
{
height: `${day.load}%`,
} as React.CSSProperties
}
/>
</div>
</div>
))}
</div>
</CardContent>
<CardFooter>
<Button className="w-full">View details</Button>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,99 @@
"use client"
import { ActivateAgentDialog } from "@/registry/bases/aria/blocks/preview/cards/activate-agent-dialog"
import { AnalyticsCard } from "@/registry/bases/aria/blocks/preview/cards/analytics-card"
import { AnomalyAlert } from "@/registry/bases/aria/blocks/preview/cards/anomaly-alert"
import { AssignIssue } from "@/registry/bases/aria/blocks/preview/cards/assign-issue"
import { BarChartCard } from "@/registry/bases/aria/blocks/preview/cards/bar-chart-card"
import { BarVisualizerCard } from "@/registry/bases/aria/blocks/preview/cards/bar-visualizer"
import { BookAppointment } from "@/registry/bases/aria/blocks/preview/cards/book-appointment"
import { CodespacesCard } from "@/registry/bases/aria/blocks/preview/cards/codespaces-card"
import { ContributionsActivity } from "@/registry/bases/aria/blocks/preview/cards/contributions-activity"
import { Contributors } from "@/registry/bases/aria/blocks/preview/cards/contributors"
import { EnvironmentVariables } from "@/registry/bases/aria/blocks/preview/cards/environment-variables"
import { FeedbackForm } from "@/registry/bases/aria/blocks/preview/cards/feedback-form"
import { FileUpload } from "@/registry/bases/aria/blocks/preview/cards/file-upload"
import { GithubProfile } from "@/registry/bases/aria/blocks/preview/cards/github-profile"
import { IconPreviewGrid } from "@/registry/bases/aria/blocks/preview/cards/icon-preview-grid"
import { InviteTeam } from "@/registry/bases/aria/blocks/preview/cards/invite-team"
import { Invoice } from "@/registry/bases/aria/blocks/preview/cards/invoice"
import { LiveWaveformCard } from "@/registry/bases/aria/blocks/preview/cards/live-waveform"
import { NoTeamMembers } from "@/registry/bases/aria/blocks/preview/cards/no-team-members"
import { NotFound } from "@/registry/bases/aria/blocks/preview/cards/not-found"
import { ObservabilityCard } from "@/registry/bases/aria/blocks/preview/cards/observability-card"
import { PieChartCard } from "@/registry/bases/aria/blocks/preview/cards/pie-chart-card"
import { ReportBug } from "@/registry/bases/aria/blocks/preview/cards/report-bug"
import { ShippingAddress } from "@/registry/bases/aria/blocks/preview/cards/shipping-address"
import { Shortcuts } from "@/registry/bases/aria/blocks/preview/cards/shortcuts"
import { SkeletonLoading } from "@/registry/bases/aria/blocks/preview/cards/skeleton-loading"
import { SleepReport } from "@/registry/bases/aria/blocks/preview/cards/sleep-report"
import { StyleOverview } from "@/registry/bases/aria/blocks/preview/cards/style-overview"
import { TypographySpecimen } from "@/registry/bases/aria/blocks/preview/cards/typography-specimen"
import { UIElements } from "@/registry/bases/aria/blocks/preview/cards/ui-elements"
import { UsageCard } from "@/registry/bases/aria/blocks/preview/cards/usage-card"
import { Visitors } from "@/registry/bases/aria/blocks/preview/cards/visitors"
import { WeeklyFitnessSummary } from "@/registry/bases/aria/blocks/preview/cards/weekly-fitness-summary"
export default function PreviewExample() {
return (
<div className="overflow-x-auto overflow-y-hidden bg-muted contain-[paint] [--gap:--spacing(4)] 3xl:[--gap:--spacing(12)] md:[--gap:--spacing(10)] dark:bg-background style-lyra:md:[--gap:--spacing(6)] style-mira:md:[--gap:--spacing(6)]">
<div className="flex w-full min-w-max justify-center">
<div
className="grid w-[2400px] grid-cols-7 items-start gap-(--gap) bg-muted p-(--gap) md:w-[3000px] dark:bg-background style-lyra:md:w-[2600px] style-mira:md:w-[2600px] *:[div]:gap-(--gap)"
data-slot="capture-target"
>
<div className="flex flex-col p-px [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<StyleOverview />
<TypographySpecimen />
<div className="md:hidden">
<UIElements />
</div>
<CodespacesCard />
<Invoice />
</div>
<div className="flex flex-col p-px [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<IconPreviewGrid />
<div className="hidden w-full md:flex">
<UIElements />
</div>
<ObservabilityCard />
<ShippingAddress />
</div>
<div className="flex flex-col p-px [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<EnvironmentVariables />
<BarChartCard />
<InviteTeam />
<ActivateAgentDialog />
</div>
<div className="flex flex-col p-px [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<SkeletonLoading />
<PieChartCard />
<NoTeamMembers />
<ReportBug />
<Contributors />
</div>
<div className="flex flex-col p-px [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<FeedbackForm />
<BookAppointment />
<SleepReport />
<GithubProfile />
</div>
<div className="flex flex-col p-px [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<WeeklyFitnessSummary />
<FileUpload />
<AnalyticsCard />
<UsageCard />
<Shortcuts />
</div>
<div className="flex flex-col p-px [contain-intrinsic-size:380px_1200px] [content-visibility:auto]">
<AnomalyAlert />
<LiveWaveformCard />
<Visitors />
<ContributionsActivity />
<NotFound />
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,180 @@
import * as React from "react"
import { SearchForm } from "@/registry/bases/aria/blocks/sidebar-01/components/search-form"
import { VersionSwitcher } from "@/registry/bases/aria/blocks/sidebar-01/components/version-switcher"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
} from "@/registry/bases/aria/ui/sidebar"
// This is sample data.
const data = {
versions: ["1.0.1", "1.1.0-alpha", "2.0.0-beta1"],
navMain: [
{
title: "Getting Started",
url: "#",
items: [
{
title: "Installation",
url: "#",
},
{
title: "Project Structure",
url: "#",
},
],
},
{
title: "Build Your Application",
url: "#",
items: [
{
title: "Routing",
url: "#",
},
{
title: "Data Fetching",
url: "#",
isActive: true,
},
{
title: "Rendering",
url: "#",
},
{
title: "Caching",
url: "#",
},
{
title: "Styling",
url: "#",
},
{
title: "Optimizing",
url: "#",
},
{
title: "Configuring",
url: "#",
},
{
title: "Testing",
url: "#",
},
{
title: "Authentication",
url: "#",
},
{
title: "Deploying",
url: "#",
},
{
title: "Upgrading",
url: "#",
},
{
title: "Examples",
url: "#",
},
],
},
{
title: "API Reference",
url: "#",
items: [
{
title: "Components",
url: "#",
},
{
title: "File Conventions",
url: "#",
},
{
title: "Functions",
url: "#",
},
{
title: "next.config.js Options",
url: "#",
},
{
title: "CLI",
url: "#",
},
{
title: "Edge Runtime",
url: "#",
},
],
},
{
title: "Architecture",
url: "#",
items: [
{
title: "Accessibility",
url: "#",
},
{
title: "Fast Refresh",
url: "#",
},
{
title: "Next.js Compiler",
url: "#",
},
{
title: "Supported Browsers",
url: "#",
},
{
title: "Turbopack",
url: "#",
},
],
},
],
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar {...props}>
<SidebarHeader>
<VersionSwitcher
versions={data.versions}
defaultVersion={data.versions[0]}
/>
<SearchForm />
</SidebarHeader>
<SidebarContent>
{data.navMain.map((item) => (
<SidebarGroup key={item.title}>
<SidebarGroupLabel>{item.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{item.items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton href={item.url} isActive={item.isActive}>
{item.title}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
<SidebarRail />
</Sidebar>
)
}

View File

@@ -0,0 +1,36 @@
"use client"
import { Label } from "@/registry/bases/aria/ui/label"
import {
SidebarGroup,
SidebarGroupContent,
SidebarInput,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
return (
<form {...props}>
<SidebarGroup className="py-0">
<SidebarGroupContent className="relative">
<Label htmlFor="search" className="sr-only">
Search
</Label>
<SidebarInput
id="search"
placeholder="Search the docs..."
className="pl-8"
/>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none"
/>
</SidebarGroupContent>
</SidebarGroup>
</form>
)
}

View File

@@ -0,0 +1,80 @@
"use client"
import * as React from "react"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function VersionSwitcher({
versions,
defaultVersion,
}: {
versions: string[]
defaultVersion: string
}) {
const [selectedVersion, setSelectedVersion] = React.useState(defaultVersion)
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenuTrigger>
<SidebarMenuButton
size="lg"
className="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground"
>
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<IconPlaceholder
lucide="GalleryVerticalEndIcon"
tabler="IconLayoutRows"
hugeicons="LayoutBottomIcon"
phosphor="RowsIcon"
remixicon="RiGalleryLine"
className="size-4"
/>
</div>
<div className="flex flex-col gap-0.5 leading-none">
<span className="font-medium">Documentation</span>
<span className="">v{selectedVersion}</span>
</div>
<IconPlaceholder
lucide="ChevronsUpDownIcon"
tabler="IconSelector"
hugeicons="UnfoldMoreIcon"
phosphor="CaretUpDownIcon"
remixicon="RiArrowUpDownLine"
className="ml-auto"
/>
</SidebarMenuButton>
<DropdownMenu placement="bottom start">
{versions.map((version) => (
<DropdownMenuItem
key={version}
onAction={() => setSelectedVersion(version)}
>
v{version}{" "}
{version === selectedVersion && (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
className="ml-auto"
/>
)}
</DropdownMenuItem>
))}
</DropdownMenu>
</DropdownMenuTrigger>
</SidebarMenuItem>
</SidebarMenu>
)
}

View File

@@ -0,0 +1,50 @@
import { AppSidebar } from "@/registry/bases/aria/blocks/sidebar-01/components/app-sidebar"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
} from "@/registry/bases/aria/ui/breadcrumb"
import { Separator } from "@/registry/bases/aria/ui/separator"
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/registry/bases/aria/ui/sidebar"
export default function Page() {
return (
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger className="-ml-1" />
<Separator
orientation="vertical"
className="mr-2 data-vertical:h-4 data-vertical:self-auto"
/>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:inline-flex">
<BreadcrumbLink href="#">Build Your Application</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
<div className="min-h-screen flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,223 @@
"use client"
import * as React from "react"
import { SearchForm } from "@/registry/bases/aria/blocks/sidebar-02/components/search-form"
import { VersionSwitcher } from "@/registry/bases/aria/blocks/sidebar-02/components/version-switcher"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/bases/aria/ui/collapsible"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
// This is sample data.
const data = {
versions: ["1.0.1", "1.1.0-alpha", "2.0.0-beta1"],
navMain: [
{
title: "Getting Started",
url: "#",
items: [
{
title: "Installation",
url: "#",
},
{
title: "Project Structure",
url: "#",
},
],
},
{
title: "Build Your Application",
url: "#",
items: [
{
title: "Routing",
url: "#",
},
{
title: "Data Fetching",
url: "#",
isActive: true,
},
{
title: "Rendering",
url: "#",
},
{
title: "Caching",
url: "#",
},
{
title: "Styling",
url: "#",
},
{
title: "Optimizing",
url: "#",
},
{
title: "Configuring",
url: "#",
},
{
title: "Testing",
url: "#",
},
{
title: "Authentication",
url: "#",
},
{
title: "Deploying",
url: "#",
},
{
title: "Upgrading",
url: "#",
},
{
title: "Examples",
url: "#",
},
],
},
{
title: "API Reference",
url: "#",
items: [
{
title: "Components",
url: "#",
},
{
title: "File Conventions",
url: "#",
},
{
title: "Functions",
url: "#",
},
{
title: "next.config.js Options",
url: "#",
},
{
title: "CLI",
url: "#",
},
{
title: "Edge Runtime",
url: "#",
},
],
},
{
title: "Architecture",
url: "#",
items: [
{
title: "Accessibility",
url: "#",
},
{
title: "Fast Refresh",
url: "#",
},
{
title: "Next.js Compiler",
url: "#",
},
{
title: "Supported Browsers",
url: "#",
},
{
title: "Turbopack",
url: "#",
},
],
},
{
title: "Community",
url: "#",
items: [
{
title: "Contribution Guide",
url: "#",
},
],
},
],
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar {...props}>
<SidebarHeader>
<VersionSwitcher
versions={data.versions}
defaultVersion={data.versions[0]}
/>
<SearchForm />
</SidebarHeader>
<SidebarContent className="gap-0">
{/* We create a collapsible SidebarGroup for each parent. */}
{data.navMain.map((item) => (
<Collapsible
key={item.title}
defaultExpanded
className="group/collapsible"
>
<SidebarGroup>
<SidebarGroupLabel
className="group/label text-sm text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
elementType={CollapsibleTrigger}
>
{item.title}{" "}
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="ml-auto transition-transform group-data-expanded/collapsible:rotate-90"
/>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent>
<SidebarMenu>
{item.items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
href={item.url}
isActive={item.isActive}
>
{item.title}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>
</Collapsible>
))}
</SidebarContent>
<SidebarRail />
</Sidebar>
)
}

View File

@@ -0,0 +1,36 @@
"use client"
import { Label } from "@/registry/bases/aria/ui/label"
import {
SidebarGroup,
SidebarGroupContent,
SidebarInput,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
return (
<form {...props}>
<SidebarGroup className="py-0">
<SidebarGroupContent className="relative">
<Label htmlFor="search" className="sr-only">
Search
</Label>
<SidebarInput
id="search"
placeholder="Search the docs..."
className="pl-8"
/>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none"
/>
</SidebarGroupContent>
</SidebarGroup>
</form>
)
}

View File

@@ -0,0 +1,80 @@
"use client"
import * as React from "react"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/registry/bases/aria/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function VersionSwitcher({
versions,
defaultVersion,
}: {
versions: string[]
defaultVersion: string
}) {
const [selectedVersion, setSelectedVersion] = React.useState(defaultVersion)
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenuTrigger>
<SidebarMenuButton
size="lg"
className="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground"
>
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<IconPlaceholder
lucide="GalleryVerticalEndIcon"
tabler="IconLayoutRows"
hugeicons="LayoutBottomIcon"
phosphor="RowsIcon"
remixicon="RiGalleryLine"
className="size-4"
/>
</div>
<div className="flex flex-col gap-0.5 leading-none">
<span className="font-medium">Documentation</span>
<span className="">v{selectedVersion}</span>
</div>
<IconPlaceholder
lucide="ChevronsUpDownIcon"
tabler="IconSelector"
hugeicons="UnfoldMoreIcon"
phosphor="CaretUpDownIcon"
remixicon="RiArrowUpDownLine"
className="ml-auto"
/>
</SidebarMenuButton>
<DropdownMenu placement="bottom start">
{versions.map((version) => (
<DropdownMenuItem
key={version}
onAction={() => setSelectedVersion(version)}
>
v{version}{" "}
{version === selectedVersion && (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
className="ml-auto"
/>
)}
</DropdownMenuItem>
))}
</DropdownMenu>
</DropdownMenuTrigger>
</SidebarMenuItem>
</SidebarMenu>
)
}

Some files were not shown because too many files have changed in this diff Show More