snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
1081
apps/v4/registry/bases/aria/ui/_registry.ts
Normal file
1081
apps/v4/registry/bases/aria/ui/_registry.ts
Normal file
File diff suppressed because it is too large
Load Diff
102
apps/v4/registry/bases/aria/ui/accordion.tsx
Normal file
102
apps/v4/registry/bases/aria/ui/accordion.tsx
Normal 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 }
|
||||
205
apps/v4/registry/bases/aria/ui/alert-dialog.tsx
Normal file
205
apps/v4/registry/bases/aria/ui/alert-dialog.tsx
Normal 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,
|
||||
}
|
||||
71
apps/v4/registry/bases/aria/ui/alert.tsx
Normal file
71
apps/v4/registry/bases/aria/ui/alert.tsx
Normal 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 }
|
||||
22
apps/v4/registry/bases/aria/ui/aspect-ratio.tsx
Normal file
22
apps/v4/registry/bases/aria/ui/aspect-ratio.tsx
Normal 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 }
|
||||
222
apps/v4/registry/bases/aria/ui/attachment.tsx
Normal file
222
apps/v4/registry/bases/aria/ui/attachment.tsx
Normal 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,
|
||||
}
|
||||
114
apps/v4/registry/bases/aria/ui/avatar.tsx
Normal file
114
apps/v4/registry/bases/aria/ui/avatar.tsx
Normal 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,
|
||||
}
|
||||
53
apps/v4/registry/bases/aria/ui/badge.tsx
Normal file
53
apps/v4/registry/bases/aria/ui/badge.tsx
Normal 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 }
|
||||
140
apps/v4/registry/bases/aria/ui/breadcrumb.tsx
Normal file
140
apps/v4/registry/bases/aria/ui/breadcrumb.tsx
Normal 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,
|
||||
}
|
||||
131
apps/v4/registry/bases/aria/ui/bubble.tsx
Normal file
131
apps/v4/registry/bases/aria/ui/bubble.tsx
Normal 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 }
|
||||
94
apps/v4/registry/bases/aria/ui/button-group.tsx
Normal file
94
apps/v4/registry/bases/aria/ui/button-group.tsx
Normal 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,
|
||||
}
|
||||
84
apps/v4/registry/bases/aria/ui/button.tsx
Normal file
84
apps/v4/registry/bases/aria/ui/button.tsx
Normal 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 }
|
||||
287
apps/v4/registry/bases/aria/ui/calendar.tsx
Normal file
287
apps/v4/registry/bases/aria/ui/calendar.tsx
Normal 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 }
|
||||
93
apps/v4/registry/bases/aria/ui/card.tsx
Normal file
93
apps/v4/registry/bases/aria/ui/card.tsx
Normal 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,
|
||||
}
|
||||
256
apps/v4/registry/bases/aria/ui/carousel.tsx
Normal file
256
apps/v4/registry/bases/aria/ui/carousel.tsx
Normal 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,
|
||||
}
|
||||
370
apps/v4/registry/bases/aria/ui/chart.tsx
Normal file
370
apps/v4/registry/bases/aria/ui/chart.tsx
Normal 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,
|
||||
}
|
||||
48
apps/v4/registry/bases/aria/ui/checkbox.tsx
Normal file
48
apps/v4/registry/bases/aria/ui/checkbox.tsx
Normal 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 }
|
||||
32
apps/v4/registry/bases/aria/ui/collapsible.tsx
Normal file
32
apps/v4/registry/bases/aria/ui/collapsible.tsx
Normal 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 }
|
||||
404
apps/v4/registry/bases/aria/ui/combobox.tsx
Normal file
404
apps/v4/registry/bases/aria/ui/combobox.tsx
Normal 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,
|
||||
}
|
||||
249
apps/v4/registry/bases/aria/ui/command.tsx
Normal file
249
apps/v4/registry/bases/aria/ui/command.tsx
Normal 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,
|
||||
}
|
||||
267
apps/v4/registry/bases/aria/ui/context-menu.tsx
Normal file
267
apps/v4/registry/bases/aria/ui/context-menu.tsx
Normal 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,
|
||||
}
|
||||
182
apps/v4/registry/bases/aria/ui/dialog.tsx
Normal file
182
apps/v4/registry/bases/aria/ui/dialog.tsx
Normal 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,
|
||||
}
|
||||
29
apps/v4/registry/bases/aria/ui/direction.tsx
Normal file
29
apps/v4/registry/bases/aria/ui/direction.tsx
Normal 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 }
|
||||
227
apps/v4/registry/bases/aria/ui/drawer.tsx
Normal file
227
apps/v4/registry/bases/aria/ui/drawer.tsx
Normal 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,
|
||||
}
|
||||
261
apps/v4/registry/bases/aria/ui/dropdown-menu.tsx
Normal file
261
apps/v4/registry/bases/aria/ui/dropdown-menu.tsx
Normal 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,
|
||||
}
|
||||
103
apps/v4/registry/bases/aria/ui/empty.tsx
Normal file
103
apps/v4/registry/bases/aria/ui/empty.tsx
Normal 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,
|
||||
}
|
||||
224
apps/v4/registry/bases/aria/ui/field.tsx
Normal file
224
apps/v4/registry/bases/aria/ui/field.tsx
Normal 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,
|
||||
}
|
||||
43
apps/v4/registry/bases/aria/ui/hover-card.tsx
Normal file
43
apps/v4/registry/bases/aria/ui/hover-card.tsx
Normal 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 }
|
||||
149
apps/v4/registry/bases/aria/ui/input-group.tsx
Normal file
149
apps/v4/registry/bases/aria/ui/input-group.tsx
Normal 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,
|
||||
}
|
||||
92
apps/v4/registry/bases/aria/ui/input-otp.tsx
Normal file
92
apps/v4/registry/bases/aria/ui/input-otp.tsx
Normal 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 }
|
||||
30
apps/v4/registry/bases/aria/ui/input.tsx
Normal file
30
apps/v4/registry/bases/aria/ui/input.tsx
Normal 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 }
|
||||
197
apps/v4/registry/bases/aria/ui/item.tsx
Normal file
197
apps/v4/registry/bases/aria/ui/item.tsx
Normal 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,
|
||||
}
|
||||
29
apps/v4/registry/bases/aria/ui/kbd.tsx
Normal file
29
apps/v4/registry/bases/aria/ui/kbd.tsx
Normal 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 }
|
||||
32
apps/v4/registry/bases/aria/ui/label.tsx
Normal file
32
apps/v4/registry/bases/aria/ui/label.tsx
Normal 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 }
|
||||
73
apps/v4/registry/bases/aria/ui/marker.tsx
Normal file
73
apps/v4/registry/bases/aria/ui/marker.tsx
Normal 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 }
|
||||
139
apps/v4/registry/bases/aria/ui/message-scroller.tsx
Normal file
139
apps/v4/registry/bases/aria/ui/message-scroller.tsx
Normal 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,
|
||||
}
|
||||
91
apps/v4/registry/bases/aria/ui/message.tsx
Normal file
91
apps/v4/registry/bases/aria/ui/message.tsx
Normal 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,
|
||||
}
|
||||
70
apps/v4/registry/bases/aria/ui/native-select.tsx
Normal file
70
apps/v4/registry/bases/aria/ui/native-select.tsx
Normal 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 }
|
||||
150
apps/v4/registry/bases/aria/ui/pagination.tsx
Normal file
150
apps/v4/registry/bases/aria/ui/pagination.tsx
Normal 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,
|
||||
}
|
||||
87
apps/v4/registry/bases/aria/ui/popover.tsx
Normal file
87
apps/v4/registry/bases/aria/ui/popover.tsx
Normal 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,
|
||||
}
|
||||
147
apps/v4/registry/bases/aria/ui/progress.tsx
Normal file
147
apps/v4/registry/bases/aria/ui/progress.tsx
Normal 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,
|
||||
}
|
||||
335
apps/v4/registry/bases/aria/ui/questionnaire.tsx
Normal file
335
apps/v4/registry/bases/aria/ui/questionnaire.tsx
Normal 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,
|
||||
}
|
||||
47
apps/v4/registry/bases/aria/ui/radio-group.tsx
Normal file
47
apps/v4/registry/bases/aria/ui/radio-group.tsx
Normal 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 }
|
||||
49
apps/v4/registry/bases/aria/ui/resizable.tsx
Normal file
49
apps/v4/registry/bases/aria/ui/resizable.tsx
Normal 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 }
|
||||
26
apps/v4/registry/bases/aria/ui/scroll-area.tsx
Normal file
26
apps/v4/registry/bases/aria/ui/scroll-area.tsx
Normal 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 }
|
||||
297
apps/v4/registry/bases/aria/ui/select.tsx
Normal file
297
apps/v4/registry/bases/aria/ui/select.tsx
Normal 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,
|
||||
}
|
||||
24
apps/v4/registry/bases/aria/ui/separator.tsx
Normal file
24
apps/v4/registry/bases/aria/ui/separator.tsx
Normal 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 }
|
||||
194
apps/v4/registry/bases/aria/ui/sheet.tsx
Normal file
194
apps/v4/registry/bases/aria/ui/sheet.tsx
Normal 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,
|
||||
}
|
||||
713
apps/v4/registry/bases/aria/ui/sidebar.tsx
Normal file
713
apps/v4/registry/bases/aria/ui/sidebar.tsx
Normal 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,
|
||||
}
|
||||
13
apps/v4/registry/bases/aria/ui/skeleton.tsx
Normal file
13
apps/v4/registry/bases/aria/ui/skeleton.tsx
Normal 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 }
|
||||
60
apps/v4/registry/bases/aria/ui/slider.tsx
Normal file
60
apps/v4/registry/bases/aria/ui/slider.tsx
Normal 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 }
|
||||
85
apps/v4/registry/bases/aria/ui/sonner.tsx
Normal file
85
apps/v4/registry/bases/aria/ui/sonner.tsx
Normal 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 }
|
||||
22
apps/v4/registry/bases/aria/ui/spinner.tsx
Normal file
22
apps/v4/registry/bases/aria/ui/spinner.tsx
Normal 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 }
|
||||
42
apps/v4/registry/bases/aria/ui/switch.tsx
Normal file
42
apps/v4/registry/bases/aria/ui/switch.tsx
Normal 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 }
|
||||
122
apps/v4/registry/bases/aria/ui/table.tsx
Normal file
122
apps/v4/registry/bases/aria/ui/table.tsx
Normal 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,
|
||||
}
|
||||
92
apps/v4/registry/bases/aria/ui/tabs.tsx
Normal file
92
apps/v4/registry/bases/aria/ui/tabs.tsx
Normal 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 }
|
||||
28
apps/v4/registry/bases/aria/ui/textarea.tsx
Normal file
28
apps/v4/registry/bases/aria/ui/textarea.tsx
Normal 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 }
|
||||
96
apps/v4/registry/bases/aria/ui/toggle-group.tsx
Normal file
96
apps/v4/registry/bases/aria/ui/toggle-group.tsx
Normal 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 }
|
||||
46
apps/v4/registry/bases/aria/ui/toggle.tsx
Normal file
46
apps/v4/registry/bases/aria/ui/toggle.tsx
Normal 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 }
|
||||
80
apps/v4/registry/bases/aria/ui/tooltip.tsx
Normal file
80
apps/v4/registry/bases/aria/ui/tooltip.tsx
Normal 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 }
|
||||
Reference in New Issue
Block a user