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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
DisclosurePanel as AccordionContentPrimitive,
Heading as AccordionHeaderPrimitive,
Disclosure as AccordionItemPrimitive,
DisclosureGroup as AccordionPrimitive,
Button as AccordionTriggerPrimitive,
type ButtonProps,
type DisclosureGroupProps,
type DisclosurePanelProps,
type DisclosureProps,
} from "react-aria-components"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function Accordion({ className, ...props }: DisclosureGroupProps) {
return (
<AccordionPrimitive
data-slot="accordion"
className={cn("cn-accordion flex w-full flex-col", className)}
{...props}
/>
)
}
function AccordionItem({ className, ...props }: DisclosureProps) {
return (
<AccordionItemPrimitive
data-slot="accordion-item"
className={cn("cn-accordion-item", className)}
{...props}
/>
)
}
function AccordionTrigger({
className,
children,
...props
}: Omit<ButtonProps, "children"> & { children: React.ReactNode }) {
return (
<AccordionHeaderPrimitive className="flex">
<AccordionTriggerPrimitive
slot="trigger"
data-slot="accordion-trigger"
className={cn(
"cn-accordion-trigger group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50",
className
)}
{...props}
>
{children}
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
data-slot="accordion-trigger-icon"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
className="cn-accordion-trigger-icon pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
/>
<IconPlaceholder
lucide="ChevronUpIcon"
tabler="IconChevronUp"
data-slot="accordion-trigger-icon"
hugeicons="ArrowUp01Icon"
phosphor="CaretUpIcon"
remixicon="RiArrowUpSLine"
className="cn-accordion-trigger-icon pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
/>
</AccordionTriggerPrimitive>
</AccordionHeaderPrimitive>
)
}
function AccordionContent({
className,
children,
...props
}: DisclosurePanelProps) {
return (
<AccordionContentPrimitive
data-slot="accordion-content"
className="cn-accordion-content h-(--disclosure-panel-height) overflow-clip transition-[height]"
{...props}
>
<div
className={cn(
"cn-accordion-content-inner [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
className
)}
>
{children}
</div>
</AccordionContentPrimitive>
)
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -0,0 +1,205 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Dialog as AlertDialogPrimitive,
DialogTrigger as AlertDialogTriggerPrimitive,
Heading,
ModalOverlay as ModalOverlayPrimitive,
Modal as ModalPrimitive,
type DialogProps as AlertDialogPrimitiveProps,
type DialogTriggerProps as AlertDialogTriggerPrimitiveProps,
type ModalOverlayProps as ModalOverlayPrimitiveProps,
} from "react-aria-components"
import { Button } from "@/registry/bases/aria/ui/button"
function AlertDialogTrigger({ ...props }: AlertDialogTriggerPrimitiveProps) {
return (
<AlertDialogTriggerPrimitive data-slot="alert-dialog-trigger" {...props} />
)
}
function AlertDialogOverlay({
className,
children,
...props
}: Omit<ModalOverlayPrimitiveProps, "className" | "children"> & {
className?: string
children: React.ReactNode
}) {
return (
<ModalOverlayPrimitive
data-slot="alert-dialog-overlay"
className={cn(
"cn-alert-dialog-overlay-aria fixed inset-0 isolate z-50",
className
)}
{...props}
>
{children}
</ModalOverlayPrimitive>
)
}
function AlertDialog({
className,
size = "default",
children,
...props
}: Omit<ModalOverlayPrimitiveProps, "className" | "children"> &
Pick<React.ComponentProps<typeof ModalPrimitive>, "isDismissable"> & {
className?: string
size?: "default" | "sm"
children: React.ReactNode
}) {
return (
<AlertDialogOverlay {...props}>
<ModalPrimitive
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"cn-alert-dialog-content-aria group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
className
)}
>
<AlertDialogPrimitive
data-slot="alert-dialog"
role="alertdialog"
className="[display:inherit] [gap:inherit] outline-none"
>
{children}
</AlertDialogPrimitive>
</ModalPrimitive>
</AlertDialogOverlay>
)
}
function AlertDialogContent({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof AlertDialog>) {
return (
<AlertDialog className={className} size={size} {...props}>
{children}
</AlertDialog>
)
}
function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
className={cn("cn-alert-dialog-header", className)}
{...props}
/>
)
}
function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
"cn-alert-dialog-footer flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className
)}
{...props}
/>
)
}
function AlertDialogMedia({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-media"
className={cn("cn-alert-dialog-media", className)}
{...props}
/>
)
}
function AlertDialogTitle({
className,
...props
}: Omit<React.ComponentProps<typeof Heading>, "slot">) {
return (
<Heading
slot="title"
data-slot="alert-dialog-title"
className={cn("cn-alert-dialog-title cn-font-heading", className)}
{...props}
/>
)
}
function AlertDialogDescription({
className,
...props
}: Omit<React.ComponentProps<"div">, "slot">) {
return (
<div
data-slot="alert-dialog-description"
className={cn("cn-alert-dialog-description", className)}
{...props}
/>
)
}
function AlertDialogAction({
className,
...props
}: React.ComponentProps<typeof Button>) {
return (
<Button
slot="close"
data-slot="alert-dialog-action"
className={cn("cn-alert-dialog-action", className)}
{...props}
/>
)
}
function AlertDialogCancel({
className,
variant = "outline",
size = "default",
...props
}: React.ComponentProps<typeof Button>) {
return (
<Button
slot="close"
data-slot="alert-dialog-cancel"
className={cn("cn-alert-dialog-cancel", className)}
variant={variant}
size={size}
{...props}
/>
)
}
export {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogOverlay,
AlertDialogTitle,
AlertDialogTrigger,
}

View File

@@ -0,0 +1,71 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
const alertVariants = cva("cn-alert group/alert relative w-full", {
variants: {
variant: {
default: "cn-alert-variant-default",
destructive: "cn-alert-variant-destructive",
},
},
defaultVariants: {
variant: "default",
},
})
function Alert({
className,
variant,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-title"
className={cn(
"cn-alert-title [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"cn-alert-description [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-action"
className={cn("cn-alert-action", className)}
{...props}
/>
)
}
export { Alert, AlertTitle, AlertDescription, AlertAction }

View File

@@ -0,0 +1,22 @@
import { cn } from "cn"
function AspectRatio({
ratio,
className,
...props
}: React.ComponentProps<"div"> & { ratio: number }) {
return (
<div
data-slot="aspect-ratio"
style={
{
"--ratio": ratio,
} as React.CSSProperties
}
className={cn("relative aspect-(--ratio)", className)}
{...props}
/>
)
}
export { AspectRatio }

View File

@@ -0,0 +1,222 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
const attachmentVariants = cva(
"cn-attachment group/attachment relative flex max-w-full min-w-0 shrink-0 flex-wrap border bg-card text-card-foreground transition-colors has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
{
variants: {
size: {
default: "cn-attachment-size-default",
sm: "cn-attachment-size-sm",
xs: "cn-attachment-size-xs",
},
orientation: {
horizontal: "cn-attachment-orientation-horizontal items-center",
vertical: "cn-attachment-orientation-vertical flex-col",
},
},
}
)
function Attachment({
className,
state = "done",
size = "default",
orientation = "horizontal",
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof attachmentVariants> & {
state?: "idle" | "uploading" | "processing" | "error" | "done"
}) {
const resolvedOrientation = orientation ?? "horizontal"
return (
<div
data-slot="attachment"
data-state={state}
data-size={size}
data-orientation={resolvedOrientation}
className={cn(attachmentVariants({ size, orientation }), className)}
{...props}
/>
)
}
const attachmentMediaVariants = cva(
"cn-attachment-media relative flex aspect-square shrink-0 items-center justify-center overflow-hidden group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive [&_svg]:pointer-events-none",
{
variants: {
variant: {
icon: "cn-attachment-media-variant-icon",
image:
"cn-attachment-media-variant-image *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover",
},
},
defaultVariants: {
variant: "icon",
},
}
)
function AttachmentMedia({
className,
variant = "icon",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof attachmentMediaVariants>) {
return (
<div
data-slot="attachment-media"
data-variant={variant}
className={cn(attachmentMediaVariants({ variant }), className)}
{...props}
/>
)
}
function AttachmentContent({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="attachment-content"
className={cn(
"cn-attachment-content max-w-full min-w-0 flex-1",
className
)}
{...props}
/>
)
}
function AttachmentTitle({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="attachment-title"
className={cn(
"cn-attachment-title block max-w-full min-w-0 truncate group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",
className
)}
{...props}
/>
)
}
function AttachmentDescription({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="attachment-description"
className={cn(
"cn-attachment-description block min-w-0 truncate text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",
"max-w-full",
className
)}
{...props}
/>
)
}
function AttachmentActions({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="attachment-actions"
className={cn(
"cn-attachment-actions flex shrink-0 items-center",
className
)}
{...props}
/>
)
}
function AttachmentAction({
className,
variant,
size = "icon-xs",
...props
}: React.ComponentProps<typeof Button>) {
return (
<Button
data-slot="attachment-action"
variant={variant ?? "ghost"}
size={size}
className={cn("cn-attachment-action", className)}
{...props}
/>
)
}
function AttachmentTrigger({
className,
render,
type,
children,
...props
}: React.ComponentProps<"button"> & {
render?: (props: React.HTMLAttributes<HTMLElement>) => React.ReactNode
}) {
if (render) {
const renderProps = {
...props,
"data-slot": "attachment-trigger",
className: cn(
"cn-attachment-trigger absolute inset-0 z-10 outline-none",
className
),
children,
}
return render(renderProps)
}
return (
<button
{...props}
type={type ?? "button"}
data-slot="attachment-trigger"
className={cn(
"cn-attachment-trigger absolute inset-0 z-10 outline-none",
className
)}
>
{children}
</button>
)
}
function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="attachment-group"
className={cn(
"cn-attachment-group flex min-w-0 scroll-fade-x snap-x snap-mandatory scrollbar-none overflow-x-auto overscroll-x-contain *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start",
className
)}
{...props}
/>
)
}
export {
Attachment,
AttachmentGroup,
AttachmentMedia,
AttachmentContent,
AttachmentTitle,
AttachmentDescription,
AttachmentActions,
AttachmentAction,
AttachmentTrigger,
}

View File

@@ -0,0 +1,114 @@
"use client"
import * as React from "react"
import { cn } from "cn"
function Avatar({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & {
size?: "default" | "sm" | "lg"
}) {
return (
<div
data-slot="avatar"
data-size={size}
className={cn(
"cn-avatar group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten",
className
)}
{...props}
/>
)
}
type ImageState = "loading" | "loaded" | "error"
function AvatarImage({ className, ...props }: React.ComponentProps<"img">) {
const [state, setState] = React.useState<ImageState>(
props.src ? "loading" : "error"
)
return (
<img
data-slot="avatar-image"
alt={props.alt || ""}
data-state={state}
onLoad={() => setState("loaded")}
onError={() => setState("error")}
className={cn(
"cn-avatar-image peer aspect-square size-full object-cover data-[state=error]:hidden",
className
)}
{...props}
/>
)
}
function AvatarFallback({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-fallback"
className={cn(
"cn-avatar-fallback flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs peer-data-[state=error]:flex peer-[*]:hidden",
className
)}
{...props}
/>
)
}
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="avatar-badge"
className={cn(
"cn-avatar-badge absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
className
)}
{...props}
/>
)
}
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group"
className={cn(
"cn-avatar-group group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className
)}
{...props}
/>
)
}
function AvatarGroupCount({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group-count"
className={cn(
"cn-avatar-group-count relative flex shrink-0 items-center justify-center ring-2 ring-background",
"",
className
)}
{...props}
/>
)
}
export {
Avatar,
AvatarImage,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarBadge,
}

View File

@@ -0,0 +1,53 @@
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
const badgeVariants = cva(
"cn-badge group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none",
{
variants: {
variant: {
default: "cn-badge-variant-default",
secondary: "cn-badge-variant-secondary",
destructive: "cn-badge-variant-destructive",
outline: "cn-badge-variant-outline",
ghost: "cn-badge-variant-ghost",
link: "cn-badge-variant-link",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant = "default",
render,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & {
render?: (props: React.HTMLAttributes<HTMLElement>) => React.ReactNode
}) {
if (render) {
const renderProps = {
"data-slot": "badge",
"data-variant": variant,
className: cn(badgeVariants({ variant }), className),
...props,
}
return render(renderProps)
}
return (
<span
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,140 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Breadcrumb as BreadcrumbPrimitive,
Breadcrumbs as BreadcrumbsPrimitive,
composeRenderProps,
Link as LinkPrimitive,
type BreadcrumbProps,
type BreadcrumbsProps,
type LinkProps,
} from "react-aria-components"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
return (
<nav
aria-label="breadcrumb"
data-slot="breadcrumb"
className={cn("cn-breadcrumb", className)}
{...props}
/>
)
}
function BreadcrumbList<T extends object>({
className,
...props
}: BreadcrumbsProps<T>) {
return (
<BreadcrumbsPrimitive
data-slot="breadcrumb-list"
className={cn(
"cn-breadcrumb-list flex flex-wrap items-center wrap-break-word",
className
)}
{...props}
/>
)
}
function BreadcrumbItem({
className,
children,
separatorClassName,
...props
}: BreadcrumbProps & { separatorClassName?: string }) {
return (
<BreadcrumbPrimitive
data-slot="breadcrumb-item"
className={cn("cn-breadcrumb-item inline-flex items-center", className)}
{...props}
>
{composeRenderProps(children, (children, { isCurrent }) => (
<>
{children}
{!isCurrent && (
<span
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn("cn-breadcrumb-separator", separatorClassName)}
>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="cn-rtl-flip"
/>
</span>
)}
</>
))}
</BreadcrumbPrimitive>
)
}
function BreadcrumbLink({ className, render, ...props }: LinkProps) {
return (
<LinkPrimitive
data-slot="breadcrumb-link"
className={cn("cn-breadcrumb-link", className)}
render={render}
{...props}
/>
)
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn("cn-breadcrumb-page", className)}
{...props}
/>
)
}
function BreadcrumbEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn(
"cn-breadcrumb-ellipsis flex items-center justify-center",
className
)}
{...props}
>
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">More</span>
</span>
)
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbEllipsis,
}

View File

@@ -0,0 +1,131 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
function BubbleGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="bubble-group"
className={cn("cn-bubble-group flex min-w-0 flex-col", className)}
{...props}
/>
)
}
const bubbleVariants = cva(
"cn-bubble group/bubble relative flex w-fit min-w-0 flex-col",
{
variants: {
variant: {
default: "cn-bubble-variant-default",
secondary: "cn-bubble-variant-secondary",
muted: "cn-bubble-variant-muted",
tinted: "cn-bubble-variant-tinted",
outline: "cn-bubble-variant-outline",
ghost: "cn-bubble-variant-ghost",
destructive: "cn-bubble-variant-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Bubble({
variant = "default",
align = "start",
className,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof bubbleVariants> & {
align?: "start" | "end"
}) {
return (
<div
data-slot="bubble"
data-variant={variant}
data-align={align}
className={cn(bubbleVariants({ variant }), className)}
{...props}
/>
)
}
function BubbleContent({
className,
render,
children,
...props
}: React.ComponentProps<"div"> & {
render?: (props: React.HTMLAttributes<HTMLElement>) => React.ReactNode
}) {
if (render) {
const renderProps = {
...props,
"data-slot": "bubble-content",
className: cn(
"cn-bubble-content w-fit max-w-full min-w-0 overflow-hidden wrap-break-word [button]:text-left [button,a]:transition-colors",
className
),
children,
}
return render(renderProps)
}
return (
<div
{...props}
data-slot="bubble-content"
className={cn(
"cn-bubble-content w-fit max-w-full min-w-0 overflow-hidden wrap-break-word [button]:text-left [button,a]:transition-colors",
className
)}
>
{children}
</div>
)
}
const bubbleReactionsVariants = cva(
"cn-bubble-reactions absolute z-10 flex w-fit items-center justify-center",
{
variants: {
side: {
top: "cn-bubble-reactions-side-top",
bottom: "cn-bubble-reactions-side-bottom",
},
align: {
start: "cn-bubble-reactions-align-start",
end: "cn-bubble-reactions-align-end",
},
},
defaultVariants: {
side: "bottom",
align: "end",
},
}
)
function BubbleReactions({
side = "bottom",
align = "end",
className,
...props
}: React.ComponentProps<"div"> & {
align?: "start" | "end"
side?: "top" | "bottom"
}) {
return (
<div
data-slot="bubble-reactions"
data-align={align}
data-side={side}
className={cn(bubbleReactionsVariants({ side, align }), className)}
{...props}
/>
)
}
export { BubbleGroup, Bubble, BubbleContent, BubbleReactions }

View File

@@ -0,0 +1,94 @@
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { Separator } from "@/registry/bases/aria/ui/separator"
const buttonGroupVariants = cva(
"cn-button-group flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
{
variants: {
orientation: {
horizontal:
"cn-button-group-orientation-horizontal **:data-slot:rounded-r-none [&_[data-slot]~[data-slot]]:rounded-l-none [&_[data-slot]~[data-slot]]:border-l-0",
vertical:
"cn-button-group-orientation-vertical flex-col **:data-slot:rounded-b-none [&_[data-slot]~[data-slot]]:rounded-t-none [&_[data-slot]~[data-slot]]:border-t-0",
},
},
defaultVariants: {
orientation: "horizontal",
},
}
)
function ButtonGroup({
className,
orientation,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
return (
<div
role="group"
data-slot="button-group"
data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)}
{...props}
/>
)
}
function ButtonGroupText({
className,
render,
...props
}: React.ComponentProps<"div"> & {
render?: (props: React.HTMLAttributes<HTMLElement>) => React.ReactNode
}) {
if (render) {
const renderProps = {
"data-slot": "button-group-text",
className: cn(
"cn-button-group-text flex items-center [&_svg]:pointer-events-none",
className
),
...props,
}
return render(renderProps)
}
return (
<div
data-slot="button-group-text"
className={cn(
"cn-button-group-text flex items-center [&_svg]:pointer-events-none",
className
)}
{...props}
/>
)
}
function ButtonGroupSeparator({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"cn-button-group-separator relative self-stretch data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
className
)}
{...props}
/>
)
}
export {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
buttonGroupVariants,
}

View File

@@ -0,0 +1,84 @@
"use client"
import type * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import {
Button as ButtonPrimitive,
Link as LinkPrimitive,
type ButtonProps as ButtonPrimitiveProps,
type LinkProps as LinkPrimitiveProps,
} from "react-aria-components"
const buttonVariants = cva(
"cn-button group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "cn-button-variant-default",
outline: "cn-button-variant-outline",
secondary: "cn-button-variant-secondary",
ghost: "cn-button-variant-ghost",
destructive: "cn-button-variant-destructive",
link: "cn-button-variant-link",
},
size: {
default: "cn-button-size-default",
xs: "cn-button-size-xs",
sm: "cn-button-size-sm",
lg: "cn-button-size-lg",
icon: "cn-button-size-icon",
"icon-xs": "cn-button-size-icon-xs",
"icon-sm": "cn-button-size-icon-sm",
"icon-lg": "cn-button-size-icon-lg",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
...props
}: Omit<ButtonPrimitiveProps, "className"> &
React.RefAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants> & {
className?: string
}) {
return (
<ButtonPrimitive
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
function LinkButton({
className,
variant = "default",
size = "default",
...props
}: Omit<LinkPrimitiveProps, "className"> &
VariantProps<typeof buttonVariants> & {
className?: string
}) {
return (
<LinkPrimitive
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, LinkButton, buttonVariants }

View File

@@ -0,0 +1,287 @@
"use client"
import * as React from "react"
import { cva } from "class-variance-authority"
import { cn } from "cn"
import {
Calendar as AriaCalendar,
CalendarGridHeader as AriaCalendarGridHeader,
RangeCalendar as AriaRangeCalendar,
CalendarCell,
CalendarGrid,
CalendarGridBody,
CalendarHeaderCell,
CalendarHeading,
CalendarMonthPicker,
CalendarYearPicker,
type CalendarCellRenderProps,
type CalendarProps,
type DateValue,
type RangeCalendarProps,
} from "react-aria-components"
import { Button, buttonVariants } from "@/registry/bases/aria/ui/button"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const cellVariants = cva(
"group/day relative mt-2 aspect-square h-full w-full cursor-default rounded-(--cell-radius) p-0 text-center select-none [&:is(:last-child>[data-selected=true])>div]:rounded-r-(--cell-radius)",
{
variants: {
showWeekNumber: {
false:
"[&:is(:first-child>[data-selected=true])>div]:rounded-l-(--cell-radius)",
true: "[&:is(:nth-child(2)>[data-selected=true])>div]:rounded-l-(--cell-radius)",
},
isToday: {
true: "rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
},
isSelectionStart: {
true: "relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
},
isSelectionEnd: {
true: "relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
},
isUnavailable: {
true: "text-muted-foreground opacity-50 [&>div]:line-through",
},
isDisabled: {
true: "text-muted-foreground opacity-50",
},
isOutsideMonth: {
true: "text-muted-foreground aria-selected:text-muted-foreground",
},
},
}
)
function Calendar<
T extends DateValue,
M extends "single" | "multiple" = "single",
>(
props: Omit<CalendarProps<T, M>, "visibleDuration"> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
captionLayout?: "label" | "dropdown"
numberOfMonths?: number
showWeekNumber?: boolean
headerFormat?: Intl.DateTimeFormatOptions
renderCell?: (
renderProps: CalendarCellRenderProps & {
defaultChildren: React.ReactNode
}
) => React.ReactNode
}
) {
return (
<AriaCalendar
{...props}
data-slot="calendar"
visibleDuration={{ months: props.numberOfMonths || 1 }}
className={cn(
"cn-calendar group/calendar w-fit bg-background in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
props.className
)}
>
<CalendarInner {...props} />
</AriaCalendar>
)
}
function RangeCalendar<T extends DateValue>(
props: RangeCalendarProps<T> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
captionLayout?: "label" | "dropdown"
headerFormat?: Intl.DateTimeFormatOptions
numberOfMonths?: number
showWeekNumber?: boolean
renderCell?: (
renderProps: CalendarCellRenderProps & {
defaultChildren: React.ReactNode
}
) => React.ReactNode
}
) {
return (
<AriaRangeCalendar
{...props}
data-slot="calendar"
visibleDuration={{ months: props.numberOfMonths || 1 }}
className={cn(
"cn-calendar group/calendar w-fit bg-background in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
props.className
)}
>
<CalendarInner {...props} isRange />
</AriaRangeCalendar>
)
}
function CalendarInner({
captionLayout = "label",
buttonVariant = "ghost",
numberOfMonths = 1,
showWeekNumber = false,
headerFormat,
renderCell,
isRange,
}: {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
captionLayout?: "label" | "dropdown"
numberOfMonths?: number
showWeekNumber?: boolean
headerFormat?: Intl.DateTimeFormatOptions
renderCell?: (
renderProps: CalendarCellRenderProps & { defaultChildren: React.ReactNode }
) => React.ReactNode
isRange?: boolean
}) {
return (
<div className="relative flex flex-col gap-4 md:flex-row">
<header className="absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1">
<Button
variant={buttonVariant}
slot="previous"
className="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
>
<IconPlaceholder
lucide="ChevronLeftIcon"
tabler="IconChevronLeft"
hugeicons="ArrowLeftIcon"
phosphor="CaretLeftIcon"
remixicon="RiArrowLeftSLine"
className="cn-rtl-flip size-4"
/>
</Button>
<Button
variant={buttonVariant}
slot="next"
className="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRightIcon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="cn-rtl-flip size-4"
/>
</Button>
</header>
{Array.from({ length: numberOfMonths }, (_, i) => (
<div key={i} className="flex w-full flex-col gap-4">
<div className="flex h-(--cell-size) w-full items-center justify-center gap-1 px-(--cell-size)">
{captionLayout === "dropdown" ? (
<>
<MonthDropdown format={headerFormat} />
<YearDropdown format={headerFormat} />
</>
) : (
<CalendarHeading
offset={{ months: i }}
format={headerFormat}
className="cn-calendar-caption text-sm font-medium select-none"
/>
)}
</div>
<CalendarGrid
className="w-full border-collapse"
offset={{ months: i }}
>
<AriaCalendarGridHeader>
{(day) => (
<CalendarHeaderCell className="rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none">
{day}
</CalendarHeaderCell>
)}
</AriaCalendarGridHeader>
<CalendarGridBody>
{(date) => (
<CalendarCell
date={date}
className={(renderProps) =>
cellVariants({ ...renderProps, showWeekNumber })
}
>
{(renderProps) => (
<div
data-selected-single={renderProps.isSelected && !isRange}
data-range-start={renderProps.isSelectionStart && isRange}
data-range-end={renderProps.isSelectionEnd && isRange}
data-range-middle={
renderProps.isSelected &&
!renderProps.isSelectionStart &&
!renderProps.isSelectionEnd &&
isRange
}
className={cn(
buttonVariants({ variant: "ghost", size: "icon" }),
"cn-calendar-day-button relative isolate z-10 flex aspect-square h-full w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70"
)}
>
{renderCell
? renderCell(renderProps)
: renderProps.defaultChildren}
</div>
)}
</CalendarCell>
)}
</CalendarGridBody>
</CalendarGrid>
</div>
))}
</div>
)
}
function MonthDropdown({ format }: { format?: Intl.DateTimeFormatOptions }) {
return (
<CalendarMonthPicker format={format?.month}>
{(props) => (
<Select {...props} className="relative">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent className="min-w-0">
<SelectGroup>
{props.items.map((item) => (
<SelectItem key={item.id} id={item.id}>
{item.formatted}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)}
</CalendarMonthPicker>
)
}
function YearDropdown({ format }: { format?: Intl.DateTimeFormatOptions }) {
return (
<CalendarYearPicker format={format}>
{(props) => (
<Select {...props} className="relative">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent className="min-w-0">
{props.items.map((item) => (
<SelectItem key={item.id} id={item.id}>
{item.formatted}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</CalendarYearPicker>
)
}
export { Calendar, RangeCalendar }

View File

@@ -0,0 +1,93 @@
import * as React from "react"
import { cn } from "cn"
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn("cn-card group/card flex flex-col", className)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"cn-card-header group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("cn-card-title cn-font-heading", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("cn-card-description", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"cn-card-action col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("cn-card-content", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("cn-card-footer flex items-center", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View File

@@ -0,0 +1,256 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { Button } from "@/registry/bases/aria/ui/button"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
function Carousel({
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
}: React.ComponentProps<"div"> & CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
data-slot="carousel-content"
>
<div
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
}
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
const { orientation } = useCarousel()
return (
<div
role="group"
aria-roledescription="slide"
data-slot="carousel-item"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
}
function CarouselPrevious({
className,
variant = "outline",
size = "icon-sm",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
data-slot="carousel-previous"
variant={variant}
size={size}
className={cn(
"cn-carousel-previous absolute touch-manipulation",
orientation === "horizontal"
? "inset-y-0 -left-12 my-auto"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
isDisabled={!canScrollPrev}
onPress={scrollPrev}
{...props}
>
<IconPlaceholder
lucide="ChevronLeftIcon"
tabler="IconChevronLeft"
hugeicons="ArrowLeft01Icon"
phosphor="CaretLeftIcon"
remixicon="RiArrowLeftSLine"
className="cn-rtl-flip"
/>
<span className="sr-only">Previous slide</span>
</Button>
)
}
function CarouselNext({
className,
variant = "outline",
size = "icon-sm",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
data-slot="carousel-next"
variant={variant}
size={size}
className={cn(
"cn-carousel-next absolute touch-manipulation",
orientation === "horizontal"
? "inset-y-0 -right-12 my-auto"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
isDisabled={!canScrollNext}
onPress={scrollNext}
{...props}
>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="cn-rtl-flip"
/>
<span className="sr-only">Next slide</span>
</Button>
)
}
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
useCarousel,
}

View File

@@ -0,0 +1,370 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import * as RechartsPrimitive from "recharts"
import type { TooltipValueType } from "recharts"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const
const INITIAL_DIMENSION = { width: 320, height: 200 } as const
type TooltipNameType = number | string
type ChartConfig = Record<
string,
{
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
>
type ChartContextProps = {
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />")
}
return context
}
function ChartContainer({
id,
className,
children,
config,
initialDimension = INITIAL_DIMENSION,
...props
}: React.ComponentProps<"div"> & {
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
initialDimension?: {
width: number
height: number
}
}) {
const uniqueId = React.useId()
const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-slot="chart"
data-chart={chartId}
className={cn(
"cn-chart flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer
initialDimension={initialDimension}
>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
)
}
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme ?? config.color
)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`
)
.join("\n"),
}}
/>
)
}
const ChartTooltip = RechartsPrimitive.Tooltip
function ChartTooltipContent({
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
} & Omit<
RechartsPrimitive.DefaultTooltipContentProps<
TooltipValueType,
TooltipNameType
>,
"accessibilityLayer"
>) {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? (config[label]?.label ?? label)
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
)
}
if (!value) {
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
className={cn("cn-chart-tooltip grid min-w-32 items-start", className)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload
.filter((item) => item.type !== "none")
.map((item, index) => {
const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color ?? item.payload?.fill ?? item.color
return (
<div
key={index}
className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center"
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
}
)}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label ?? item.name}
</span>
</div>
{item.value != null && (
<span className="font-mono font-medium text-foreground tabular-nums">
{typeof item.value === "number"
? item.value.toLocaleString()
: String(item.value)}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div>
</div>
)
}
const ChartLegend = RechartsPrimitive.Legend
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = "bottom",
nameKey,
}: React.ComponentProps<"div"> & {
hideIcon?: boolean
nameKey?: string
} & RechartsPrimitive.DefaultLegendContentProps) {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className
)}
>
{payload
.filter((item) => item.type !== "none")
.map((item, index) => {
const key = `${nameKey ?? item.dataKey ?? "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={index}
className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
)
})}
</div>
)
}
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string
) {
if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string
}
return configLabelKey in config ? config[configLabelKey] : config[key]
}
export {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
}

View File

@@ -0,0 +1,48 @@
"use client"
import { cn } from "cn"
import {
Checkbox as CheckboxPrimitive,
composeRenderProps,
type CheckboxProps,
} from "react-aria-components"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function Checkbox({ className, children, ...props }: CheckboxProps) {
return (
<CheckboxPrimitive
data-slot="checkbox"
className={cn(
"cn-checkbox cn-checkbox-aria peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
className
)}
{...props}
>
{composeRenderProps(
children,
(children, { isSelected, isIndeterminate }) => (
<>
<span
data-slot="checkbox-indicator"
className="cn-checkbox-indicator grid place-content-center text-current transition-none"
>
{(isSelected || isIndeterminate) && (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
)}
</span>
{children}
</>
)
)}
</CheckboxPrimitive>
)
}
export { Checkbox }

View File

@@ -0,0 +1,32 @@
"use client"
import {
DisclosurePanel as CollapsibleContentPrimitive,
Disclosure as CollapsiblePrimitive,
Button as CollapsibleTriggerPrimitive,
type ButtonProps,
type DisclosurePanelProps,
type DisclosureProps,
} from "react-aria-components"
function Collapsible({ ...props }: DisclosureProps) {
return <CollapsiblePrimitive data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({ ...props }: ButtonProps) {
return (
<CollapsibleTriggerPrimitive
slot="trigger"
data-slot="collapsible-trigger"
{...props}
/>
)
}
function CollapsibleContent({ ...props }: DisclosurePanelProps) {
return (
<CollapsibleContentPrimitive data-slot="collapsible-content" {...props} />
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

@@ -0,0 +1,404 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Button as ButtonPrimitive,
Collection,
ComboBox as ComboBoxPrimitive,
ComboBoxStateContext,
ComboBoxValue as ComboBoxValuePrimitive,
composeRenderProps,
Group,
Header as HeaderPrimitive,
Input as InputPrimitive,
ListBoxItem as ListBoxItemPrimitive,
ListBox as ListBoxPrimitive,
ListBoxSection as ListBoxSectionPrimitive,
Popover as PopoverPrimitive,
Separator as SeparatorPrimitive,
TagGroup as TagGroupPrimitive,
TagList as TagListPrimitive,
Tag as TagPrimitive,
type ButtonProps,
type ComboBoxValueProps,
type GroupProps,
type HeaderProps,
type InputProps,
type ListBoxItemProps,
type ListBoxProps,
type ListBoxSectionProps,
type SeparatorProps,
type TagListProps,
type TagProps,
} from "react-aria-components"
import { Button } from "@/registry/bases/aria/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function ComboboxValue<T>({ ...props }: ComboBoxValueProps<T>) {
return <ComboBoxValuePrimitive data-slot="combobox-value" {...props} />
}
function ComboboxTrigger({
className,
children,
...props
}: Omit<ButtonProps, "children"> & {
children?: React.ReactNode
}) {
return (
<ButtonPrimitive
data-slot="combobox-trigger"
className={cn("cn-combobox-trigger", className)}
{...props}
>
{children}
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
className="cn-combobox-trigger-icon pointer-events-none"
/>
</ButtonPrimitive>
)
}
function ComboboxClear({
className,
...props
}: React.ComponentProps<typeof InputGroupButton>) {
const state = React.useContext(ComboBoxStateContext)
if (state?.inputValue === "") {
return null
}
return (
<InputGroupButton
data-slot="combobox-clear"
variant="ghost"
size="icon-xs"
aria-label="Clear"
className={cn("cn-combobox-clear", className)}
onPress={() => {
state?.setValue(null)
}}
slot={null}
{...props}
>
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
className="cn-combobox-clear-icon pointer-events-none"
/>
</InputGroupButton>
)
}
function ComboboxInput({
className,
children,
disabled = false,
showTrigger = true,
showClear = false,
...props
}: React.ComponentProps<"input"> & {
showTrigger?: boolean
showClear?: boolean
}) {
return (
<InputGroup className={cn("cn-combobox-input w-auto", className)}>
<InputGroupInput disabled={disabled} {...props} />
<InputGroupAddon align="inline-end">
{showTrigger && (
<InputGroupButton
size="icon-xs"
variant="ghost"
data-slot="combobox-trigger"
className="cn-combobox-trigger group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
isDisabled={disabled}
>
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
className="cn-combobox-trigger-icon pointer-events-none"
/>
</InputGroupButton>
)}
{showClear && <ComboboxClear isDisabled={disabled} />}
</InputGroupAddon>
{children}
</InputGroup>
)
}
function ComboboxContent({
className,
placement = "bottom",
offset = 6,
crossOffset = 0,
anchor,
...props
}: Omit<
React.ComponentProps<typeof PopoverPrimitive>,
"className" | "children"
> & {
className?: string
children?: React.ReactNode
anchor?: React.RefObject<HTMLDivElement | null>
}) {
return (
<PopoverPrimitive
data-slot="combobox-content"
placement={placement}
offset={offset}
crossOffset={crossOffset}
triggerRef={anchor}
className={cn(
"cn-combobox-content-aria cn-menu-target cn-menu-translucent cn-menu-translucent-aria relative isolate z-50 w-(--trigger-width) origin-(--trigger-anchor-point)",
className
)}
{...props}
/>
)
}
function ComboboxList<T extends object>({
className,
...props
}: ListBoxProps<T>) {
return (
<ListBoxPrimitive
data-slot="combobox-list"
className={cn(
"cn-combobox-list group/combobox-content max-h-[inherit] overflow-y-auto overscroll-contain",
className
)}
{...props}
/>
)
}
function ComboboxItem<T extends object>({
className,
children,
...props
}: ListBoxItemProps<T>) {
return (
<ListBoxItemPrimitive
data-slot="combobox-item"
textValue={typeof children === "string" ? children : undefined}
className={cn(
"cn-combobox-item cn-combobox-item-aria relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
{...props}
>
{composeRenderProps(children, (children, { isSelected }) => (
<>
{children}
<span className="cn-combobox-item-indicator">
{isSelected ? (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
className="cn-combobox-item-indicator-icon pointer-events-none"
/>
) : null}
</span>
</>
))}
</ListBoxItemPrimitive>
)
}
function ComboboxGroup<T extends object>({
className,
...props
}: ListBoxSectionProps<T>) {
return (
<ListBoxSectionPrimitive
data-slot="combobox-group"
className={cn("cn-combobox-group", className)}
{...props}
/>
)
}
function ComboboxLabel({ className, ...props }: HeaderProps) {
return (
<HeaderPrimitive
data-slot="combobox-label"
className={cn("cn-combobox-label", className)}
{...props}
/>
)
}
function ComboboxEmpty({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="combobox-empty"
className={cn("cn-combobox-empty", className)}
{...props}
/>
)
}
function ComboboxSeparator({ className, ...props }: SeparatorProps) {
return (
<SeparatorPrimitive
data-slot="combobox-separator"
className={cn("cn-combobox-separator", className)}
{...props}
/>
)
}
function ComboboxChips({ children, className, ...props }: GroupProps) {
return (
<Group
data-slot="combobox-chips"
className={cn("cn-combobox-chips", className)}
{...props}
>
{children}
</Group>
)
}
function ComboboxChipList<T extends object>({
className,
...props
}: Omit<TagListProps<T>, "className" | "items"> & {
className?: string
}) {
return (
<ComboBoxValuePrimitive<T> className="contents">
{({ selectedItems, state }) => (
<TagGroupPrimitive
data-slot="combobox-chip-list"
className={cn("contents", className)}
onRemove={(keys) => {
if (Array.isArray(state.value)) {
state.setValue(state.value.filter((k) => !keys.has(k)))
}
}}
>
<TagListPrimitive
className="contents"
items={selectedItems.filter((item) => item != null)}
{...props}
/>
</TagGroupPrimitive>
)}
</ComboBoxValuePrimitive>
)
}
function ComboboxChip({
className,
children,
showRemove = true,
...props
}: Omit<TagProps, "children"> & {
showRemove?: boolean
children?: React.ReactNode
}) {
return (
<TagPrimitive
data-slot="combobox-chip"
className={cn(
"cn-combobox-chip has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50",
className
)}
{...props}
>
{children}
{showRemove && (
<Button
slot="remove"
variant="ghost"
size="icon-xs"
className="cn-combobox-chip-remove"
data-slot="combobox-chip-remove"
>
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
className="cn-combobox-chip-indicator-icon pointer-events-none"
/>
</Button>
)}
</TagPrimitive>
)
}
function ComboboxChipsInput({ className, ...props }: InputProps) {
const state = React.useContext(ComboBoxStateContext)
return (
<InputPrimitive
data-slot="combobox-chip-input"
className={cn(
"cn-combobox-chip-input min-w-16 flex-1 outline-none",
className
)}
onKeyDown={(e) => {
if (
e.key === "Backspace" &&
e.currentTarget.value === "" &&
Array.isArray(state?.value) &&
state.value.length > 0
) {
e.preventDefault()
state.setValue(state.value.slice(0, -1))
}
}}
{...props}
/>
)
}
function useComboboxAnchor() {
return React.useRef<HTMLDivElement | null>(null)
}
export {
ComboBoxPrimitive as Combobox,
ComboboxInput,
ComboboxContent,
ComboboxList,
ComboboxItem,
ComboboxGroup,
ComboboxLabel,
Collection as ComboboxCollection,
ComboboxEmpty,
ComboboxSeparator,
ComboboxChips,
ComboboxChip,
ComboboxChipList,
ComboboxChipsInput,
ComboboxTrigger,
ComboboxValue,
useComboboxAnchor,
}

View File

@@ -0,0 +1,249 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Autocomplete,
Collection,
composeRenderProps,
Header,
Input,
Menu,
MenuItem,
MenuSection,
SearchField,
Separator,
useFilter,
type AutocompleteProps,
type InputProps,
type MenuItemProps,
type MenuProps,
type MenuSectionProps,
type SeparatorProps,
} from "react-aria-components"
import {
Dialog,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/registry/bases/aria/ui/dialog"
import {
InputGroup,
InputGroupAddon,
} from "@/registry/bases/aria/ui/input-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function Command({
className,
dir,
style,
...props
}: Omit<AutocompleteProps, "className" | "style"> & {
className?: string
dir?: React.HTMLAttributes<HTMLDivElement>["dir"]
style?: React.CSSProperties
}) {
const { contains } = useFilter({ sensitivity: "base" })
return (
<div
data-slot="command"
dir={dir}
className={cn(
"cn-command flex size-full flex-col overflow-hidden",
className
)}
style={style}
>
<Autocomplete {...props} filter={props.filter || contains}>
{props.children}
</Autocomplete>
</div>
)
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
open,
onOpenChange,
className,
showCloseButton = false,
...props
}: Omit<
React.ComponentProps<typeof Dialog>,
"children" | "className" | "isOpen" | "onOpenChange"
> & {
title?: string
description?: string
open?: boolean
onOpenChange?: (isOpen: boolean) => void
className?: string
showCloseButton?: boolean
children: React.ReactNode
}) {
return (
<Dialog
isOpen={open}
onOpenChange={onOpenChange}
className={cn(
"cn-command-dialog top-1/3 translate-y-0 overflow-hidden p-0",
className
)}
showCloseButton={showCloseButton}
isDismissable
{...props}
>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
{children}
</Dialog>
)
}
function CommandInput({ className, ...props }: InputProps) {
return (
<SearchField
autoFocus
aria-label={props.placeholder || "Search"}
data-slot="command-input-wrapper"
className="cn-command-input-wrapper"
>
<InputGroup className="cn-command-input-group">
<Input
{...props}
data-slot="command-input"
className={cn(
"cn-command-input outline-hidden disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-search-cancel-button]:hidden",
className
)}
/>
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
className="cn-command-input-icon"
/>
</InputGroupAddon>
</InputGroup>
</SearchField>
)
}
function CommandList<T extends object>({ className, ...props }: MenuProps<T>) {
return (
<Menu
{...props}
data-slot="command-list"
className={cn(
"cn-command-list overflow-x-hidden overflow-y-auto",
className
)}
/>
)
}
function CommandEmpty({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="command-empty"
className={cn("cn-command-empty", className)}
{...props}
/>
)
}
function CommandGroup<T extends object>({
className,
children,
items,
heading,
...props
}: MenuSectionProps<T> & { heading?: string }) {
return (
<MenuSection
data-slot="command-group"
className={cn("cn-command-group", className)}
{...props}
>
{heading && <Header cmdk-group-heading="">{heading}</Header>}
<Collection items={items}>{children}</Collection>
</MenuSection>
)
}
function CommandSeparator({ className, ...props }: SeparatorProps) {
return (
<Separator
data-slot="command-separator"
className={cn("cn-command-separator", className)}
{...props}
/>
)
}
function CommandItem<T extends object>({
className,
children,
textValue,
...props
}: MenuItemProps<T>) {
return (
<MenuItem
{...props}
data-slot="command-item"
className={cn(
"cn-command-item cn-command-item-aria group/command-item data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
textValue={
textValue || (typeof children === "string" ? children : undefined)
}
>
{composeRenderProps(children, (children) => (
<>
{children}
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
className="cn-command-item-indicator ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100"
/>
</>
))}
</MenuItem>
)
}
function CommandShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn("cn-command-shortcut cn-command-shortcut-aria", className)}
{...props}
/>
)
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@@ -0,0 +1,267 @@
"use client"
import * as React from "react"
import { cva } from "class-variance-authority"
import { cn } from "cn"
import {
composeRenderProps,
Header as HeaderPrimitive,
MenuItem as MenuItemPrimitive,
Menu as MenuPrimitive,
MenuSection as MenuSectionPrimitive,
MenuTrigger as MenuTriggerPrimitive,
Popover as PopoverPrimitive,
Separator as SeparatorPrimitive,
SubmenuTrigger as SubmenuTriggerPrimitive,
type MenuItemProps as MenuItemPrimitiveProps,
type MenuSectionProps as MenuSectionPrimitiveProps,
} from "react-aria-components"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function ContextMenu({
"data-slot": dataSlot = "context-menu-content",
placement = "bottom start",
offset = 4,
crossOffset = 0,
className,
children,
...props
}: Omit<
React.ComponentProps<typeof MenuPrimitive<object>>,
"children" | "className"
> &
Pick<
React.ComponentProps<typeof PopoverPrimitive>,
"placement" | "offset" | "crossOffset"
> & {
"data-slot"?: string
className?: string
children?: React.ReactNode
}) {
return (
<PopoverPrimitive
data-slot={dataSlot}
placement={placement}
offset={offset}
crossOffset={crossOffset}
className={cn(
"cn-context-menu-content-aria cn-menu-target cn-menu-translucent cn-menu-translucent-aria z-50 w-(--trigger-width) origin-(--trigger-anchor-point) overflow-x-hidden overflow-y-auto outline-none data-exiting:overflow-hidden",
className
)}
>
<MenuPrimitive
className="max-h-[inherit] overflow-x-hidden overflow-y-auto outline-hidden"
{...props}
>
{children}
</MenuPrimitive>
</PopoverPrimitive>
)
}
function ContextMenuTrigger({
...props
}: Omit<React.ComponentProps<typeof MenuTriggerPrimitive>, "trigger">) {
return (
<MenuTriggerPrimitive
data-slot="context-menu"
trigger="contextMenu"
{...props}
/>
)
}
function ContextMenuGroup({
...props
}: Omit<MenuSectionPrimitiveProps<object>, "children"> & {
children?: React.ReactNode
}) {
return <MenuSectionPrimitive data-slot="context-menu-group" {...props} />
}
function ContextMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof HeaderPrimitive> & {
inset?: boolean
}) {
return (
<HeaderPrimitive
data-slot="context-menu-label"
data-inset={inset}
className={cn("cn-context-menu-label", className)}
{...props}
/>
)
}
const contextMenuItemVariants = cva(
"group/context-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
selectionMode: {
none: "cn-context-menu-item",
single: "cn-context-menu-radio-item",
multiple: "cn-context-menu-checkbox-item",
},
},
}
)
function ContextMenuItem({
className,
inset,
variant = "default",
children,
...props
}: MenuItemPrimitiveProps<object> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<MenuItemPrimitive
data-slot="context-menu-item"
data-inset={inset}
data-variant={variant}
textValue={typeof children === "string" ? children : props.textValue}
className={composeRenderProps(className, (className, { selectionMode }) =>
cn(contextMenuItemVariants({ selectionMode }), className)
)}
{...props}
>
{composeRenderProps(
children,
(children, { isSelected, selectionMode }) => (
<>
{selectionMode !== "none" ? (
<span
className="cn-context-menu-item-indicator pointer-events-none"
data-slot={
selectionMode === "single"
? "context-menu-radio-item-indicator"
: "context-menu-checkbox-item-indicator"
}
>
{isSelected ? (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
) : null}
</span>
) : null}
{children}
</>
)
)}
</MenuItemPrimitive>
)
}
function ContextMenuSub({
...props
}: React.ComponentProps<typeof SubmenuTriggerPrimitive>) {
return <SubmenuTriggerPrimitive data-slot="context-menu-sub" {...props} />
}
function ContextMenuSubTrigger({
className,
inset,
children,
...props
}: MenuItemPrimitiveProps<object> & {
inset?: boolean
}) {
return (
<MenuItemPrimitive
data-slot="context-menu-sub-trigger"
data-inset={inset}
textValue={typeof children === "string" ? children : props.textValue}
className={cn(
"cn-context-menu-sub-trigger flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
{...props}
>
{composeRenderProps(children, (children) => (
<>
{children}
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="cn-rtl-flip ml-auto"
/>
</>
))}
</MenuItemPrimitive>
)
}
function ContextMenuSubContent({
placement = "end top",
crossOffset = -3,
offset = 0,
className,
...props
}: React.ComponentProps<typeof ContextMenu>) {
return (
<ContextMenu
data-slot="context-menu-sub-content"
className={cn(
"cn-context-menu-sub-content-aria cn-menu-target cn-menu-translucent w-auto",
className
)}
placement={placement}
crossOffset={crossOffset}
offset={offset}
{...props}
/>
)
}
function ContextMenuSeparator({
className,
...props
}: React.ComponentProps<typeof SeparatorPrimitive>) {
return (
<SeparatorPrimitive
data-slot="context-menu-separator"
className={cn("cn-context-menu-separator", className)}
{...props}
/>
)
}
function ContextMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="context-menu-shortcut"
className={cn("cn-context-menu-shortcut", className)}
{...props}
/>
)
}
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
}

View File

@@ -0,0 +1,182 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Dialog as DialogPrimitive,
DialogTrigger as DialogTriggerPrimitive,
Heading,
ModalOverlay as ModalOverlayPrimitive,
Modal as ModalPrimitive,
type DialogProps as DialogPrimitiveProps,
type DialogTriggerProps as DialogTriggerPrimitiveProps,
type ModalOverlayProps as ModalOverlayPrimitiveProps,
} from "react-aria-components"
import { Button } from "@/registry/bases/aria/ui/button"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function DialogTrigger({ ...props }: DialogTriggerPrimitiveProps) {
return <DialogTriggerPrimitive data-slot="dialog-trigger" {...props} />
}
function DialogClose({
className,
variant = "outline",
size = "default",
...props
}: React.ComponentProps<typeof Button>) {
return (
<Button
slot="close"
data-slot="dialog-close"
variant={variant}
size={size}
className={cn(className)}
{...props}
/>
)
}
function DialogOverlay({
className,
children,
...props
}: Omit<ModalOverlayPrimitiveProps, "className" | "children"> & {
className?: string
children: React.ReactNode
}) {
return (
<ModalOverlayPrimitive
data-slot="dialog-overlay"
className={cn(
"cn-dialog-overlay-aria fixed inset-0 isolate z-50",
className
)}
{...props}
>
{children}
</ModalOverlayPrimitive>
)
}
function Dialog({
className,
children,
showCloseButton = true,
isDismissable = true,
...props
}: Omit<ModalOverlayPrimitiveProps, "className" | "children"> &
Pick<React.ComponentProps<typeof ModalPrimitive>, "isDismissable"> & {
className?: string
children: React.ReactNode
showCloseButton?: boolean
}) {
return (
<DialogOverlay isDismissable={isDismissable} {...props}>
<ModalPrimitive
data-slot="dialog-content"
className={cn(
"cn-dialog-content-aria fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
className
)}
>
<DialogPrimitive
data-slot="dialog"
className="[display:inherit] [gap:inherit] outline-none"
>
{children}
{showCloseButton && (
<DialogClose
variant="ghost"
className="cn-dialog-close"
size="icon-sm"
>
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
/>
<span className="sr-only">Close</span>
</DialogClose>
)}
</DialogPrimitive>
</ModalPrimitive>
</DialogOverlay>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("cn-dialog-header flex flex-col", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
"cn-dialog-footer flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{children}
{showCloseButton && <DialogClose variant="outline">Close</DialogClose>}
</div>
)
}
function DialogTitle({
className,
...props
}: Omit<React.ComponentProps<typeof Heading>, "slot">) {
return (
<Heading
slot="title"
data-slot="dialog-title"
className={cn("cn-dialog-title cn-font-heading", className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: Omit<React.ComponentProps<"div">, "slot">) {
return (
<div
data-slot="dialog-description"
className={cn("cn-dialog-description", className)}
{...props}
/>
)
}
export {
type DialogPrimitiveProps,
type DialogTriggerPrimitiveProps,
Dialog,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogTitle,
DialogTrigger,
}

View File

@@ -0,0 +1,29 @@
"use client"
import * as React from "react"
import { I18nProvider, useLocale } from "react-aria-components"
function DirectionProvider(
props: React.ComponentProps<typeof I18nProvider> & {
direction?: "ltr" | "rtl"
}
) {
// For compatibility with Radix / Base UI, if only a `direction` is provided and not a `locale`,
// create a locale string that forces the direction by setting the script to arabic or latin.
const { locale: currentLocale } = useLocale()
let locale = props.locale
if (!locale && props.direction) {
locale = new Intl.Locale(currentLocale, {
script: props.direction === "rtl" ? "Arab" : "Latn",
}).toString()
}
return <I18nProvider {...props} locale={locale} />
}
function useDirection() {
const { direction } = useLocale()
return direction
}
export { DirectionProvider, I18nProvider, useDirection, useLocale }

View File

@@ -0,0 +1,227 @@
"use client"
import * as React from "react"
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer"
import { cn } from "cn"
type DrawerContextProps = {
hasSnapPoints: boolean
modal: DrawerPrimitive.Root.Props["modal"]
showSwipeHandle: boolean
swipeDirection: NonNullable<DrawerPrimitive.Root.Props["swipeDirection"]>
}
const DrawerContext = React.createContext<DrawerContextProps | null>(null)
function useDrawer() {
const context = React.useContext(DrawerContext)
if (!context) {
throw new Error("useDrawer must be used within a Drawer.")
}
return context
}
function Drawer({
modal = true,
showSwipeHandle = false,
snapPoints,
swipeDirection = "down",
...props
}: DrawerPrimitive.Root.Props & {
showSwipeHandle?: boolean
}) {
const hasSnapPoints = snapPoints != null && snapPoints.length > 0
const contextValue = React.useMemo(
() => ({ hasSnapPoints, modal, showSwipeHandle, swipeDirection }),
[hasSnapPoints, modal, showSwipeHandle, swipeDirection]
)
return (
<DrawerContext.Provider value={contextValue}>
<DrawerPrimitive.Root
data-slot="drawer"
modal={modal}
snapPoints={snapPoints}
swipeDirection={swipeDirection}
{...props}
/>
</DrawerContext.Provider>
)
}
function DrawerTrigger({ ...props }: DrawerPrimitive.Trigger.Props) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
}
function DrawerPortal({ ...props }: DrawerPrimitive.Portal.Props) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
}
function DrawerClose({ ...props }: DrawerPrimitive.Close.Props) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
}
function DrawerOverlay({
className,
...props
}: DrawerPrimitive.Backdrop.Props) {
return (
<DrawerPrimitive.Backdrop
data-slot="drawer-overlay"
className={cn(
"cn-drawer-overlay fixed inset-0 z-50 min-h-dvh opacity-[max(var(--drawer-overlay-min-opacity,0),calc(1-var(--drawer-swipe-progress)))] transition-opacity duration-450 ease-[cubic-bezier(0.32,0.72,0,1)] select-none data-ending-style:pointer-events-none data-ending-style:opacity-0 data-ending-style:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-snap-points:[--drawer-overlay-min-opacity:0.5] data-starting-style:opacity-0 data-swiping:duration-0 supports-[-webkit-touch-callout:none]:absolute",
className
)}
{...props}
/>
)
}
function DrawerSwipeHandle({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-swipe-handle"
aria-hidden="true"
className={cn(
"cn-drawer-swipe-handle relative z-10 flex shrink-0 cursor-grab transition-opacity duration-200 group-data-nested-drawer-open/drawer-popup:opacity-0 group-data-nested-drawer-swiping/drawer-popup:opacity-100 group-data-[swipe-direction=left]/drawer-popup:order-last group-data-[swipe-direction=up]/drawer-popup:order-last active:cursor-grabbing",
className
)}
{...props}
/>
)
}
function DrawerContent({
className,
children,
...props
}: DrawerPrimitive.Popup.Props) {
const { hasSnapPoints, modal, showSwipeHandle, swipeDirection } = useDrawer()
const swipeAxis =
swipeDirection === "down" || swipeDirection === "up" ? "y" : "x"
return (
<DrawerPortal data-slot="drawer-portal">
{modal === true && (
<DrawerOverlay data-snap-points={hasSnapPoints ? "" : undefined} />
)}
<DrawerPrimitive.Viewport
data-slot="drawer-viewport"
data-modal={modal}
className="pointer-events-none fixed inset-0 z-50 select-none data-[modal=true]:pointer-events-auto"
>
<DrawerPrimitive.Popup
data-slot="drawer-popup"
data-swipe-axis={swipeAxis}
data-snap-points={hasSnapPoints ? "" : undefined}
className={cn(
// Base.
"cn-drawer-popup group/drawer-popup pointer-events-auto fixed z-50 m-(--drawer-inset,0px) flex h-(--drawer-content-height) max-h-(--drawer-content-max-height,none) min-h-0 w-(--drawer-content-width,auto) transform-[translate3d(var(--translate-x,0px),var(--translate-y,0px),0)_scale(var(--stack-scale))] flex-col transition-[transform,height,opacity,filter] duration-450 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform outline-none select-none [interpolate-size:allow-keywords]",
// Nested.
"data-nested-drawer-open:overflow-hidden data-nested-drawer-open:brightness-95",
// Bleed.
"after:pointer-events-none after:absolute after:bg-(--drawer-bleed-background,var(--color-popover)) data-[swipe-axis=x]:after:inset-y-0 data-[swipe-axis=x]:after:w-(--bleed) data-[swipe-axis=y]:after:inset-x-0 data-[swipe-axis=y]:after:h-(--bleed) data-[swipe-direction=down]:after:top-full data-[swipe-direction=left]:after:right-full data-[swipe-direction=right]:after:left-full data-[swipe-direction=up]:after:bottom-full",
// Sizing.
"[--drawer-content-height:var(--drawer-height,auto)] data-[swipe-axis=x]:[--drawer-content-width:75%] data-[swipe-axis=y]:[--drawer-content-max-height:calc(100dvh-6rem)] data-[swipe-axis=y]:data-snap-points:[--drawer-content-height:100dvh] data-[swipe-axis=x]:sm:[--drawer-content-width:24rem]",
// Stack.
"[--bleed:3rem] [--peek:1rem] [--stack-height:var(--drawer-frontmost-height,var(--drawer-height,0px))] [--stack-peek-offset:max(0px,calc((var(--nested-drawers)-var(--stack-progress))*var(--peek)))] [--stack-progress:clamp(0,var(--drawer-swipe-progress),1)] [--stack-scale-base:max(0,calc(1-(var(--nested-drawers)*var(--stack-step))))] [--stack-scale:clamp(0,calc(var(--stack-scale-base)+(var(--stack-step)*var(--stack-progress))),1)] [--stack-shrink:calc(1-var(--stack-scale))] [--stack-step:0.05]",
// Transitions.
"data-ending-style:transform-(--closed-transform) data-ending-style:opacity-[0.9999] data-ending-style:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-nested-drawer-swiping:duration-0 data-ending-style:data-nested-drawer-swiping:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-starting-style:transform-(--closed-transform) data-swiping:duration-0 data-ending-style:data-swiping:duration-[calc(var(--drawer-swipe-strength)*400ms)]",
// Axis: y.
"data-[swipe-axis=y]:inset-x-0 data-[swipe-axis=y]:data-nested-drawer-open:h-(--stack-height)",
// Axis: x.
"data-[swipe-axis=x]:inset-y-0 data-[swipe-axis=x]:flex-row",
// Direction: down.
"data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:origin-bottom data-[swipe-direction=down]:[--closed-transform:translate3d(0,calc(100%+var(--drawer-inset,0px)+2px),0)] data-[swipe-direction=down]:[--translate-y:calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)-var(--stack-peek-offset)-(var(--stack-shrink)*var(--stack-height)))]",
// Direction: up.
"data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:origin-top data-[swipe-direction=up]:[--closed-transform:translate3d(0,calc(-100%-var(--drawer-inset,0px)-2px),0)] data-[swipe-direction=up]:[--translate-y:calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)+var(--stack-peek-offset)+(var(--stack-shrink)*var(--stack-height)))]",
// Direction: left.
"data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:origin-left data-[swipe-direction=left]:[--closed-transform:translate3d(calc(-100%-var(--drawer-inset,0px)-2px),0,0)] data-[swipe-direction=left]:[--translate-x:calc(var(--drawer-swipe-movement-x)+var(--stack-peek-offset)+(var(--stack-shrink)*100%))]",
// Direction: right.
"data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:origin-right data-[swipe-direction=right]:[--closed-transform:translate3d(calc(100%+var(--drawer-inset,0px)+2px),0,0)] data-[swipe-direction=right]:[--translate-x:calc(var(--drawer-swipe-movement-x)-var(--stack-peek-offset)-(var(--stack-shrink)*100%))]",
className
)}
{...props}
>
{showSwipeHandle && <DrawerSwipeHandle />}
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"cn-drawer-content-base flex min-h-0 flex-1 flex-col overflow-hidden overscroll-contain rounded-[inherit] transition-opacity duration-300 ease-[cubic-bezier(0.45,1.005,0,1.005)] select-text group-data-nested-drawer-open/drawer-popup:opacity-0 group-data-nested-drawer-swiping/drawer-popup:opacity-100 group-data-swiping/drawer-popup:select-none"
)}
>
{children}
</DrawerPrimitive.Content>
</DrawerPrimitive.Popup>
</DrawerPrimitive.Viewport>
</DrawerPortal>
)
}
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-header"
className={cn(
"cn-drawer-header-base flex shrink-0 flex-col group-data-[swipe-axis=y]/drawer-popup:text-center",
className
)}
{...props}
/>
)
}
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-footer"
className={cn(
"cn-drawer-footer-base mt-auto flex shrink-0 flex-col",
className
)}
{...props}
/>
)
}
function DrawerTitle({ className, ...props }: DrawerPrimitive.Title.Props) {
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn("cn-drawer-title cn-font-heading", className)}
{...props}
/>
)
}
function DrawerDescription({
className,
...props
}: DrawerPrimitive.Description.Props) {
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn("cn-drawer-description text-balance", className)}
{...props}
/>
)
}
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerSwipeHandle,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}

View File

@@ -0,0 +1,261 @@
"use client"
import * as React from "react"
import { cva } from "class-variance-authority"
import { cn } from "cn"
import {
composeRenderProps,
Header as HeaderPrimitive,
MenuItem as MenuItemPrimitive,
Menu as MenuPrimitive,
MenuSection as MenuSectionPrimitive,
MenuTrigger as MenuTriggerPrimitive,
Popover as PopoverPrimitive,
Separator as SeparatorPrimitive,
SubmenuTrigger as SubmenuTriggerPrimitive,
type MenuItemProps as MenuItemPrimitiveProps,
type MenuSectionProps as MenuSectionPrimitiveProps,
} from "react-aria-components"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof MenuTriggerPrimitive>) {
return <MenuTriggerPrimitive data-slot="dropdown-menu-trigger" {...props} />
}
function DropdownMenu({
"data-slot": dataSlot = "dropdown-menu-content",
placement = "bottom start",
offset = 4,
crossOffset = 0,
className,
children,
...props
}: Omit<
React.ComponentProps<typeof MenuPrimitive<object>>,
"children" | "className"
> &
Pick<
React.ComponentProps<typeof PopoverPrimitive>,
"placement" | "offset" | "crossOffset"
> & {
"data-slot"?: string
className?: string
children?: React.ReactNode
}) {
return (
<PopoverPrimitive
data-slot={dataSlot}
placement={placement}
offset={offset}
crossOffset={crossOffset}
className={cn(
"cn-dropdown-menu-content-aria cn-menu-target cn-menu-translucent cn-menu-translucent-aria z-50 w-(--trigger-width) origin-(--trigger-anchor-point) overflow-x-hidden overflow-y-auto outline-none data-exiting:overflow-hidden",
className
)}
>
<MenuPrimitive
className="max-h-[inherit] overflow-x-hidden overflow-y-auto outline-hidden"
{...props}
>
{children}
</MenuPrimitive>
</PopoverPrimitive>
)
}
function DropdownMenuGroup({
...props
}: Omit<MenuSectionPrimitiveProps<object>, "children"> & {
children?: React.ReactNode
}) {
return <MenuSectionPrimitive data-slot="dropdown-menu-group" {...props} />
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof HeaderPrimitive> & {
inset?: boolean
}) {
return (
<HeaderPrimitive
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn("cn-dropdown-menu-label", className)}
{...props}
/>
)
}
const dropdownMenuItemVariants = cva(
"group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
selectionMode: {
none: "cn-dropdown-menu-item",
single: "cn-dropdown-menu-radio-item",
multiple: "cn-dropdown-menu-checkbox-item",
},
},
}
)
function DropdownMenuItem({
className,
inset,
variant = "default",
children,
...props
}: MenuItemPrimitiveProps<object> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<MenuItemPrimitive
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
textValue={typeof children === "string" ? children : props.textValue}
className={composeRenderProps(className, (className, { selectionMode }) =>
cn(dropdownMenuItemVariants({ selectionMode }), className)
)}
{...props}
>
{composeRenderProps(
children,
(children, { isSelected, selectionMode }) => (
<>
{selectionMode !== "none" ? (
<span
className="cn-dropdown-menu-item-indicator pointer-events-none"
data-slot={
selectionMode === "single"
? "dropdown-menu-radio-item-indicator"
: "dropdown-menu-checkbox-item-indicator"
}
>
{isSelected ? (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
) : null}
</span>
) : null}
{children}
</>
)
)}
</MenuItemPrimitive>
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof SubmenuTriggerPrimitive>) {
return <SubmenuTriggerPrimitive data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: MenuItemPrimitiveProps<object> & {
inset?: boolean
}) {
return (
<MenuItemPrimitive
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
textValue={typeof children === "string" ? children : props.textValue}
className={cn(
"cn-dropdown-menu-sub-trigger flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
{...props}
>
{composeRenderProps(children, (children) => (
<>
{children}
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="cn-rtl-flip ml-auto"
/>
</>
))}
</MenuItemPrimitive>
)
}
function DropdownMenuSubContent({
placement = "end top",
crossOffset = -3,
offset = 0,
className,
...props
}: React.ComponentProps<typeof DropdownMenu>) {
return (
<DropdownMenu
data-slot="dropdown-menu-sub-content"
className={cn(
"cn-dropdown-menu-sub-content-aria cn-menu-target cn-menu-translucent w-auto",
className
)}
placement={placement}
crossOffset={crossOffset}
offset={offset}
{...props}
/>
)
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof SeparatorPrimitive>) {
return (
<SeparatorPrimitive
data-slot="dropdown-menu-separator"
className={cn("cn-dropdown-menu-separator", className)}
{...props}
/>
)
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn("cn-dropdown-menu-shortcut", className)}
{...props}
/>
)
}
export {
DropdownMenuTrigger,
DropdownMenu,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}

View File

@@ -0,0 +1,103 @@
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
function Empty({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty"
className={cn(
"cn-empty flex w-full min-w-0 flex-1 flex-col items-center justify-center text-center text-balance",
className
)}
{...props}
/>
)
}
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-header"
className={cn(
"cn-empty-header flex max-w-sm flex-col items-center",
className
)}
{...props}
/>
)
}
const emptyMediaVariants = cva(
"cn-empty-media flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "cn-empty-media-default",
icon: "cn-empty-media-icon",
},
},
defaultVariants: {
variant: "default",
},
}
)
function EmptyMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
return (
<div
data-slot="empty-icon"
data-variant={variant}
className={cn(emptyMediaVariants({ variant, className }))}
{...props}
/>
)
}
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-title"
className={cn("cn-empty-title cn-font-heading", className)}
{...props}
/>
)
}
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
data-slot="empty-description"
className={cn(
"cn-empty-description text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
/>
)
}
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-content"
className={cn(
"cn-empty-content flex w-full max-w-sm min-w-0 flex-col items-center text-balance",
className
)}
{...props}
/>
)
}
export {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
EmptyMedia,
}

View File

@@ -0,0 +1,224 @@
"use client"
import { useMemo } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { Label } from "@/registry/bases/aria/ui/label"
import { Separator } from "@/registry/bases/aria/ui/separator"
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
<fieldset
data-slot="field-set"
className={cn("cn-field-set flex flex-col", className)}
{...props}
/>
)
}
function FieldLegend({
className,
variant = "legend",
...props
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
return (
<legend
data-slot="field-legend"
data-variant={variant}
className={cn("cn-field-legend", className)}
{...props}
/>
)
}
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-group"
className={cn(
"cn-field-group group/field-group @container/field-group flex w-full flex-col",
className
)}
{...props}
/>
)
}
const fieldVariants = cva("cn-field group/field flex w-full", {
variants: {
orientation: {
vertical:
"cn-field-orientation-vertical flex-col *:w-full [&>.sr-only]:w-auto",
horizontal:
"cn-field-orientation-horizontal flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
responsive:
"cn-field-orientation-responsive flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
},
},
defaultVariants: {
orientation: "vertical",
},
})
function Field({
className,
orientation = "vertical",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
return (
<div
role="group"
data-slot="field"
data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)}
{...props}
/>
)
}
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-content"
className={cn(
"cn-field-content group/field-content flex flex-1 flex-col leading-snug",
className
)}
{...props}
/>
)
}
function FieldLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
<Label
data-slot="field-label"
className={cn(
"cn-field-label cn-field-label-aria group/field-label peer/field-label flex w-fit",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
className
)}
{...props}
/>
)
}
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-label"
className={cn("cn-field-title flex w-fit items-center", className)}
{...props}
/>
)
}
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="field-description"
className={cn(
"cn-field-description leading-normal font-normal group-has-data-horizontal/field:text-balance",
"last:mt-0 nth-last-2:-mt-1",
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
/>
)
}
function FieldSeparator({
children,
className,
...props
}: React.ComponentProps<"div"> & {
children?: React.ReactNode
}) {
return (
<div
data-slot="field-separator"
data-content={!!children}
className={cn("cn-field-separator relative", className)}
{...props}
>
<Separator className="absolute inset-0 top-1/2" />
{children && (
<span
className="cn-field-separator-content relative mx-auto block w-fit bg-background"
data-slot="field-separator-content"
>
{children}
</span>
)}
</div>
)
}
function FieldError({
className,
children,
errors,
...props
}: React.ComponentProps<"div"> & {
errors?: Array<{ message?: string } | undefined>
}) {
const content = useMemo(() => {
if (children) {
return children
}
if (!errors?.length) {
return null
}
const uniqueErrors = [
...new Map(errors.map((error) => [error?.message, error])).values(),
]
if (uniqueErrors?.length == 1) {
return uniqueErrors[0]?.message
}
return (
<ul className="ml-4 flex list-disc flex-col gap-1">
{uniqueErrors.map(
(error, index) =>
error?.message && <li key={index}>{error.message}</li>
)}
</ul>
)
}, [children, errors])
if (!content) {
return null
}
return (
<div
role="alert"
data-slot="field-error"
className={cn("cn-field-error font-normal", className)}
{...props}
>
{content}
</div>
)
}
export {
Field,
FieldLabel,
FieldDescription,
FieldError,
FieldGroup,
FieldLegend,
FieldSeparator,
FieldSet,
FieldContent,
FieldTitle,
}

View File

@@ -0,0 +1,43 @@
"use client"
import { cn } from "cn"
import {
Popover as PopoverPrimitive,
PreviewTrigger as PreviewTriggerPrimitive,
type PopoverProps as PopoverPrimitiveProps,
type PreviewTriggerProps,
} from "react-aria-components"
function HoverCardTrigger({ children, ...props }: PreviewTriggerProps) {
return (
<PreviewTriggerPrimitive data-slot="hover-card-trigger" {...props}>
{children}
</PreviewTriggerPrimitive>
)
}
function HoverCard({
className,
placement = "bottom",
offset = 4,
crossOffset = 0,
...props
}: Omit<PopoverPrimitiveProps, "className"> & {
className?: string
}) {
return (
<PopoverPrimitive
data-slot="hover-card-content"
placement={placement}
offset={offset}
crossOffset={crossOffset}
className={cn(
"cn-hover-card-content-aria z-50 origin-(--trigger-anchor-point) outline-hidden",
className
)}
{...props}
/>
)
}
export { HoverCard, HoverCardTrigger }

View File

@@ -0,0 +1,149 @@
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { Group, type GroupProps } from "react-aria-components"
import { Button } from "@/registry/bases/aria/ui/button"
import { Input } from "@/registry/bases/aria/ui/input"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
function InputGroup({ className, ...props }: GroupProps) {
return (
<Group
data-slot="input-group"
className={cn(
"group/input-group cn-input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto",
className
)}
{...props}
/>
)
}
const inputGroupAddonVariants = cva(
"cn-input-group-addon flex cursor-text items-center justify-center select-none",
{
variants: {
align: {
"inline-start": "cn-input-group-addon-align-inline-start order-first",
"inline-end": "cn-input-group-addon-align-inline-end order-last",
"block-start":
"cn-input-group-addon-align-block-start order-first w-full justify-start",
"block-end":
"cn-input-group-addon-align-block-end order-last w-full justify-start",
},
},
defaultVariants: {
align: "inline-start",
},
}
)
function InputGroupAddon({
className,
align = "inline-start",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
return (
<div
role="group"
data-slot="input-group-addon"
data-align={align}
className={cn(inputGroupAddonVariants({ align }), className)}
onClick={(e) => {
if ((e.target as HTMLElement).closest("button")) {
return
}
e.currentTarget.parentElement?.querySelector("input")?.focus()
}}
{...props}
/>
)
}
const inputGroupButtonVariants = cva(
"cn-input-group-button flex items-center shadow-none",
{
variants: {
size: {
xs: "cn-input-group-button-size-xs",
sm: "cn-input-group-button-size-sm",
"icon-xs": "cn-input-group-button-size-icon-xs",
"icon-sm": "cn-input-group-button-size-icon-sm",
},
},
defaultVariants: {
size: "xs",
},
}
)
function InputGroupButton({
className,
type = "button",
variant = "ghost",
size = "xs",
...props
}: Omit<React.ComponentProps<typeof Button>, "size" | "type"> &
VariantProps<typeof inputGroupButtonVariants> & {
type?: "button" | "submit" | "reset"
}) {
return (
<Button
type={type}
data-size={size}
variant={variant}
className={cn(inputGroupButtonVariants({ size }), className)}
{...props}
/>
)
}
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"cn-input-group-text flex items-center [&_svg]:pointer-events-none",
className
)}
{...props}
/>
)
}
function InputGroupInput({
className,
...props
}: React.ComponentProps<"input">) {
return (
<Input
data-slot="input-group-control"
className={cn("cn-input-group-input flex-1", className)}
{...props}
/>
)
}
function InputGroupTextarea({
className,
...props
}: React.ComponentProps<"textarea">) {
return (
<Textarea
data-slot="input-group-control"
className={cn("cn-input-group-textarea flex-1 resize-none", className)}
{...props}
/>
)
}
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupInput,
InputGroupTextarea,
}

View File

@@ -0,0 +1,92 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import { OTPInput, OTPInputContext } from "input-otp"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function InputOTP({
className,
containerClassName,
...props
}: React.ComponentProps<typeof OTPInput> & {
containerClassName?: string
}) {
return (
<OTPInput
data-slot="input-otp"
containerClassName={cn(
"cn-input-otp flex items-center has-disabled:opacity-50",
containerClassName
)}
spellCheck={false}
className={cn(
"cn-input-otp-input disabled:cursor-not-allowed",
className
)}
{...props}
/>
)
}
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-otp-group"
className={cn("cn-input-otp-group flex items-center", className)}
{...props}
/>
)
}
function InputOTPSlot({
index,
className,
...props
}: React.ComponentProps<"div"> & {
index: number
}) {
const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
return (
<div
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
"cn-input-otp-slot relative flex items-center justify-center data-[active=true]:z-10",
className
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="cn-input-otp-caret pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="cn-input-otp-caret-line" />
</div>
)}
</div>
)
}
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-otp-separator"
className="cn-input-otp-separator flex items-center"
role="separator"
{...props}
>
<IconPlaceholder
lucide="MinusIcon"
tabler="IconMinus"
hugeicons="MinusSignIcon"
phosphor="MinusIcon"
remixicon="RiSubtractLine"
/>
</div>
)
}
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

View File

@@ -0,0 +1,30 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
composeRenderProps,
Input as InputPrimitive,
} from "react-aria-components"
function Input({
className,
type,
...props
}: React.ComponentProps<typeof InputPrimitive>) {
return (
<InputPrimitive
type={type}
data-slot="input"
className={composeRenderProps(className, (className) =>
cn(
"cn-input w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
className
)
)}
{...props}
/>
)
}
export { Input }

View File

@@ -0,0 +1,197 @@
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import { Link as LinkPrimitive, type LinkProps } from "react-aria-components"
import { Separator } from "@/registry/bases/aria/ui/separator"
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
role="list"
data-slot="item-group"
className={cn(
"cn-item-group group/item-group flex w-full flex-col",
className
)}
{...props}
/>
)
}
function ItemSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="item-separator"
orientation="horizontal"
className={cn("cn-item-separator", className)}
{...props}
/>
)
}
const itemVariants = cva(
"cn-item group/item flex w-full flex-wrap items-center transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors",
{
variants: {
variant: {
default: "cn-item-variant-default",
outline: "cn-item-variant-outline",
muted: "cn-item-variant-muted",
},
size: {
default: "cn-item-size-default",
sm: "cn-item-size-sm",
xs: "cn-item-size-xs",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Item({
className,
variant = "default",
size = "default",
...props
}: Omit<LinkProps, "children"> &
React.HTMLAttributes<HTMLElement> &
VariantProps<typeof itemVariants>) {
const Element = "href" in props ? LinkPrimitive : "div"
return (
<Element
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
)
}
const itemMediaVariants = cva(
"cn-item-media flex shrink-0 items-center justify-center [&_svg]:pointer-events-none",
{
variants: {
variant: {
default: "cn-item-media-variant-default",
icon: "cn-item-media-variant-icon",
image: "cn-item-media-variant-image",
},
},
defaultVariants: {
variant: "default",
},
}
)
function ItemMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
return (
<div
data-slot="item-media"
data-variant={variant}
className={cn(itemMediaVariants({ variant, className }))}
{...props}
/>
)
}
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-content"
className={cn(
"cn-item-content flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none",
className
)}
{...props}
/>
)
}
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-title"
className={cn(
"cn-item-title line-clamp-1 flex w-fit items-center",
className
)}
{...props}
/>
)
}
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="item-description"
className={cn(
"cn-item-description line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
/>
)
}
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-actions"
className={cn("cn-item-actions flex items-center", className)}
{...props}
/>
)
}
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-header"
className={cn(
"cn-item-header flex basis-full items-center justify-between",
className
)}
{...props}
/>
)
}
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-footer"
className={cn(
"cn-item-footer flex basis-full items-center justify-between",
className
)}
{...props}
/>
)
}
export {
Item,
ItemMedia,
ItemContent,
ItemActions,
ItemGroup,
ItemSeparator,
ItemTitle,
ItemDescription,
ItemHeader,
ItemFooter,
}

View File

@@ -0,0 +1,29 @@
"use client"
import { cn } from "cn"
import { Keyboard as KbdPrimitive } from "react-aria-components"
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<KbdPrimitive
data-slot="kbd"
className={cn(
"cn-kbd pointer-events-none inline-flex items-center justify-center select-none",
className
)}
{...props}
/>
)
}
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<KbdPrimitive
data-slot="kbd-group"
className={cn("cn-kbd-group inline-flex items-center", className)}
{...props}
/>
)
}
export { Kbd, KbdGroup }

View File

@@ -0,0 +1,32 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
LabelContext,
Label as LabelPrimitive,
type LabelProps,
} from "react-aria-components"
function Label({ className, htmlFor, slot, ...props }: LabelProps) {
const label = (
<LabelPrimitive
data-slot="label"
className={cn(
"cn-label cn-label-aria flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
className
)}
{...props}
htmlFor={htmlFor}
slot={slot}
/>
)
if (htmlFor && slot === undefined) {
return <LabelContext.Provider value={null}>{label}</LabelContext.Provider>
}
return label
}
export { Label }

View File

@@ -0,0 +1,73 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
const markerVariants = cva(
"cn-marker group/marker relative flex w-full items-center",
{
variants: {
variant: {
default: "cn-marker-variant-default",
separator: "cn-marker-variant-separator",
border: "cn-marker-variant-border",
},
},
}
)
function Marker({
className,
variant = "default",
render,
children,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof markerVariants> & {
render?: (props: React.HTMLAttributes<HTMLElement>) => React.ReactNode
}) {
if (render) {
const renderProps = {
...props,
"data-slot": "marker",
"data-variant": variant,
className: cn(markerVariants({ variant, className })),
children,
}
return render(renderProps)
}
return (
<div
{...props}
data-slot="marker"
data-variant={variant}
className={cn(markerVariants({ variant, className }))}
>
{children}
</div>
)
}
function MarkerIcon({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="marker-icon"
aria-hidden="true"
className={cn("cn-marker-icon shrink-0", className)}
{...props}
/>
)
}
function MarkerContent({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="marker-content"
className={cn("cn-marker-content min-w-0 wrap-break-word", className)}
{...props}
/>
)
}
export { Marker, MarkerIcon, MarkerContent, markerVariants }

View File

@@ -0,0 +1,139 @@
"use client"
import * as React from "react"
import {
MessageScroller as MessageScrollerPrimitive,
useMessageScroller,
useMessageScrollerScrollable,
useMessageScrollerVisibility,
} from "@shadcn/react/message-scroller"
import { cn } from "cn"
import { Button } from "@/registry/bases/aria/ui/button"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function MessageScrollerProvider(
props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>
) {
return <MessageScrollerPrimitive.Provider {...props} />
}
function MessageScroller({
className,
...props
}: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) {
return (
<MessageScrollerPrimitive.Root
data-slot="message-scroller"
className={cn(
"cn-message-scroller group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden",
className
)}
{...props}
/>
)
}
function MessageScrollerViewport({
className,
...props
}: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) {
return (
<MessageScrollerPrimitive.Viewport
data-slot="message-scroller-viewport"
className={cn(
"cn-message-scroller-viewport size-full min-h-0 min-w-0 scroll-fade-b scrollbar-thin scrollbar-gutter-stable overflow-y-auto overscroll-contain contain-content data-autoscrolling:scrollbar-thumb-transparent data-autoscrolling:scrollbar-track-transparent data-pending-scroll:invisible",
className
)}
{...props}
/>
)
}
function MessageScrollerContent({
className,
...props
}: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) {
return (
<MessageScrollerPrimitive.Content
data-slot="message-scroller-content"
className={cn(
"cn-message-scroller-content flex h-max min-h-full flex-col",
className
)}
{...props}
/>
)
}
function MessageScrollerItem({
className,
scrollAnchor = false,
...props
}: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) {
return (
<MessageScrollerPrimitive.Item
data-slot="message-scroller-item"
scrollAnchor={scrollAnchor}
className={cn(
"cn-message-scroller-item min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]",
className
)}
{...props}
/>
)
}
function MessageScrollerButton({
direction = "end",
className,
children,
render,
variant = "secondary",
size = "icon-sm",
...props
}: React.ComponentProps<typeof MessageScrollerPrimitive.Button> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<MessageScrollerPrimitive.Button
data-slot="message-scroller-button"
data-direction={direction}
data-variant={variant}
data-size={size}
direction={direction}
className={cn(
"cn-message-scroller-button absolute inset-s-1/2 -translate-x-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180",
className
)}
render={render ?? <Button variant={variant} size={size} />}
{...props}
>
{children ?? (
<>
<IconPlaceholder
lucide="ArrowDownIcon"
tabler="IconArrowDown"
hugeicons="ArrowDown02Icon"
phosphor="ArrowDownIcon"
remixicon="RiArrowDownLine"
/>
<span className="sr-only">
{direction === "end" ? "Scroll to end" : "Scroll to start"}
</span>
</>
)}
</MessageScrollerPrimitive.Button>
)
}
export {
MessageScrollerProvider,
MessageScroller,
MessageScrollerViewport,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerButton,
useMessageScroller,
useMessageScrollerScrollable,
useMessageScrollerVisibility,
}

View File

@@ -0,0 +1,91 @@
import * as React from "react"
import { cn } from "cn"
function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-group"
className={cn("cn-message-group flex min-w-0 flex-col", className)}
{...props}
/>
)
}
function Message({
className,
align = "start",
...props
}: React.ComponentProps<"div"> & { align?: "start" | "end" }) {
return (
<div
data-slot="message"
data-align={align}
className={cn(
"cn-message group/message relative flex w-full min-w-0 data-[align=end]:flex-row-reverse",
className
)}
{...props}
/>
)
}
function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-avatar"
className={cn(
"cn-message-avatar flex w-fit shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted",
className
)}
{...props}
/>
)
}
function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-content"
className={cn(
"cn-message-content flex w-full min-w-0 flex-col wrap-break-word",
className
)}
{...props}
/>
)
}
function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-header"
className={cn(
"cn-message-header flex max-w-full min-w-0 items-center",
className
)}
{...props}
/>
)
}
function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-footer"
className={cn(
"cn-message-footer flex max-w-full min-w-0 items-center group-data-[align=end]/message:justify-end",
className
)}
{...props}
/>
)
}
export {
MessageGroup,
Message,
MessageAvatar,
MessageContent,
MessageFooter,
MessageHeader,
}

View File

@@ -0,0 +1,70 @@
import * as React from "react"
import { cn } from "cn"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
size?: "sm" | "default"
}
function NativeSelect({
className,
size = "default",
...props
}: NativeSelectProps) {
return (
<div
className={cn(
"cn-native-select-wrapper group/native-select relative w-fit has-[select:disabled]:opacity-50",
className
)}
data-slot="native-select-wrapper"
data-size={size}
>
<select
data-slot="native-select"
data-size={size}
className="cn-native-select outline-none disabled:pointer-events-none disabled:cursor-not-allowed"
{...props}
/>
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconSelector"
hugeicons="UnfoldMoreIcon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
className="cn-native-select-icon pointer-events-none absolute select-none"
aria-hidden="true"
data-slot="native-select-icon"
/>
</div>
)
}
function NativeSelectOption({
className,
...props
}: React.ComponentProps<"option">) {
return (
<option
data-slot="native-select-option"
className={cn("bg-[Canvas] text-[CanvasText]", className)}
{...props}
/>
)
}
function NativeSelectOptGroup({
className,
...props
}: React.ComponentProps<"optgroup">) {
return (
<optgroup
data-slot="native-select-optgroup"
className={cn("bg-[Canvas] text-[CanvasText]", className)}
{...props}
/>
)
}
export { NativeSelect, NativeSelectOptGroup, NativeSelectOption }

View File

@@ -0,0 +1,150 @@
import * as React from "react"
import { cn } from "cn"
import { LinkButton } from "@/registry/bases/aria/ui/button"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn(
"cn-pagination mx-auto flex w-full justify-center",
className
)}
{...props}
/>
)
}
function PaginationContent({
className,
...props
}: React.ComponentProps<"ul">) {
return (
<ul
data-slot="pagination-content"
className={cn("cn-pagination-content flex items-center", className)}
{...props}
/>
)
}
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
return <li data-slot="pagination-item" {...props} />
}
type PaginationLinkProps = {
isActive?: boolean
} & Omit<React.ComponentProps<typeof LinkButton>, "variant">
function PaginationLink({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) {
return (
<LinkButton
variant={isActive ? "outline" : "ghost"}
size={size}
className={cn("cn-pagination-link", className)}
aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
data-active={isActive}
{...props}
/>
)
}
function PaginationPrevious({
className,
text = "Previous",
...props
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("cn-pagination-previous", className)}
{...props}
>
<IconPlaceholder
lucide="ChevronLeftIcon"
tabler="IconChevronLeft"
hugeicons="ArrowLeft01Icon"
phosphor="CaretLeftIcon"
remixicon="RiArrowLeftSLine"
data-icon="inline-start"
className="cn-rtl-flip"
/>
<span className="cn-pagination-previous-text hidden sm:block">
{text}
</span>
</PaginationLink>
)
}
function PaginationNext({
className,
text = "Next",
...props
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("cn-pagination-next", className)}
{...props}
>
<span className="cn-pagination-next-text hidden sm:block">{text}</span>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
data-icon="inline-end"
className="cn-rtl-flip"
/>
</PaginationLink>
)
}
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn(
"cn-pagination-ellipsis flex items-center justify-center",
className
)}
{...props}
>
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">More pages</span>
</span>
)
}
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
}

View File

@@ -0,0 +1,87 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
DialogTrigger,
Heading,
Popover as PopoverPrimitive,
type DialogTriggerProps,
type PopoverProps as PopoverPrimitiveProps,
} from "react-aria-components"
function PopoverTrigger({ children, ...props }: DialogTriggerProps) {
return (
<DialogTrigger data-slot="popover-trigger" {...props}>
{children}
</DialogTrigger>
)
}
function Popover({
className,
placement = "bottom",
offset = 4,
crossOffset = 0,
...props
}: Omit<PopoverPrimitiveProps, "className"> & {
className?: string
}) {
return (
<PopoverPrimitive
data-slot="popover-content"
placement={placement}
offset={offset}
crossOffset={crossOffset}
className={cn(
"cn-popover-content-aria z-50 w-72 origin-(--trigger-anchor-point) outline-hidden",
className
)}
{...props}
/>
)
}
function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="popover-header"
className={cn("cn-popover-header", className)}
{...props}
/>
)
}
function PopoverTitle({
className,
...props
}: React.ComponentProps<typeof Heading>) {
return (
<Heading
data-slot="popover-title"
className={cn("cn-popover-title", className)}
{...props}
/>
)
}
function PopoverDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="popover-description"
className={cn("cn-popover-description", className)}
{...props}
/>
)
}
export {
Popover,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
}

View File

@@ -0,0 +1,147 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Label as LabelPrimitive,
ProgressBar as ProgressPrimitive,
type LabelProps,
type ProgressBarProps as ProgressPrimitiveProps,
} from "react-aria-components"
type ProgressContextValue = {
percentage?: number
isIndeterminate: boolean
valueText?: string
}
const ProgressContext = React.createContext<ProgressContextValue | null>(null)
function useProgress() {
const context = React.useContext(ProgressContext)
if (!context) {
throw new Error("useProgress must be used within a Progress.")
}
return context
}
function ProgressContent({
children,
percentage,
isIndeterminate,
valueText,
}: ProgressContextValue & {
children?: React.ReactNode
}) {
const context = React.useMemo(
() => ({ percentage, isIndeterminate, valueText }),
[percentage, isIndeterminate, valueText]
)
return (
<ProgressContext value={context}>
{children}
<ProgressTrack>
<ProgressIndicator />
</ProgressTrack>
</ProgressContext>
)
}
function Progress({
className,
children,
...props
}: Omit<ProgressPrimitiveProps, "children" | "className"> & {
children?: React.ReactNode
className?: string
}) {
return (
<ProgressPrimitive
data-slot="progress"
className={cn("cn-progress-root flex flex-wrap gap-3", className)}
{...props}
>
{({ percentage, valueText, isIndeterminate }) => (
<ProgressContent
percentage={percentage}
valueText={valueText}
isIndeterminate={isIndeterminate}
>
{children}
</ProgressContent>
)}
</ProgressPrimitive>
)
}
function ProgressTrack({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"cn-progress-track relative flex w-full items-center overflow-x-hidden",
className
)}
data-slot="progress-track"
{...props}
/>
)
}
function ProgressIndicator({
className,
style,
...props
}: React.ComponentProps<"span">) {
const { percentage, isIndeterminate } = useProgress()
return (
<span
data-slot="progress-indicator"
className={cn("cn-progress-indicator h-full transition-all", className)}
style={{
...style,
width: `${isIndeterminate ? 100 : (percentage ?? 0)}%`,
}}
{...props}
/>
)
}
function ProgressLabel({ className, ...props }: LabelProps) {
return (
<LabelPrimitive
className={cn("cn-progress-label", className)}
data-slot="progress-label"
{...props}
/>
)
}
function ProgressValue({
className,
children,
...props
}: Omit<React.ComponentProps<"span">, "children"> & {
children?: (value: string) => React.ReactNode
}) {
const { valueText } = useProgress()
return (
<span
className={cn("cn-progress-value", className)}
data-slot="progress-value"
{...props}
>
{children && valueText != null ? children(valueText) : valueText}
</span>
)
}
export {
Progress,
ProgressTrack,
ProgressIndicator,
ProgressLabel,
ProgressValue,
}

View File

@@ -0,0 +1,335 @@
"use client"
import * as React from "react"
import { Questionnaire as QuestionnairePrimitive } from "@shadcn/react/questionnaire"
import { cn } from "cn"
import { buttonVariants, type Button } from "@/registry/bases/aria/ui/button"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function Questionnaire({
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Root>) {
return (
<QuestionnairePrimitive.Root
data-slot="questionnaire"
className={cn("cn-questionnaire flex w-full min-w-0 flex-col", className)}
{...props}
/>
)
}
function QuestionnaireProgress({
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Progress>) {
return (
<QuestionnairePrimitive.Progress
data-slot="questionnaire-progress"
className={cn(
"cn-questionnaire-progress min-h-[1lh] w-fit min-w-[14ch] font-medium text-muted-foreground tabular-nums",
className
)}
{...props}
/>
)
}
function QuestionnaireItem({
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Item>) {
return (
<QuestionnairePrimitive.Item
data-slot="questionnaire-item"
className={cn(
"cn-questionnaire-item min-w-0 border-0 p-0 outline-none",
className
)}
{...props}
/>
)
}
function QuestionnaireTitle({
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Title>) {
return (
<QuestionnairePrimitive.Title
data-slot="questionnaire-title"
className={cn(
"cn-questionnaire-title cn-font-heading text-pretty",
className
)}
{...props}
/>
)
}
function QuestionnaireDescription({
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Description>) {
return (
<QuestionnairePrimitive.Description
data-slot="questionnaire-description"
className={cn(
"cn-questionnaire-description text-pretty text-muted-foreground",
className
)}
{...props}
/>
)
}
function QuestionnaireChoices({
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Choices>) {
return (
<QuestionnairePrimitive.Choices
data-slot="questionnaire-choices"
className={cn(
"cn-questionnaire-choices group/questionnaire-choices grid min-w-0",
className
)}
{...props}
/>
)
}
function QuestionnaireChoice({
children,
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Choice>) {
return (
<QuestionnairePrimitive.Choice
data-slot="questionnaire-choice"
className={cn(
"cn-questionnaire-choice group/questionnaire-choice relative flex min-h-11 cursor-pointer items-start text-start transition-colors outline-none select-none",
"data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50",
className
)}
{...props}
>
<QuestionnairePrimitive.ChoiceInput
data-slot="questionnaire-choice-input"
className="cn-questionnaire-choice-input absolute inset-0 z-10 size-full cursor-pointer opacity-0"
/>
<span
aria-hidden="true"
data-slot="questionnaire-choice-indicator"
className="cn-questionnaire-choice-indicator pointer-events-none relative flex shrink-0 items-center justify-center border group-data-[type=radio]/questionnaire-choice:rounded-full"
>
<span
data-slot="questionnaire-choice-indicator-dot"
className="cn-questionnaire-choice-indicator-dot hidden rounded-full group-data-[type=checkbox]/questionnaire-choice:hidden group-data-checked/questionnaire-choice:block"
/>
<IconPlaceholder
data-slot="questionnaire-choice-indicator-check"
className="cn-questionnaire-choice-indicator-check hidden group-data-[type=radio]/questionnaire-choice:hidden group-data-checked/questionnaire-choice:block"
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
</span>
<QuestionnairePrimitive.ChoiceLabel
data-slot="questionnaire-choice-label"
className="cn-questionnaire-choice-label cn-questionnaire-choice-content flex min-w-0 flex-1 flex-col leading-snug"
>
{children}
</QuestionnairePrimitive.ChoiceLabel>
<QuestionnairePrimitive.ChoiceShortcut
data-slot="questionnaire-choice-shortcut"
className="cn-questionnaire-choice-shortcut cn-questionnaire-shortcut pointer-events-none ms-auto hidden shrink-0 group-data-[shortcut]/questionnaire-choice:inline-flex"
/>
</QuestionnairePrimitive.Choice>
)
}
function QuestionnaireChoiceDescription({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="questionnaire-choice-description"
className={cn("cn-questionnaire-choice-description", className)}
{...props}
/>
)
}
function QuestionnaireInput({
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Input>) {
return (
<div
data-slot="questionnaire-input-wrapper"
className="cn-questionnaire-input-wrapper group/questionnaire-input relative min-w-0"
>
<QuestionnairePrimitive.Input
data-slot="questionnaire-input"
className={cn(
"cn-questionnaire-input min-h-11 w-full min-w-0 transition-[color,box-shadow,background-color] outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-0",
"selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground",
className
)}
{...props}
/>
</div>
)
}
function QuestionnaireError({
className,
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Error>) {
return (
<QuestionnairePrimitive.Error
data-slot="questionnaire-error"
className={cn("cn-questionnaire-error text-destructive", className)}
{...props}
/>
)
}
function QuestionnaireActions({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="questionnaire-actions"
className={cn(
"cn-questionnaire-actions grid min-h-11 w-full grid-cols-[minmax(0,1fr)_auto_auto] items-center",
className
)}
{...props}
/>
)
}
function QuestionnairePrevious({
children,
className,
size = "default",
variant = "outline",
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Previous> &
Pick<React.ComponentProps<typeof Button>, "size" | "variant">) {
return (
<QuestionnairePrimitive.Previous
data-slot="questionnaire-previous"
data-size={size}
data-variant={variant}
className={cn(
buttonVariants({ size, variant }),
"cn-questionnaire-previous col-start-1 row-start-1 min-h-11 justify-self-start sm:min-h-0",
className
)}
{...props}
>
{children ?? "Previous"}
</QuestionnairePrimitive.Previous>
)
}
function QuestionnaireSkip({
children,
className,
size = "default",
variant = "outline",
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Skip> &
Pick<React.ComponentProps<typeof Button>, "size" | "variant">) {
return (
<QuestionnairePrimitive.Skip
data-slot="questionnaire-skip"
data-size={size}
data-variant={variant}
className={cn(
buttonVariants({ size, variant }),
"cn-questionnaire-skip col-start-2 row-start-1 min-h-11 justify-self-end sm:min-h-0",
className
)}
{...props}
>
{children ?? "Skip"}
</QuestionnairePrimitive.Skip>
)
}
function QuestionnaireNext({
children,
className,
size = "default",
variant = "default",
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Next> &
Pick<React.ComponentProps<typeof Button>, "size" | "variant">) {
return (
<QuestionnairePrimitive.Next
data-slot="questionnaire-next"
data-size={size}
data-variant={variant}
className={cn(
buttonVariants({ size, variant }),
"cn-questionnaire-next col-start-3 row-start-1 min-h-11 justify-self-end sm:min-h-0",
className
)}
{...props}
>
{children ?? "Next"}
</QuestionnairePrimitive.Next>
)
}
function QuestionnaireSubmit({
children,
className,
size = "default",
variant = "default",
...props
}: React.ComponentProps<typeof QuestionnairePrimitive.Submit> &
Pick<React.ComponentProps<typeof Button>, "size" | "variant">) {
return (
<QuestionnairePrimitive.Submit
data-slot="questionnaire-submit"
data-size={size}
data-variant={variant}
className={cn(
buttonVariants({ size, variant }),
"cn-questionnaire-submit col-start-3 row-start-1 min-h-11 justify-self-end sm:min-h-0",
className
)}
{...props}
>
{children ?? "Submit"}
</QuestionnairePrimitive.Submit>
)
}
export {
Questionnaire,
QuestionnaireActions,
QuestionnaireChoice,
QuestionnaireChoiceDescription,
QuestionnaireChoices,
QuestionnaireDescription,
QuestionnaireError,
QuestionnaireInput,
QuestionnaireItem,
QuestionnaireNext,
QuestionnairePrevious,
QuestionnaireProgress,
QuestionnaireSkip,
QuestionnaireSubmit,
QuestionnaireTitle,
}

View File

@@ -0,0 +1,47 @@
"use client"
import { cn } from "cn"
import {
composeRenderProps,
RadioGroup as RadioGroupPrimitive,
Radio as RadioPrimitive,
type RadioGroupProps,
type RadioProps,
} from "react-aria-components"
function RadioGroup({ className, ...props }: RadioGroupProps) {
return (
<RadioGroupPrimitive
data-slot="radio-group"
className={cn("cn-radio-group w-full", className)}
{...props}
/>
)
}
function RadioGroupItem({ className, children, ...props }: RadioProps) {
return (
<RadioPrimitive
data-slot="radio-group-item"
className={cn(
"cn-radio-group-item cn-radio-group-item-aria group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
className
)}
{...props}
>
{composeRenderProps(children, (children, { isSelected }) => (
<>
<span
data-slot="radio-group-indicator"
className="cn-radio-group-indicator"
>
{isSelected && <span className="cn-radio-group-indicator-icon" />}
</span>
{children}
</>
))}
</RadioPrimitive>
)
}
export { RadioGroup, RadioGroupItem }

View File

@@ -0,0 +1,49 @@
"use client"
import { cn } from "cn"
import * as ResizablePrimitive from "react-resizable-panels"
function ResizablePanelGroup({
className,
...props
}: ResizablePrimitive.GroupProps) {
return (
<ResizablePrimitive.Group
data-slot="resizable-panel-group"
className={cn(
"cn-resizable-panel-group flex h-full w-full aria-[orientation=vertical]:flex-col",
className
)}
{...props}
/>
)
}
function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
}
function ResizableHandle({
withHandle,
className,
...props
}: ResizablePrimitive.SeparatorProps & {
withHandle?: boolean
}) {
return (
<ResizablePrimitive.Separator
data-slot="resizable-handle"
className={cn(
"cn-resizable-handle relative flex w-px items-center justify-center bg-border ring-offset-background after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
className
)}
{...props}
>
{withHandle && (
<div className="cn-resizable-handle-icon z-10 flex shrink-0" />
)}
</ResizablePrimitive.Separator>
)
}
export { ResizableHandle, ResizablePanel, ResizablePanelGroup }

View File

@@ -0,0 +1,26 @@
"use client"
import * as React from "react"
import { cn } from "cn"
function ScrollArea({
className,
children,
...props
}: React.ComponentProps<"div">) {
// Use native scrollbar-width and scrollbar-color to customize the scrollbar.
return (
<div
data-slot="scroll-area"
className={cn(
"cn-scroll-area relative [scrollbar-width:thin] [scrollbar-color:var(--color-border)_transparent] overflow-auto outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
className
)}
{...props}
>
{children}
</div>
)
}
export { ScrollArea }

View File

@@ -0,0 +1,297 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Button as ButtonPrimitive,
composeRenderProps,
Header as HeaderPrimitive,
ListBoxItem as ListBoxItemPrimitive,
ListBox as ListBoxPrimitive,
ListBoxSection as ListBoxSectionPrimitive,
Popover as PopoverPrimitive,
SearchField,
Select as SelectPrimitive,
SelectValue as SelectValuePrimitive,
Separator as SeparatorPrimitive,
type ListBoxProps,
type SearchFieldProps,
type ListBoxSectionProps as SelectGroupProps,
type SelectProps,
type SelectValueProps,
} from "react-aria-components"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function Select<T extends object, M extends "single" | "multiple" = "single">({
className,
...props
}: SelectProps<T, M>) {
return (
<SelectPrimitive
data-slot="select"
className={cn("w-fit", className)}
{...props}
/>
)
}
function SelectGroup<T extends object>({
className,
...props
}: SelectGroupProps<T>) {
return (
<ListBoxSectionPrimitive
data-slot="select-group"
className={cn("cn-select-group", className)}
{...props}
/>
)
}
function SelectValue<T extends object>({
className,
children,
...props
}: SelectValueProps<T>) {
return (
<SelectValuePrimitive
data-slot="select-value"
className={cn("cn-select-value cn-select-value-aria", className)}
{...props}
>
{typeof children === "function"
? children
: ({ selectedItems, selectedText, defaultChildren }) =>
selectedItems.length > 1 ? selectedText : defaultChildren}
</SelectValuePrimitive>
)
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: Omit<React.ComponentProps<typeof ButtonPrimitive>, "children"> & {
children?: React.ReactNode
size?: "sm" | "default"
}) {
return (
<ButtonPrimitive
data-slot="select-trigger"
data-size={size}
className={cn(
"cn-select-trigger flex w-full items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
{...props}
>
{children}
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconSelector"
hugeicons="UnfoldMoreIcon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
className="cn-select-trigger-icon pointer-events-none"
/>
</ButtonPrimitive>
)
}
function SelectContent({
className,
children,
placement = "bottom",
offset = 4,
crossOffset = 0,
...props
}: Omit<
React.ComponentProps<typeof PopoverPrimitive>,
"className" | "children"
> & {
className?: string
children?: React.ReactNode
}) {
return (
<SelectPopover
className={className}
placement={placement}
offset={offset}
crossOffset={crossOffset}
{...props}
>
<SelectList>{children}</SelectList>
</SelectPopover>
)
}
function SelectPopover({
className,
children,
placement = "bottom start",
offset = 4,
crossOffset = 0,
...props
}: Omit<
React.ComponentProps<typeof PopoverPrimitive>,
"className" | "children"
> & {
className?: string
children?: React.ReactNode
}) {
return (
<PopoverPrimitive
data-slot="select-content"
placement={placement}
offset={offset}
crossOffset={crossOffset}
className={cn(
"cn-select-content-aria cn-menu-target cn-menu-translucent cn-menu-translucent-aria relative isolate z-50 w-(--trigger-width) origin-(--trigger-anchor-point) overflow-hidden",
className
)}
{...props}
>
{children}
</PopoverPrimitive>
)
}
function SelectList<T extends object>({
className,
...props
}: ListBoxProps<T>) {
return (
<ListBoxPrimitive
data-slot="select-list"
className={cn(
"group/select-list max-h-[inherit] overflow-x-hidden overflow-y-auto p-0 outline-hidden",
className
)}
{...props}
/>
)
}
function SelectInput({ className, ...props }: SearchFieldProps) {
return (
<SearchField
{...props}
autoFocus
data-slot="select-input-wrapper"
className={cn("p-1 pb-0", className)}
>
<InputGroup>
<InputGroupInput
data-slot="select-input"
className="[&::-webkit-search-cancel-button]:hidden"
/>
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
className="cn-command-input-icon"
/>
</InputGroupAddon>
</InputGroup>
</SearchField>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof HeaderPrimitive>) {
return (
<HeaderPrimitive
data-slot="select-label"
className={cn("cn-select-label", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof ListBoxItemPrimitive>) {
return (
<ListBoxItemPrimitive
data-slot="select-item"
textValue={typeof children === "string" ? children : undefined}
className={cn(
"cn-select-item cn-select-item-aria relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
{...props}
>
{composeRenderProps(children, (children, { isSelected }) => (
<>
<span className="cn-select-item-text shrink-0 whitespace-nowrap">
{children}
</span>
<span className="cn-select-item-indicator">
{isSelected ? (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
className="cn-select-item-indicator-icon pointer-events-none"
/>
) : null}
</span>
</>
))}
</ListBoxItemPrimitive>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SeparatorPrimitive>) {
return (
<SeparatorPrimitive
data-slot="select-separator"
className={cn("cn-select-separator pointer-events-none", className)}
{...props}
/>
)
}
function SelectEmpty({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="select-empty"
className={cn("cn-select-empty-aria", className)}
{...props}
/>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectInput,
SelectItem,
SelectLabel,
SelectList,
SelectPopover,
SelectSeparator,
SelectTrigger,
SelectValue,
SelectEmpty,
}

View File

@@ -0,0 +1,24 @@
"use client"
import { cn } from "cn"
import { Separator as SeparatorPrimitive } from "react-aria-components"
function Separator({
className,
orientation = "horizontal",
...props
}: React.ComponentProps<typeof SeparatorPrimitive>) {
return (
<SeparatorPrimitive
data-slot="separator"
orientation={orientation}
className={cn(
"block shrink-0 border-0 bg-border aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=vertical]:w-px aria-[orientation=vertical]:self-stretch [:is(hr)]:h-px [:is(hr)]:w-full",
className
)}
{...props}
/>
)
}
export { Separator }

View File

@@ -0,0 +1,194 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Heading,
ModalOverlay as ModalOverlayPrimitive,
Modal as ModalPrimitive,
Dialog as SheetPrimitive,
DialogTrigger as SheetTriggerPrimitive,
type ModalOverlayProps as ModalOverlayPrimitiveProps,
type DialogProps as SheetPrimitiveProps,
type DialogTriggerProps as SheetTriggerPrimitiveProps,
} from "react-aria-components"
import { Button } from "@/registry/bases/aria/ui/button"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function SheetTrigger({ ...props }: SheetTriggerPrimitiveProps) {
return <SheetTriggerPrimitive data-slot="sheet-trigger" {...props} />
}
function SheetClose({
className,
variant = "outline",
size = "default",
...props
}: React.ComponentProps<typeof Button>) {
return (
<Button
slot="close"
data-slot="sheet-close"
variant={variant}
size={size}
className={cn(className)}
{...props}
/>
)
}
function SheetOverlay({
className,
children,
...props
}: Omit<ModalOverlayPrimitiveProps, "className" | "children"> & {
className?: string
children: React.ReactNode
}) {
return (
<ModalOverlayPrimitive
data-slot="sheet-overlay"
isDismissable
className={cn(
"cn-sheet-overlay fixed inset-0 z-50 transition-opacity duration-150 data-entering:opacity-0 data-exiting:opacity-0",
className
)}
{...props}
>
{children}
</ModalOverlayPrimitive>
)
}
function Sheet({
className,
children,
side = "right",
showCloseButton = true,
...props
}: Omit<ModalOverlayPrimitiveProps, "className" | "children"> &
Pick<React.ComponentProps<typeof ModalPrimitive>, "isDismissable"> & {
className?: string
children: React.ReactNode
side?: "top" | "right" | "bottom" | "left"
showCloseButton?: boolean
}) {
return (
<SheetOverlay {...props}>
<ModalPrimitive
data-slot="sheet-content"
data-side={side}
className={cn(
"cn-sheet-content data-entering:opacity-0 data-exiting:opacity-0 data-[side=bottom]:data-entering:translate-y-[2.5rem] data-[side=bottom]:data-exiting:translate-y-[2.5rem] data-[side=left]:data-entering:translate-x-[-2.5rem] data-[side=left]:data-exiting:translate-x-[-2.5rem] data-[side=right]:data-entering:translate-x-[2.5rem] data-[side=right]:data-exiting:translate-x-[2.5rem] data-[side=top]:data-entering:translate-y-[-2.5rem] data-[side=top]:data-exiting:translate-y-[-2.5rem]",
className
)}
>
<SheetPrimitive
data-slot="sheet"
className="[display:inherit] h-full max-h-[inherit] [flex-direction:inherit] [gap:inherit] outline-none"
>
{children}
{showCloseButton && (
<SheetClose
variant="ghost"
className="cn-sheet-close"
size="icon-sm"
>
<IconPlaceholder
lucide="XIcon"
tabler="IconX"
hugeicons="Cancel01Icon"
phosphor="XIcon"
remixicon="RiCloseLine"
/>
<span className="sr-only">Close</span>
</SheetClose>
)}
</SheetPrimitive>
</ModalPrimitive>
</SheetOverlay>
)
}
function SheetContent({
className,
children,
side = "right",
showCloseButton = true,
...props
}: React.ComponentProps<typeof Sheet> & {
side?: "top" | "right" | "bottom" | "left"
showCloseButton?: boolean
}) {
return (
<Sheet
className={className}
side={side}
showCloseButton={showCloseButton}
{...props}
>
{children}
</Sheet>
)
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("cn-sheet-header flex flex-col", className)}
{...props}
/>
)
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-footer"
className={cn("cn-sheet-footer mt-auto flex flex-col", className)}
{...props}
/>
)
}
function SheetTitle({
className,
...props
}: Omit<React.ComponentProps<typeof Heading>, "slot">) {
return (
<Heading
slot="title"
data-slot="sheet-title"
className={cn("cn-sheet-title cn-font-heading", className)}
{...props}
/>
)
}
function SheetDescription({
className,
...props
}: Omit<React.ComponentProps<"div">, "slot">) {
return (
<div
data-slot="sheet-description"
className={cn("cn-sheet-description", className)}
{...props}
/>
)
}
export {
type SheetPrimitiveProps,
type SheetTriggerPrimitiveProps,
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@@ -0,0 +1,713 @@
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import {
Button as ButtonPrimitive,
Link as LinkPrimitive,
type ButtonProps,
type LinkProps,
} from "react-aria-components"
import { useIsMobile } from "@/registry/bases/aria/hooks/use-mobile"
import { Button } from "@/registry/bases/aria/ui/button"
import { Input } from "@/registry/bases/aria/ui/input"
import { Separator } from "@/registry/bases/aria/ui/separator"
import {
Sheet,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/registry/bases/aria/ui/sheet"
import { Skeleton } from "@/registry/bases/aria/ui/skeleton"
import { Tooltip, TooltipTrigger } from "@/registry/bases/aria/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const SIDEBAR_COOKIE_NAME = "sidebar_state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"
const SIDEBAR_WIDTH_ICON = "3rem"
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
type SidebarContextProps = {
state: "expanded" | "collapsed"
open: boolean
setOpen: (open: boolean) => void
openMobile: boolean
setOpenMobile: (open: boolean) => void
isMobile: boolean
toggleSidebar: () => void
}
type SidebarButtonProps =
| (LinkProps & { href: string })
| (ButtonProps & { href?: never })
const SidebarContext = React.createContext<SidebarContextProps | null>(null)
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.")
}
return context
}
function SidebarProvider({
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
}: React.ComponentProps<"div"> & {
defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}) {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value
if (setOpenProp) {
setOpenProp(openState)
} else {
_setOpen(openState)
}
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open]
)
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault()
toggleSidebar()
}
}
window.addEventListener("keydown", handleKeyDown)
return () => window.removeEventListener("keydown", handleKeyDown)
}, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed"
const contextValue = React.useMemo<SidebarContextProps>(
() => ({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
)
return (
<SidebarContext.Provider value={contextValue}>
<div
data-slot="sidebar-wrapper"
style={
{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
}
className={cn(
"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
className
)}
{...props}
>
{children}
</div>
</SidebarContext.Provider>
)
}
function Sidebar({
side = "left",
variant = "sidebar",
collapsible = "offcanvas",
className,
children,
dir,
...props
}: React.ComponentProps<"div"> & {
side?: "left" | "right"
variant?: "sidebar" | "floating" | "inset"
collapsible?: "offcanvas" | "icon" | "none"
}) {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
if (collapsible === "none") {
return (
<div
data-slot="sidebar"
className={cn(
"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
className
)}
{...props}
>
{children}
</div>
)
}
if (isMobile) {
return (
<Sheet
isOpen={openMobile}
onOpenChange={setOpenMobile}
dir={dir}
data-sidebar="sidebar"
data-slot="sidebar"
data-mobile="true"
className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
side={side}
{...props}
>
<SheetHeader className="sr-only">
<SheetTitle>Sidebar</SheetTitle>
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
</SheetHeader>
<div className="flex h-full w-full flex-col">{children}</div>
</Sheet>
)
}
return (
<div
className="group peer hidden text-sidebar-foreground md:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
data-side={side}
data-slot="sidebar"
>
{/* This is what handles the sidebar gap on desktop */}
<div
data-slot="sidebar-gap"
className={cn(
"cn-sidebar-gap relative w-(--sidebar-width) bg-transparent",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
)}
/>
<div
data-slot="sidebar-container"
data-side={side}
className={cn(
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
className
)}
{...props}
>
<div
data-sidebar="sidebar"
data-slot="sidebar-inner"
className="cn-sidebar-inner flex size-full flex-col"
>
{children}
</div>
</div>
</div>
)
}
function SidebarTrigger({
className,
onPress,
...props
}: React.ComponentProps<typeof Button>) {
const { toggleSidebar } = useSidebar()
return (
<Button
data-sidebar="trigger"
data-slot="sidebar-trigger"
variant="ghost"
size="icon-sm"
className={cn("cn-sidebar-trigger", className)}
onPress={(event) => {
onPress?.(event)
toggleSidebar()
}}
{...props}
>
<IconPlaceholder
lucide="PanelLeftIcon"
tabler="IconLayoutSidebar"
hugeicons="SidebarLeftIcon"
phosphor="SidebarIcon"
remixicon="RiSideBarLine"
className="cn-rtl-flip"
/>
<span className="sr-only">Toggle Sidebar</span>
</Button>
)
}
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
const { toggleSidebar } = useSidebar()
return (
<button
data-sidebar="rail"
data-slot="sidebar-rail"
aria-label="Toggle Sidebar"
tabIndex={-1}
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"cn-sidebar-rail absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className
)}
{...props}
/>
)
}
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
return (
<main
data-slot="sidebar-inset"
className={cn(
"cn-sidebar-inset relative flex w-full flex-1 flex-col",
className
)}
{...props}
/>
)
}
function SidebarInput({
className,
...props
}: React.ComponentProps<typeof Input>) {
return (
<Input
data-slot="sidebar-input"
data-sidebar="input"
className={cn("cn-sidebar-input", className)}
{...props}
/>
)
}
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sidebar-header"
data-sidebar="header"
className={cn("cn-sidebar-header flex flex-col", className)}
{...props}
/>
)
}
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sidebar-footer"
data-sidebar="footer"
className={cn("cn-sidebar-footer flex flex-col", className)}
{...props}
/>
)
}
function SidebarSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="sidebar-separator"
data-sidebar="separator"
className={cn("cn-sidebar-separator w-auto", className)}
{...props}
/>
)
}
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sidebar-content"
data-sidebar="content"
className={cn(
"cn-sidebar-content flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className
)}
{...props}
/>
)
}
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sidebar-group"
data-sidebar="group"
className={cn(
"cn-sidebar-group relative flex w-full min-w-0 flex-col",
className
)}
{...props}
/>
)
}
function SidebarGroupLabel({
className,
elementType: Element = "div",
...props
}: React.HTMLAttributes<HTMLElement> & { elementType?: React.ElementType }) {
return (
<Element
data-slot="sidebar-group-label"
data-sidebar="group-label"
className={cn(
"cn-sidebar-group-label flex shrink-0 items-center outline-hidden [&>svg]:shrink-0",
className
)}
{...props}
/>
)
}
function SidebarGroupAction({
className,
...props
}: React.ComponentProps<"button">) {
return (
<button
data-slot="sidebar-group-action"
data-sidebar="group-action"
className={cn(
"cn-sidebar-group-action flex aspect-square items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 md:after:hidden [&>svg]:shrink-0",
className
)}
{...props}
/>
)
}
function SidebarGroupContent({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="sidebar-group-content"
data-sidebar="group-content"
className={cn("cn-sidebar-group-content w-full", className)}
{...props}
/>
)
}
function SidebarMenu({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sidebar-menu"
data-sidebar="menu"
className={cn("cn-sidebar-menu flex w-full min-w-0 flex-col", className)}
{...props}
/>
)
}
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sidebar-menu-item"
data-sidebar="menu-item"
className={cn("group/menu-item relative", className)}
{...props}
/>
)
}
const sidebarMenuButtonVariants = cva(
"cn-sidebar-menu-button cn-sidebar-menu-button-aria peer/menu-button group/menu-button flex w-full items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
{
variants: {
variant: {
default: "cn-sidebar-menu-button-variant-default",
outline: "cn-sidebar-menu-button-variant-outline",
},
size: {
default: "cn-sidebar-menu-button-size-default",
sm: "cn-sidebar-menu-button-size-sm",
lg: "cn-sidebar-menu-button-size-lg",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function SidebarMenuButton({
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
}: SidebarButtonProps & {
isActive?: boolean
tooltip?: string | React.ComponentProps<typeof Tooltip>
} & VariantProps<typeof sidebarMenuButtonVariants>) {
const { isMobile, state } = useSidebar()
const comp =
props.href !== undefined ? (
<LinkPrimitive
data-slot="sidebar-menu-button"
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
) : (
<ButtonPrimitive
data-slot="sidebar-menu-button"
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
)
if (!tooltip) {
return comp
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
}
}
return (
<TooltipTrigger isDisabled={state !== "collapsed" || isMobile}>
{comp}
<Tooltip placement="right" {...tooltip} />
</TooltipTrigger>
)
}
function SidebarMenuAction({
className,
showOnHover = false,
...props
}: ButtonProps & {
showOnHover?: boolean
}) {
return (
<ButtonPrimitive
data-slot="sidebar-menu-action"
data-sidebar="menu-action"
className={cn(
"cn-sidebar-menu-action flex items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 md:after:hidden [&>svg]:shrink-0",
showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0",
className
)}
{...props}
/>
)
}
function SidebarMenuBadge({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="sidebar-menu-badge"
data-sidebar="menu-badge"
className={cn(
"cn-sidebar-menu-badge flex items-center justify-center tabular-nums select-none group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
)
}
function SidebarMenuSkeleton({
className,
showIcon = false,
...props
}: React.ComponentProps<"div"> & {
showIcon?: boolean
}) {
// Random width between 50 to 90%.
const [width] = React.useState(() => {
return `${Math.floor(Math.random() * 40) + 50}%`
})
return (
<div
data-slot="sidebar-menu-skeleton"
data-sidebar="menu-skeleton"
className={cn("cn-sidebar-menu-skeleton flex items-center", className)}
{...props}
>
{showIcon && (
<Skeleton
className="cn-sidebar-menu-skeleton-icon"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton
className="cn-sidebar-menu-skeleton-text max-w-(--skeleton-width) flex-1"
data-sidebar="menu-skeleton-text"
style={
{
"--skeleton-width": width,
} as React.CSSProperties
}
/>
</div>
)
}
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
return (
<ul
data-slot="sidebar-menu-sub"
data-sidebar="menu-sub"
className={cn("cn-sidebar-menu-sub flex min-w-0 flex-col", className)}
{...props}
/>
)
}
function SidebarMenuSubItem({
className,
...props
}: React.ComponentProps<"li">) {
return (
<li
data-slot="sidebar-menu-sub-item"
data-sidebar="menu-sub-item"
className={cn("group/menu-sub-item relative", className)}
{...props}
/>
)
}
function SidebarMenuSubButton({
size = "md",
isActive = false,
className,
...props
}: SidebarButtonProps & {
size?: "sm" | "md"
isActive?: boolean
}) {
return props.href !== undefined ? (
<LinkPrimitive
data-slot="sidebar-menu-sub-button"
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
"cn-sidebar-menu-sub-button flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0",
className
)}
{...props}
/>
) : (
<ButtonPrimitive
data-slot="sidebar-menu-sub-button"
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
"cn-sidebar-menu-sub-button flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0",
className
)}
{...props}
/>
)
}
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
}

View File

@@ -0,0 +1,13 @@
import { cn } from "cn"
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn("cn-skeleton animate-pulse", className)}
{...props}
/>
)
}
export { Skeleton }

View File

@@ -0,0 +1,60 @@
"use client"
import { cn } from "cn"
import {
SliderFill,
Slider as SliderPrimitive,
SliderThumb,
SliderTrack,
type SliderProps as SliderPrimitiveProps,
} from "react-aria-components"
type SliderValue = number | number[]
type SliderProps<T extends SliderValue = SliderValue> = Omit<
SliderPrimitiveProps<T>,
"className"
> & {
className?: string
}
function Slider<T extends SliderValue = SliderValue>({
className,
...props
}: SliderProps<T>) {
return (
<SliderPrimitive
className={cn(
"cn-slider group relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:w-auto data-vertical:flex-col",
className
)}
data-slot="slider"
{...props}
>
{({ state }) => {
return (
<>
<SliderTrack
data-slot="slider-track"
className="cn-slider-track relative grow overflow-hidden select-none"
>
<SliderFill
data-slot="slider-range"
className="cn-slider-range absolute select-none data-horizontal:h-full data-vertical:w-full"
/>
</SliderTrack>
{state.values.map((_, index) => (
<SliderThumb
data-slot="slider-thumb"
key={index}
index={index}
className="cn-slider-thumb block shrink-0 select-none group-data-horizontal:top-[50%] group-data-vertical:left-[50%] disabled:pointer-events-none disabled:opacity-50"
/>
))}
</>
)
}}
</SliderPrimitive>
)
}
export { Slider }

View File

@@ -0,0 +1,85 @@
"use client"
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: (
<IconPlaceholder
lucide="CircleCheckIcon"
tabler="IconCircleCheck"
hugeicons="CheckmarkCircle02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
className="size-4"
/>
),
info: (
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="InformationCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
className="size-4"
/>
),
warning: (
<IconPlaceholder
lucide="TriangleAlertIcon"
tabler="IconAlertTriangle"
hugeicons="Alert02Icon"
phosphor="WarningIcon"
remixicon="RiErrorWarningLine"
className="size-4"
/>
),
error: (
<IconPlaceholder
lucide="OctagonXIcon"
tabler="IconAlertOctagon"
hugeicons="MultiplicationSignCircleIcon"
phosphor="XCircleIcon"
remixicon="RiCloseCircleLine"
className="size-4"
/>
),
loading: (
<IconPlaceholder
lucide="Loader2Icon"
tabler="IconLoader"
hugeicons="Loading03Icon"
phosphor="SpinnerIcon"
remixicon="RiLoaderLine"
className="size-4 animate-spin"
/>
),
}}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
} as React.CSSProperties
}
toastOptions={{
classNames: {
toast: "cn-toast",
},
}}
{...props}
/>
)
}
export { Toaster }

View File

@@ -0,0 +1,22 @@
import { cn } from "cn"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
<IconPlaceholder
lucide="Loader2Icon"
tabler="IconLoader"
hugeicons="Loading03Icon"
phosphor="SpinnerIcon"
remixicon="RiLoaderLine"
data-slot="spinner"
role="status"
aria-label="Loading"
className={cn("size-4 animate-spin", className)}
{...props}
/>
)
}
export { Spinner }

View File

@@ -0,0 +1,42 @@
"use client"
import { cn } from "cn"
import {
composeRenderProps,
Switch as SwitchPrimitive,
type SwitchProps as SwitchPrimitiveProps,
} from "react-aria-components"
function Switch({
className,
size = "default",
children,
...props
}: SwitchPrimitiveProps & {
size?: "sm" | "default"
}) {
return (
<SwitchPrimitive
data-slot="switch"
data-size={size}
className={cn(
"cn-switch cn-switch-aria peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-disabled:cursor-not-allowed data-disabled:opacity-50",
className
)}
{...props}
>
{composeRenderProps(children, (children, { isSelected }) => (
<>
<span
data-slot="switch-thumb"
data-selected={isSelected || undefined}
className="cn-switch-thumb cn-switch-thumb-aria pointer-events-none block ring-0 transition-transform"
/>
{children}
</>
))}
</SwitchPrimitive>
)
}
export { Switch }

View File

@@ -0,0 +1,122 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Cell as CellPrimitive,
Column as ColumnPrimitive,
Row as RowPrimitive,
TableBody as TableBodyPrimitive,
TableFooter as TableFooterPrimitive,
TableHeader as TableHeaderPrimitive,
Table as TablePrimitive,
type CellProps,
type ColumnProps,
type RowProps,
type TableBodyProps,
type TableFooterProps,
type TableHeaderProps,
type TableProps,
} from "react-aria-components"
function Table({ className, ...props }: TableProps) {
return (
<div data-slot="table-container" className="cn-table-container">
<TablePrimitive
data-slot="table"
className={cn("cn-table", className)}
{...props}
/>
</div>
)
}
function TableHeader<T>({ className, ...props }: TableHeaderProps<T>) {
return (
<TableHeaderPrimitive
data-slot="table-header"
className={cn("cn-table-header", className)}
{...props}
/>
)
}
function TableBody<T>({ className, ...props }: TableBodyProps<T>) {
return (
<TableBodyPrimitive
data-slot="table-body"
className={cn(
"cn-table-body data-empty:h-24 data-empty:text-center",
className
)}
{...props}
/>
)
}
function TableFooter<T>({ className, ...props }: TableFooterProps<T>) {
return (
<TableFooterPrimitive
data-slot="table-footer"
className={cn("cn-table-footer", className)}
{...props}
/>
)
}
function TableRow<T>({ className, ...props }: RowProps<T>) {
return (
<RowPrimitive
data-slot="table-row"
className={cn(
"cn-table-row cn-table-row-aria has-aria-expanded:bg-muted/50",
className
)}
{...props}
/>
)
}
function TableHead({ className, ...props }: ColumnProps) {
return (
<ColumnPrimitive
data-slot="table-head"
className={cn("cn-table-head cn-table-head-aria", className)}
{...props}
/>
)
}
function TableCell({ className, ...props }: CellProps) {
return (
<CellPrimitive
data-slot="table-cell"
className={cn("cn-table-cell cn-table-cell-aria", className)}
{...props}
/>
)
}
function TableCaption({
className,
...props
}: React.ComponentProps<"figcaption">) {
return (
<figcaption
data-slot="table-caption"
className={cn("cn-table-caption text-center", className)}
{...props}
/>
)
}
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@@ -0,0 +1,92 @@
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import {
TabList as TabListPrimitive,
TabPanel as TabPanelPrimitive,
Tab as TabPrimitive,
Tabs as TabsPrimitive,
} from "react-aria-components"
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive>) {
return (
<TabsPrimitive
data-slot="tabs"
className={cn(
"cn-tabs group/tabs flex data-horizontal:flex-col",
className
)}
{...props}
/>
)
}
const tabsListVariants = cva(
"cn-tabs-list group/tabs-list inline-flex w-fit items-center justify-center text-muted-foreground group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
{
variants: {
variant: {
default: "cn-tabs-list-variant-default bg-muted",
line: "cn-tabs-list-variant-line gap-1 bg-transparent",
},
},
defaultVariants: {
variant: "default",
},
}
)
function TabsList({
className,
variant = "default",
...props
}: React.ComponentProps<typeof TabListPrimitive> &
VariantProps<typeof tabsListVariants>) {
return (
<TabListPrimitive
data-slot="tabs-list"
data-variant={variant}
className={cn(tabsListVariants({ variant }), className)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabPrimitive>) {
return (
<TabPrimitive
data-slot="tabs-trigger"
className={cn(
"cn-tabs-trigger cn-tabs-trigger-aria relative inline-flex h-[calc(100%-1px)] flex-1 cursor-default items-center justify-center whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-selected:bg-transparent dark:group-data-[variant=line]/tabs-list:data-selected:border-transparent dark:group-data-[variant=line]/tabs-list:data-selected:bg-transparent",
"data-selected:bg-background data-selected:text-foreground dark:data-selected:border-input dark:data-selected:bg-input/30 dark:data-selected:text-foreground",
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-selected:after:opacity-100",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabPanelPrimitive>) {
return (
<TabPanelPrimitive
data-slot="tabs-content"
className={cn("cn-tabs-content flex-1 outline-none", className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }

View File

@@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
composeRenderProps,
TextArea as TextareaPrimitive,
} from "react-aria-components"
function Textarea({
className,
...props
}: React.ComponentProps<typeof TextareaPrimitive>) {
return (
<TextareaPrimitive
data-slot="textarea"
className={composeRenderProps(className, (className) =>
cn(
"cn-textarea flex field-sizing-content min-h-16 w-full outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)
)}
{...props}
/>
)
}
export { Textarea }

View File

@@ -0,0 +1,96 @@
"use client"
import * as React from "react"
import { type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import {
ToggleButtonGroup as ToggleGroupPrimitive,
ToggleButton as TogglePrimitive,
type ToggleButtonGroupProps,
type ToggleButtonProps,
} from "react-aria-components"
import { toggleVariants } from "@/registry/bases/aria/ui/toggle"
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants> & {
spacing?: number
orientation?: "horizontal" | "vertical"
}
>({
size: "default",
variant: "default",
spacing: 2,
orientation: "horizontal",
})
function ToggleGroup({
className,
variant,
size,
spacing = 2,
orientation = "horizontal",
children,
...props
}: Omit<ToggleButtonGroupProps, "children"> &
VariantProps<typeof toggleVariants> & {
spacing?: number
orientation?: "horizontal" | "vertical"
children?: React.ReactNode
}) {
return (
<ToggleGroupPrimitive
data-slot="toggle-group"
data-variant={variant}
data-size={size}
data-spacing={spacing}
orientation={orientation}
style={
{ "--gap": `calc(var(--spacing) * ${spacing})` } as React.CSSProperties
}
className={cn(
"cn-toggle-group group/toggle-group flex w-fit flex-row items-center gap-(--gap) data-vertical:flex-col data-vertical:items-stretch",
className
)}
{...props}
>
<ToggleGroupContext.Provider
value={{ variant, size, spacing, orientation }}
>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive>
)
}
function ToggleGroupItem({
className,
children,
variant = "default",
size = "default",
...props
}: ToggleButtonProps & VariantProps<typeof toggleVariants>) {
const context = React.useContext(ToggleGroupContext)
return (
<TogglePrimitive
data-slot="toggle-group-item"
data-variant={context.variant || variant}
data-size={context.size || size}
data-spacing={context.spacing}
className={cn(
"cn-toggle-group-item shrink-0 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className
)}
{...props}
>
{children}
</TogglePrimitive>
)
}
export { ToggleGroup, ToggleGroupItem }

View File

@@ -0,0 +1,46 @@
"use client"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import {
ToggleButton as TogglePrimitive,
type ToggleButtonProps,
} from "react-aria-components"
const toggleVariants = cva(
"cn-toggle cn-toggle-aria group/toggle inline-flex items-center justify-center whitespace-nowrap outline-none hover:bg-muted focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "cn-toggle-variant-default",
outline: "cn-toggle-variant-outline",
},
size: {
default: "cn-toggle-size-default",
sm: "cn-toggle-size-sm",
lg: "cn-toggle-size-lg",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Toggle({
className,
variant = "default",
size = "default",
...props
}: ToggleButtonProps & VariantProps<typeof toggleVariants>) {
return (
<TogglePrimitive
data-slot="toggle"
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Toggle, toggleVariants }

View File

@@ -0,0 +1,80 @@
"use client"
import * as React from "react"
import { cn } from "cn"
import {
Focusable,
OverlayArrow,
Tooltip as TooltipPrimitive,
TooltipTrigger as TooltipTriggerPrimitive,
} from "react-aria-components"
function TooltipTrigger({
delay = 0,
children,
...props
}: React.ComponentProps<typeof TooltipTriggerPrimitive>) {
const [trigger, tooltip] = React.Children.toArray(children)
return (
<TooltipTriggerPrimitive
data-slot="tooltip-trigger"
delay={delay}
{...props}
>
<Focusable>
{trigger as React.ComponentProps<typeof Focusable>["children"]}
</Focusable>
{tooltip}
</TooltipTriggerPrimitive>
)
}
function Tooltip({
className,
placement = "top",
offset = 4,
crossOffset = 0,
children,
...props
}: Omit<
React.ComponentProps<typeof TooltipPrimitive>,
"children" | "className"
> & {
className?: string
children?: React.ReactNode
}) {
return (
<TooltipPrimitive
data-slot="tooltip-content"
placement={placement}
offset={offset}
crossOffset={crossOffset}
className={cn(
"cn-tooltip-content-aria z-50 w-fit max-w-xs origin-(--trigger-anchor-point) bg-foreground text-background",
className
)}
{...props}
>
{children}
<OverlayArrow
className="cn-tooltip-arrow z-50 bg-foreground fill-foreground"
style={({ placement, defaultStyle }) => ({
...defaultStyle,
rotate: "0deg",
translate: "0 0",
transform:
placement === "bottom"
? "translate(-50%, calc(50% + 2px)) rotate(45deg)"
: placement === "top"
? "translate(-50%, calc(-50% - 2px)) rotate(45deg)"
: placement === "left"
? "translate(calc(-50% - 2px), -50%) rotate(45deg)"
: "translate(calc(50% + 2px), -50%) rotate(45deg)",
})}
/>
</TooltipPrimitive>
)
}
export { Tooltip, TooltipTrigger }