refactor!: rename the tailwind prefix from lib: to fui:

BREAKING: every utility this package emits is now fui:* instead of
lib:*. Consumers who wrote lib:-prefixed classes against @foomo/ui must
rename them.

"lib" is a plausible prefix for any in-house library, so it risked
colliding in apps that consume more than one. 3658 occurrences across
59 files, plus tailwind.config.js and components.json. The ./lib/*
export path is untouched — only the prefix token moved.

Also drops the ASCII banner comments and the explanatory docblocks I
had added to data-table and the dashboard story.

Verified: 19 stories x 2 themes, 194 overlays opened, 0 console errors
and 0 elements left unstyled.
This commit is contained in:
Wlad Meixner
2026-09-17 14:25:32 +02:00
parent 92fc19d52b
commit 3830364f9c
65 changed files with 1465 additions and 672 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ const withTheme: Decorator = (Story, context) => {
document.documentElement.style.backgroundColor = "var(--background)";
document.body.style.backgroundColor = "var(--background)";
document.body.style.color = "var(--foreground)";
document.body.classList.add("lib:bg-background", "lib:text-foreground");
document.body.classList.add("fui:bg-background", "fui:text-foreground");
return Story();
};
+1 -1
View File
@@ -8,7 +8,7 @@
"css": "src/index.css",
"baseColor": "mist",
"cssVariables": true,
"prefix": "lib"
"prefix": "fui"
},
"aliases": {
"components": "@/components",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@foomo/ui",
"version": "0.1.0-alpha.4",
"version": "0.1.0-alpha.5",
"type": "module",
"files": [
"dist",
@@ -45,7 +45,7 @@
},
"scripts": {
"dev": "vite",
"build": "rm -rf ./dist && tsc -p tsconfig.build.json && vite build",
"build": "rm -rf ./dist && tsc -p tsconfig.build.json && node scripts/rewrite-dts-aliases.mjs && vite build",
"lint": "biome lint --write ./src",
"format": "biome format --write ./src && biome check --fix --unsafe ./src",
"preview": "vite preview",
@@ -123,4 +123,4 @@
"vite-plugin-static-copy": "^3.1.4",
"vitest": "3.2.4"
}
}
}
+61
View File
@@ -0,0 +1,61 @@
/**
* Rewrite `@/…` path aliases in the emitted declaration files to relative
* specifiers.
*
* `tsc` resolves the alias when type-checking but emits it verbatim into the
* `.d.ts` output, and consumers have no `@/*` mapping — so every such import
* silently degrades to `any` on their side. That is not a cosmetic problem:
* `alert-dialog.d.ts` importing `Button` this way turns
* `Pick<ComponentProps<typeof Button>, "variant" | "size">` into
* `Pick<any, …>`, which makes both props *required* for callers.
*
* Vite already rewrites the alias in the `.js` output, so this only has to
* catch the declarations. It runs as a build step rather than a source-level
* change because `regenerate-components` re-runs the shadcn CLI, which always
* emits `@/` imports.
*/
import { readdir, readFile, writeFile } from "node:fs/promises";
import { join, relative, dirname, sep } from "node:path";
const DIST = new URL("../dist/", import.meta.url).pathname;
const ALIAS = /(from\s*")@\/([^"]+)(")/g;
async function* declarations(dir) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) {
yield* declarations(path);
} else if (entry.name.endsWith(".d.ts")) {
yield path;
}
}
}
let rewritten = 0;
let files = 0;
for await (const file of declarations(DIST)) {
const source = await readFile(file, "utf8");
let count = 0;
const output = source.replace(ALIAS, (_match, open, target, close) => {
count += 1;
// `dist/components/x.d.ts` -> `dist/`, so `@/components/button`
// becomes `./button`.
let path = relative(dirname(file), join(DIST, target)).split(sep).join("/");
if (!path.startsWith(".")) {
path = `./${path}`;
}
return `${open}${path}${close}`;
});
if (count > 0) {
await writeFile(file, output);
rewritten += count;
files += 1;
}
}
console.log(
`rewrite-dts-aliases: ${rewritten} import(s) across ${files} declaration file(s)`,
);
+8 -8
View File
@@ -7,7 +7,7 @@ function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
<AccordionPrimitive.Root
data-slot="accordion"
className={cn(
"lib:flex lib:w-full lib:flex-col lib:overflow-hidden lib:rounded-2xl lib:border",
"fui:flex fui:w-full fui:flex-col fui:overflow-hidden fui:rounded-2xl fui:border",
className,
)}
{...props}
@@ -20,7 +20,7 @@ function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn(
"lib:not-last:border-b lib:data-open:bg-muted/50",
"fui:not-last:border-b fui:data-open:bg-muted/50",
className,
)}
{...props}
@@ -34,11 +34,11 @@ function AccordionTrigger({
...props
}: AccordionPrimitive.Trigger.Props) {
return (
<AccordionPrimitive.Header className="lib:flex">
<AccordionPrimitive.Header className="fui:flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"lib:group/accordion-trigger lib:relative lib:flex lib:flex-1 lib:items-start lib:justify-between lib:gap-6 lib:border lib:border-transparent lib:p-4 lib:text-left lib:text-sm lib:font-medium lib:transition-all lib:outline-none lib:hover:underline lib:aria-disabled:pointer-events-none lib:aria-disabled:opacity-50 lib:**:data-[slot=accordion-trigger-icon]:ml-auto lib:**:data-[slot=accordion-trigger-icon]:size-4 lib:**:data-[slot=accordion-trigger-icon]:text-muted-foreground",
"fui:group/accordion-trigger fui:relative fui:flex fui:flex-1 fui:items-start fui:justify-between fui:gap-6 fui:border fui:border-transparent fui:p-4 fui:text-left fui:text-sm fui:font-medium fui:transition-all fui:outline-none fui:hover:underline fui:aria-disabled:pointer-events-none fui:aria-disabled:opacity-50 fui:**:data-[slot=accordion-trigger-icon]:ml-auto fui:**:data-[slot=accordion-trigger-icon]:size-4 fui:**:data-[slot=accordion-trigger-icon]:text-muted-foreground",
className,
)}
{...props}
@@ -46,11 +46,11 @@ function AccordionTrigger({
{children}
<ChevronDownIcon
data-slot="accordion-trigger-icon"
className="lib:pointer-events-none lib:shrink-0 lib:group-aria-expanded/accordion-trigger:hidden"
className="fui:pointer-events-none fui:shrink-0 fui:group-aria-expanded/accordion-trigger:hidden"
/>
<ChevronUpIcon
data-slot="accordion-trigger-icon"
className="lib:pointer-events-none lib:hidden lib:shrink-0 lib:group-aria-expanded/accordion-trigger:inline"
className="fui:pointer-events-none fui:hidden fui:shrink-0 fui:group-aria-expanded/accordion-trigger:inline"
/>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
@@ -65,12 +65,12 @@ function AccordionContent({
return (
<AccordionPrimitive.Panel
data-slot="accordion-content"
className="lib:overflow-hidden lib:px-4 lib:text-sm lib:data-open:animate-accordion-down lib:data-closed:animate-accordion-up"
className="fui:overflow-hidden fui:px-4 fui:text-sm fui:data-open:animate-accordion-down fui:data-closed:animate-accordion-up"
{...props}
>
<div
className={cn(
"lib:h-(--accordion-panel-height) lib:pt-0 lib:pb-4 lib:data-ending-style:h-0 lib:data-starting-style:h-0 lib:[&_a]:underline lib:[&_a]:underline-offset-3 lib:[&_a]:hover:text-foreground lib:[&_p:not(:last-child)]:mb-4",
"fui:h-(--accordion-panel-height) fui:pt-0 fui:pb-4 fui:data-ending-style:h-0 fui:data-starting-style:h-0 fui:[&_a]:underline fui:[&_a]:underline-offset-3 fui:[&_a]:hover:text-foreground fui:[&_p:not(:last-child)]:mb-4",
className,
)}
>
+7 -7
View File
@@ -28,7 +28,7 @@ function AlertDialogOverlay({
<AlertDialogPrimitive.Backdrop
data-slot="alert-dialog-overlay"
className={cn(
"lib:fixed lib:inset-0 lib:isolate lib:z-50 lib:bg-black/80 lib:duration-100 lib:supports-backdrop-filter:backdrop-blur-xs lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-closed:animate-out lib:data-closed:fade-out-0",
"fui:fixed fui:inset-0 fui:isolate fui:z-50 fui:bg-black/80 fui:duration-100 fui:supports-backdrop-filter:backdrop-blur-xs fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-closed:animate-out fui:data-closed:fade-out-0",
className,
)}
{...props}
@@ -50,7 +50,7 @@ function AlertDialogContent({
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"lib:group/alert-dialog-content lib:fixed lib:top-1/2 lib:left-1/2 lib:z-50 lib:grid lib:w-full lib:-translate-x-1/2 lib:-translate-y-1/2 lib:gap-6 lib:rounded-4xl lib:bg-popover lib:p-6 lib:text-popover-foreground lib:ring-1 lib:ring-foreground/5 lib:duration-100 lib:outline-none lib:data-[size=default]:max-w-xs lib:data-[size=sm]:max-w-xs lib:data-[size=default]:sm:max-w-md lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95",
"fui:group/alert-dialog-content fui:fixed fui:top-1/2 fui:left-1/2 fui:z-50 fui:grid fui:w-full fui:-translate-x-1/2 fui:-translate-y-1/2 fui:gap-6 fui:rounded-4xl fui:bg-popover fui:p-6 fui:text-popover-foreground fui:ring-1 fui:ring-foreground/5 fui:duration-100 fui:outline-none fui:data-[size=default]:max-w-xs fui:data-[size=sm]:max-w-xs fui:data-[size=default]:sm:max-w-md fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95",
className,
)}
{...props}
@@ -67,7 +67,7 @@ function AlertDialogHeader({
<div
data-slot="alert-dialog-header"
className={cn(
"lib:grid lib:grid-rows-[auto_1fr] lib:place-items-center lib:gap-1.5 lib:text-center lib:has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] lib:has-data-[slot=alert-dialog-media]:gap-x-6 lib:sm:group-data-[size=default]/alert-dialog-content:place-items-start lib:sm:group-data-[size=default]/alert-dialog-content:text-left lib:sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
"fui:grid fui:grid-rows-[auto_1fr] fui:place-items-center fui:gap-1.5 fui:text-center fui:has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] fui:has-data-[slot=alert-dialog-media]:gap-x-6 fui:sm:group-data-[size=default]/alert-dialog-content:place-items-start fui:sm:group-data-[size=default]/alert-dialog-content:text-left fui:sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
className,
)}
{...props}
@@ -83,7 +83,7 @@ function AlertDialogFooter({
<div
data-slot="alert-dialog-footer"
className={cn(
"lib:flex lib:flex-col-reverse lib:gap-2 lib:group-data-[size=sm]/alert-dialog-content:grid lib:group-data-[size=sm]/alert-dialog-content:grid-cols-2 lib:sm:flex-row lib:sm:justify-end",
"fui:flex fui:flex-col-reverse fui:gap-2 fui:group-data-[size=sm]/alert-dialog-content:grid fui:group-data-[size=sm]/alert-dialog-content:grid-cols-2 fui:sm:flex-row fui:sm:justify-end",
className,
)}
{...props}
@@ -99,7 +99,7 @@ function AlertDialogMedia({
<div
data-slot="alert-dialog-media"
className={cn(
"lib:mb-2 lib:inline-flex lib:size-16 lib:items-center lib:justify-center lib:rounded-full lib:bg-muted lib:sm:group-data-[size=default]/alert-dialog-content:row-span-2 lib:*:[svg:not([class*=size-])]:size-8",
"fui:mb-2 fui:inline-flex fui:size-16 fui:items-center fui:justify-center fui:rounded-full fui:bg-muted fui:sm:group-data-[size=default]/alert-dialog-content:row-span-2 fui:*:[svg:not([class*=size-])]:size-8",
className,
)}
{...props}
@@ -115,7 +115,7 @@ function AlertDialogTitle({
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn(
"lib:font-heading lib:text-lg lib:font-medium lib:sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
"fui:font-heading fui:text-lg fui:font-medium fui:sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className,
)}
{...props}
@@ -131,7 +131,7 @@ function AlertDialogDescription({
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn(
"lib:text-sm lib:text-balance lib:text-muted-foreground lib:md:text-pretty lib:*:[a]:underline lib:*:[a]:underline-offset-3 lib:*:[a]:hover:text-foreground",
"fui:text-sm fui:text-balance fui:text-muted-foreground fui:md:text-pretty fui:*:[a]:underline fui:*:[a]:underline-offset-3 fui:*:[a]:hover:text-foreground",
className,
)}
{...props}
+6 -6
View File
@@ -3,13 +3,13 @@ import { cn } from "cn";
import type * as React from "react";
const alertVariants = cva(
"lib:group/alert lib:relative lib:grid lib:w-full lib:gap-0.5 lib:rounded-lg lib:border lib:px-4 lib:py-3 lib:text-left lib:text-sm lib:has-data-[slot=alert-action]:relative lib:has-data-[slot=alert-action]:pr-18 lib:has-[>svg]:grid-cols-[auto_1fr] lib:has-[>svg]:gap-x-2.5 lib:*:[svg]:row-span-2 lib:*:[svg]:translate-y-0.5 lib:*:[svg]:text-current lib:*:[svg:not([class*=size-])]:size-4",
"fui:group/alert fui:relative fui:grid fui:w-full fui:gap-0.5 fui:rounded-lg fui:border fui:px-4 fui:py-3 fui:text-left fui:text-sm fui:has-data-[slot=alert-action]:relative fui:has-data-[slot=alert-action]:pr-18 fui:has-[>svg]:grid-cols-[auto_1fr] fui:has-[>svg]:gap-x-2.5 fui:*:[svg]:row-span-2 fui:*:[svg]:translate-y-0.5 fui:*:[svg]:text-current fui:*:[svg:not([class*=size-])]:size-4",
{
variants: {
variant: {
default: "lib:bg-card lib:text-card-foreground",
default: "fui:bg-card fui:text-card-foreground",
destructive:
"lib:bg-card lib:text-destructive lib:*:data-[slot=alert-description]:text-destructive/90 lib:*:[svg]:text-current",
"fui:bg-card fui:text-destructive fui:*:data-[slot=alert-description]:text-destructive/90 fui:*:[svg]:text-current",
},
},
defaultVariants: {
@@ -38,7 +38,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="alert-title"
className={cn(
"lib:font-medium lib:group-has-[>svg]/alert:col-start-2 lib:[&_a]:underline lib:[&_a]:underline-offset-3 lib:[&_a]:hover:text-foreground",
"fui:font-medium fui:group-has-[>svg]/alert:col-start-2 fui:[&_a]:underline fui:[&_a]:underline-offset-3 fui:[&_a]:hover:text-foreground",
className,
)}
{...props}
@@ -54,7 +54,7 @@ function AlertDescription({
<div
data-slot="alert-description"
className={cn(
"lib:text-sm lib:text-balance lib:text-muted-foreground lib:md:text-pretty lib:[&_a]:underline lib:[&_a]:underline-offset-3 lib:[&_a]:hover:text-foreground lib:[&_p:not(:last-child)]:mb-4",
"fui:text-sm fui:text-balance fui:text-muted-foreground fui:md:text-pretty fui:[&_a]:underline fui:[&_a]:underline-offset-3 fui:[&_a]:hover:text-foreground fui:[&_p:not(:last-child)]:mb-4",
className,
)}
{...props}
@@ -66,7 +66,7 @@ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-action"
className={cn("lib:absolute lib:top-2.5 lib:right-3", className)}
className={cn("fui:absolute fui:top-2.5 fui:right-3", className)}
{...props}
/>
);
+1 -1
View File
@@ -14,7 +14,7 @@ function AspectRatio({
"--ratio": ratio,
} as React.CSSProperties
}
className={cn("lib:relative lib:aspect-(--ratio)", className)}
className={cn("fui:relative fui:aspect-(--ratio)", className)}
{...props}
/>
);
+9 -9
View File
@@ -14,7 +14,7 @@ function Avatar({
data-slot="avatar"
data-size={size}
className={cn(
"lib:group/avatar lib:relative lib:flex lib:size-8 lib:shrink-0 lib:rounded-full lib:select-none lib:after:absolute lib:after:inset-0 lib:after:rounded-full lib:after:border lib:after:border-border lib:after:mix-blend-darken lib:data-[size=lg]:size-10 lib:data-[size=sm]:size-6 lib:dark:after:mix-blend-lighten",
"fui:group/avatar fui:relative fui:flex fui:size-8 fui:shrink-0 fui:rounded-full fui:select-none fui:after:absolute fui:after:inset-0 fui:after:rounded-full fui:after:border fui:after:border-border fui:after:mix-blend-darken fui:data-[size=lg]:size-10 fui:data-[size=sm]:size-6 fui:dark:after:mix-blend-lighten",
className,
)}
{...props}
@@ -27,7 +27,7 @@ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn(
"lib:aspect-square lib:size-full lib:rounded-full lib:object-cover",
"fui:aspect-square fui:size-full fui:rounded-full fui:object-cover",
className,
)}
{...props}
@@ -43,7 +43,7 @@ function AvatarFallback({
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"lib:flex lib:size-full lib:items-center lib:justify-center lib:rounded-full lib:bg-muted lib:text-sm lib:text-muted-foreground lib:group-data-[size=sm]/avatar:text-xs",
"fui:flex fui:size-full fui:items-center fui:justify-center fui:rounded-full fui:bg-muted fui:text-sm fui:text-muted-foreground fui:group-data-[size=sm]/avatar:text-xs",
className,
)}
{...props}
@@ -56,10 +56,10 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
<span
data-slot="avatar-badge"
className={cn(
"lib:absolute lib:right-0 lib:bottom-0 lib:z-10 lib:inline-flex lib:items-center lib:justify-center lib:rounded-full lib:bg-primary lib:text-primary-foreground lib:bg-blend-color lib:ring-2 lib:ring-background lib:select-none",
"lib:group-data-[size=sm]/avatar:size-2 lib:group-data-[size=sm]/avatar:[&>svg]:hidden",
"lib:group-data-[size=default]/avatar:size-2.5 lib:group-data-[size=default]/avatar:[&>svg]:size-2",
"lib:group-data-[size=lg]/avatar:size-3 lib:group-data-[size=lg]/avatar:[&>svg]:size-2",
"fui:absolute fui:right-0 fui:bottom-0 fui:z-10 fui:inline-flex fui:items-center fui:justify-center fui:rounded-full fui:bg-primary fui:text-primary-foreground fui:bg-blend-color fui:ring-2 fui:ring-background fui:select-none",
"fui:group-data-[size=sm]/avatar:size-2 fui:group-data-[size=sm]/avatar:[&>svg]:hidden",
"fui:group-data-[size=default]/avatar:size-2.5 fui:group-data-[size=default]/avatar:[&>svg]:size-2",
"fui:group-data-[size=lg]/avatar:size-3 fui:group-data-[size=lg]/avatar:[&>svg]:size-2",
className,
)}
{...props}
@@ -72,7 +72,7 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="avatar-group"
className={cn(
"lib:group/avatar-group lib:flex lib:-space-x-2 lib:*:data-[slot=avatar]:ring-2 lib:*:data-[slot=avatar]:ring-background",
"fui:group/avatar-group fui:flex fui:-space-x-2 fui:*:data-[slot=avatar]:ring-2 fui:*:data-[slot=avatar]:ring-background",
className,
)}
{...props}
@@ -88,7 +88,7 @@ function AvatarGroupCount({
<div
data-slot="avatar-group-count"
className={cn(
"lib:relative lib:flex lib:size-8 lib:shrink-0 lib:items-center lib:justify-center lib:rounded-full lib:bg-muted lib:text-sm lib:text-muted-foreground lib:ring-2 lib:ring-background lib:group-has-data-[size=lg]/avatar-group:size-10 lib:group-has-data-[size=sm]/avatar-group:size-6 lib:[&>svg]:size-4 lib:group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 lib:group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
"fui:relative fui:flex fui:size-8 fui:shrink-0 fui:items-center fui:justify-center fui:rounded-full fui:bg-muted fui:text-sm fui:text-muted-foreground fui:ring-2 fui:ring-background fui:group-has-data-[size=lg]/avatar-group:size-10 fui:group-has-data-[size=sm]/avatar-group:size-6 fui:[&>svg]:size-4 fui:group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 fui:group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className,
)}
{...props}
+7 -7
View File
@@ -4,21 +4,21 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "cn";
const badgeVariants = cva(
"lib:group/badge lib:inline-flex lib:h-5 lib:w-fit lib:shrink-0 lib:items-center lib:justify-center lib:gap-1 lib:overflow-hidden lib:rounded-4xl lib:border lib:border-transparent lib:px-2 lib:py-0.5 lib:text-xs lib:font-medium lib:whitespace-nowrap lib:transition-all lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:has-data-[icon=inline-end]:pr-1.5 lib:has-data-[icon=inline-start]:pl-1.5 lib:aria-invalid:border-destructive lib:aria-invalid:ring-destructive/20 lib:dark:aria-invalid:ring-destructive/40 lib:[&>svg]:pointer-events-none lib:[&>svg]:size-3!",
"fui:group/badge fui:inline-flex fui:h-5 fui:w-fit fui:shrink-0 fui:items-center fui:justify-center fui:gap-1 fui:overflow-hidden fui:rounded-4xl fui:border fui:border-transparent fui:px-2 fui:py-0.5 fui:text-xs fui:font-medium fui:whitespace-nowrap fui:transition-all fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:has-data-[icon=inline-end]:pr-1.5 fui:has-data-[icon=inline-start]:pl-1.5 fui:aria-invalid:border-destructive fui:aria-invalid:ring-destructive/20 fui:dark:aria-invalid:ring-destructive/40 fui:[&>svg]:pointer-events-none fui:[&>svg]:size-3!",
{
variants: {
variant: {
default:
"lib:bg-primary lib:text-primary-foreground lib:[a]:hover:bg-primary/80",
"fui:bg-primary fui:text-primary-foreground fui:[a]:hover:bg-primary/80",
secondary:
"lib:bg-secondary lib:text-secondary-foreground lib:[a]:hover:bg-secondary/80",
"fui:bg-secondary fui:text-secondary-foreground fui:[a]:hover:bg-secondary/80",
destructive:
"lib:bg-destructive/10 lib:text-destructive lib:focus-visible:ring-destructive/20 lib:dark:bg-destructive/20 lib:dark:focus-visible:ring-destructive/40 lib:[a]:hover:bg-destructive/20",
"fui:bg-destructive/10 fui:text-destructive fui:focus-visible:ring-destructive/20 fui:dark:bg-destructive/20 fui:dark:focus-visible:ring-destructive/40 fui:[a]:hover:bg-destructive/20",
outline:
"lib:border-border lib:bg-input/30 lib:text-foreground lib:[a]:hover:bg-muted lib:[a]:hover:text-muted-foreground",
"fui:border-border fui:bg-input/30 fui:text-foreground fui:[a]:hover:bg-muted fui:[a]:hover:text-muted-foreground",
ghost:
"lib:hover:bg-muted lib:hover:text-muted-foreground lib:dark:hover:bg-muted/50",
link: "lib:text-primary lib:underline-offset-4 lib:hover:underline",
"fui:hover:bg-muted fui:hover:text-muted-foreground fui:dark:hover:bg-muted/50",
link: "fui:text-link fui:underline-offset-4 fui:hover:underline",
},
},
defaultVariants: {
+7 -7
View File
@@ -20,7 +20,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
<ol
data-slot="breadcrumb-list"
className={cn(
"lib:flex lib:flex-wrap lib:items-center lib:gap-1.5 lib:text-sm lib:wrap-break-word lib:text-muted-foreground lib:sm:gap-2.5",
"fui:flex fui:flex-wrap fui:items-center fui:gap-1.5 fui:text-sm fui:wrap-break-word fui:text-muted-foreground fui:sm:gap-2.5",
className,
)}
{...props}
@@ -32,7 +32,7 @@ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-item"
className={cn("lib:inline-flex lib:items-center lib:gap-1.5", className)}
className={cn("fui:inline-flex fui:items-center fui:gap-1.5", className)}
{...props}
/>
);
@@ -48,7 +48,7 @@ function BreadcrumbLink({
props: mergeProps<"a">(
{
className: cn(
"lib:transition-colors lib:hover:text-foreground",
"fui:transition-colors fui:hover:text-foreground",
className,
),
},
@@ -68,7 +68,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
role="link"
aria-disabled="true"
aria-current="page"
className={cn("lib:font-normal lib:text-foreground", className)}
className={cn("fui:font-normal fui:text-foreground", className)}
{...props}
/>
);
@@ -84,7 +84,7 @@ function BreadcrumbSeparator({
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn("lib:[&>svg]:size-3.5", className)}
className={cn("fui:[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRightIcon />}
@@ -102,13 +102,13 @@ function BreadcrumbEllipsis({
role="presentation"
aria-hidden="true"
className={cn(
"lib:flex lib:size-5 lib:items-center lib:justify-center lib:[&>svg]:size-4",
"fui:flex fui:size-5 fui:items-center fui:justify-center fui:[&>svg]:size-4",
className,
)}
{...props}
>
<MoreHorizontalIcon />
<span className="lib:sr-only">More</span>
<span className="fui:sr-only">More</span>
</span>
);
}
+5 -5
View File
@@ -6,14 +6,14 @@ import { cn } from "cn";
import { Separator } from "@/components/separator";
const buttonGroupVariants = cva(
"lib:flex lib:w-fit lib:items-stretch lib:*:focus-visible:relative lib:*:focus-visible:z-10 lib:has-[>[data-slot=button-group]]:gap-2 lib:has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-4xl lib:[&>[data-slot=select-trigger]:not([class*=w-])]:w-fit lib:[&>input]:flex-1",
"fui:flex fui:w-fit fui:items-stretch fui:*:focus-visible:relative fui:*:focus-visible:z-10 fui:has-[>[data-slot=button-group]]:gap-2 fui:has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-4xl fui:[&>[data-slot=select-trigger]:not([class*=w-])]:w-fit fui:[&>input]:flex-1",
{
variants: {
orientation: {
horizontal:
"lib:*:data-slot:rounded-r-none lib:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-4xl! lib:[&>[data-slot]~[data-slot]]:rounded-l-none lib:[&>[data-slot]~[data-slot]]:border-l-0",
"fui:*:data-slot:rounded-r-none fui:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-4xl! fui:[&>[data-slot]~[data-slot]]:rounded-l-none fui:[&>[data-slot]~[data-slot]]:border-l-0",
vertical:
"lib:flex-col lib:*:data-slot:rounded-b-none lib:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-4xl! lib:[&>[data-slot]~[data-slot]]:rounded-t-none lib:[&>[data-slot]~[data-slot]]:border-t-0",
"fui:flex-col fui:*:data-slot:rounded-b-none fui:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-4xl! fui:[&>[data-slot]~[data-slot]]:rounded-t-none fui:[&>[data-slot]~[data-slot]]:border-t-0",
},
},
defaultVariants: {
@@ -48,7 +48,7 @@ function ButtonGroupText({
props: mergeProps<"div">(
{
className: cn(
"flex items-center gap-2 rounded-4xl border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
"fui:flex fui:items-center fui:gap-2 fui:rounded-4xl fui:border fui:bg-muted fui:px-2.5 fui:text-sm fui:font-medium fui:[&_svg]:pointer-events-none fui:[&_svg:not([class*='size-'])]:size-4",
className,
),
},
@@ -71,7 +71,7 @@ function ButtonGroupSeparator({
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"lib:relative lib:self-stretch lib:bg-input lib:data-horizontal:mx-px lib:data-horizontal:w-auto lib:data-vertical:my-px lib:data-vertical:h-auto",
"fui:relative fui:self-stretch fui:bg-input fui:data-horizontal:mx-px fui:data-horizontal:w-auto fui:data-vertical:my-px fui:data-vertical:h-auto",
className,
)}
{...props}
+15 -15
View File
@@ -3,32 +3,32 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "cn";
const buttonVariants = cva(
"lib:group/button lib:inline-flex lib:shrink-0 lib:items-center lib:justify-center lib:rounded-4xl lib:border lib:border-transparent lib:bg-clip-padding lib:text-sm lib:font-medium lib:whitespace-nowrap lib:transition-all lib:outline-none lib:select-none lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:active:not-aria-[haspopup]:translate-y-px lib:disabled:pointer-events-none lib:disabled:opacity-50 lib:aria-invalid:border-destructive lib:aria-invalid:ring-[3px] lib:aria-invalid:ring-destructive/20 lib:dark:aria-invalid:border-destructive/50 lib:dark:aria-invalid:ring-destructive/40 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:group/button fui:inline-flex fui:shrink-0 fui:items-center fui:justify-center fui:rounded-4xl fui:border fui:border-transparent fui:bg-clip-padding fui:text-sm fui:font-medium fui:whitespace-nowrap fui:transition-all fui:outline-none fui:select-none fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:active:not-aria-[haspopup]:translate-y-px fui:disabled:pointer-events-none fui:disabled:opacity-50 fui:aria-invalid:border-destructive fui:aria-invalid:ring-[3px] fui:aria-invalid:ring-destructive/20 fui:dark:aria-invalid:border-destructive/50 fui:dark:aria-invalid:ring-destructive/40 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
{
variants: {
variant: {
default:
"lib:bg-primary lib:text-primary-foreground lib:hover:bg-primary/80",
"fui:bg-primary fui:text-primary-foreground fui:hover:bg-primary/80",
outline:
"lib:border-border lib:bg-input/30 lib:hover:bg-input/50 lib:hover:text-foreground lib:aria-expanded:bg-muted lib:aria-expanded:text-foreground",
"fui:border-border fui:bg-input/30 fui:hover:bg-input/50 fui:hover:text-foreground fui:aria-expanded:bg-muted fui:aria-expanded:text-foreground",
secondary:
"lib:bg-secondary lib:text-secondary-foreground lib:hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] lib:aria-expanded:bg-secondary lib:aria-expanded:text-secondary-foreground",
"fui:bg-secondary fui:text-secondary-foreground fui:hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] fui:aria-expanded:bg-secondary fui:aria-expanded:text-secondary-foreground",
ghost:
"lib:hover:bg-muted lib:hover:text-foreground lib:aria-expanded:bg-muted lib:aria-expanded:text-foreground lib:dark:hover:bg-muted/50",
"fui:hover:bg-muted fui:hover:text-foreground fui:aria-expanded:bg-muted fui:aria-expanded:text-foreground fui:dark:hover:bg-muted/50",
destructive:
"lib:bg-destructive/10 lib:text-destructive lib:hover:bg-destructive/20 lib:focus-visible:border-destructive/40 lib:focus-visible:ring-destructive/20 lib:dark:bg-destructive/20 lib:dark:hover:bg-destructive/30 lib:dark:focus-visible:ring-destructive/40",
link: "lib:text-primary lib:underline-offset-4 lib:hover:underline",
"fui:bg-destructive/10 fui:text-destructive fui:hover:bg-destructive/20 fui:focus-visible:border-destructive/40 fui:focus-visible:ring-destructive/20 fui:dark:bg-destructive/20 fui:dark:hover:bg-destructive/30 fui:dark:focus-visible:ring-destructive/40",
link: "fui:text-link fui:underline-offset-4 fui:hover:underline",
},
size: {
default:
"lib:h-9 lib:gap-1.5 lib:px-3 lib:has-data-[icon=inline-end]:pr-2.5 lib:has-data-[icon=inline-start]:pl-2.5",
xs: "lib:h-6 lib:gap-1 lib:px-2.5 lib:text-xs lib:has-data-[icon=inline-end]:pr-2 lib:has-data-[icon=inline-start]:pl-2 lib:[&_svg:not([class*=size-])]:size-3",
sm: "lib:h-8 lib:gap-1 lib:px-3 lib:has-data-[icon=inline-end]:pr-2 lib:has-data-[icon=inline-start]:pl-2",
lg: "lib:h-10 lib:gap-1.5 lib:px-4 lib:has-data-[icon=inline-end]:pr-3 lib:has-data-[icon=inline-start]:pl-3",
icon: "lib:size-9",
"icon-xs": "lib:size-6 lib:[&_svg:not([class*=size-])]:size-3",
"icon-sm": "lib:size-8",
"icon-lg": "lib:size-10",
"fui:h-9 fui:gap-1.5 fui:px-3 fui:has-data-[icon=inline-end]:pr-2.5 fui:has-data-[icon=inline-start]:pl-2.5",
xs: "fui:h-6 fui:gap-1 fui:px-2.5 fui:text-xs fui:has-data-[icon=inline-end]:pr-2 fui:has-data-[icon=inline-start]:pl-2 fui:[&_svg:not([class*=size-])]:size-3",
sm: "fui:h-8 fui:gap-1 fui:px-3 fui:has-data-[icon=inline-end]:pr-2 fui:has-data-[icon=inline-start]:pl-2",
lg: "fui:h-10 fui:gap-1.5 fui:px-4 fui:has-data-[icon=inline-end]:pr-3 fui:has-data-[icon=inline-start]:pl-3",
icon: "fui:size-9",
"icon-xs": "fui:size-6 fui:[&_svg:not([class*=size-])]:size-3",
"icon-sm": "fui:size-8",
"icon-lg": "fui:size-10",
},
},
defaultVariants: {
+35 -35
View File
@@ -32,7 +32,7 @@ function Calendar({
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"lib:group/calendar lib:bg-background lib:p-3 lib:[--cell-radius:var(--radius-4xl)] lib:[--cell-size:--spacing(8)] lib:in-data-[slot=card-content]:bg-transparent lib:in-data-[slot=popover-content]:bg-transparent",
"fui:group/calendar fui:bg-background fui:p-3 fui:[--cell-radius:var(--radius-4xl)] fui:[--cell-size:--spacing(8)] fui:in-data-[slot=card-content]:bg-transparent fui:in-data-[slot=popover-content]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className,
@@ -45,99 +45,99 @@ function Calendar({
...formatters,
}}
classNames={{
root: cn("lib:w-fit", defaultClassNames.root),
root: cn("fui:w-fit", defaultClassNames.root),
months: cn(
"lib:relative lib:flex lib:flex-col lib:gap-4 lib:md:flex-row",
"fui:relative fui:flex fui:flex-col fui:gap-4 fui:md:flex-row",
defaultClassNames.months,
),
month: cn(
"lib:flex lib:w-full lib:flex-col lib:gap-4",
"fui:flex fui:w-full fui:flex-col fui:gap-4",
defaultClassNames.month,
),
nav: cn(
"lib:absolute lib:inset-x-0 lib:top-0 lib:flex lib:w-full lib:items-center lib:justify-between lib:gap-1",
"fui:absolute fui:inset-x-0 fui:top-0 fui:flex fui:w-full fui:items-center fui:justify-between fui:gap-1",
defaultClassNames.nav,
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"lib:size-(--cell-size) lib:p-0 lib:select-none lib:aria-disabled:opacity-50",
"fui:size-(--cell-size) fui:p-0 fui:select-none fui:aria-disabled:opacity-50",
defaultClassNames.button_previous,
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"lib:size-(--cell-size) lib:p-0 lib:select-none lib:aria-disabled:opacity-50",
"fui:size-(--cell-size) fui:p-0 fui:select-none fui:aria-disabled:opacity-50",
defaultClassNames.button_next,
),
month_caption: cn(
"lib:flex lib:h-(--cell-size) lib:w-full lib:items-center lib:justify-center lib:px-(--cell-size)",
"fui:flex fui:h-(--cell-size) fui:w-full fui:items-center fui:justify-center fui:px-(--cell-size)",
defaultClassNames.month_caption,
),
dropdowns: cn(
"lib:flex lib:h-(--cell-size) lib:w-full lib:items-center lib:justify-center lib:gap-1.5 lib:text-sm lib:font-medium",
"fui:flex fui:h-(--cell-size) fui:w-full fui:items-center fui:justify-center fui:gap-1.5 fui:text-sm fui:font-medium",
defaultClassNames.dropdowns,
),
dropdown_root: cn(
"lib:relative lib:rounded-(--cell-radius)",
"fui:relative fui:rounded-(--cell-radius)",
defaultClassNames.dropdown_root,
),
dropdown: cn(
"lib:absolute lib:inset-0 lib:bg-popover lib:opacity-0",
"fui:absolute fui:inset-0 fui:bg-popover fui:opacity-0",
defaultClassNames.dropdown,
),
caption_label: cn(
"lib:font-medium lib:select-none",
"fui:font-medium fui:select-none",
captionLayout === "label"
? "lib:text-sm"
: "lib:flex lib:items-center lib:gap-1 lib:rounded-(--cell-radius) lib:text-sm lib:[&>svg]:size-3.5 lib:[&>svg]:text-muted-foreground",
? "fui:text-sm"
: "fui:flex fui:items-center fui:gap-1 fui:rounded-(--cell-radius) fui:text-sm fui:[&>svg]:size-3.5 fui:[&>svg]:text-muted-foreground",
defaultClassNames.caption_label,
),
month_grid: cn(
"lib:w-full lib:border-collapse",
"fui:w-full fui:border-collapse",
defaultClassNames.month_grid,
),
weekdays: cn("lib:flex", defaultClassNames.weekdays),
weekdays: cn("fui:flex", defaultClassNames.weekdays),
weekday: cn(
"lib:flex-1 lib:rounded-(--cell-radius) lib:text-[0.8rem] lib:font-normal lib:text-muted-foreground lib:select-none",
"fui:flex-1 fui:rounded-(--cell-radius) fui:text-[0.8rem] fui:font-normal fui:text-muted-foreground fui:select-none",
defaultClassNames.weekday,
),
week: cn("lib:mt-2 lib:flex lib:w-full", defaultClassNames.week),
week: cn("fui:mt-2 fui:flex fui:w-full", defaultClassNames.week),
week_number_header: cn(
"lib:w-(--cell-size) lib:select-none",
"fui:w-(--cell-size) fui:select-none",
defaultClassNames.week_number_header,
),
week_number: cn(
"lib:text-[0.8rem] lib:text-muted-foreground lib:select-none",
"fui:text-[0.8rem] fui:text-muted-foreground fui:select-none",
defaultClassNames.week_number,
),
day: cn(
"lib:group/day lib:relative lib:aspect-square lib:h-full lib:w-full lib:rounded-(--cell-radius) lib:p-0 lib:text-center lib:select-none lib:[&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
"fui:group/day fui:relative fui:aspect-square fui:h-full fui:w-full fui:rounded-(--cell-radius) fui:p-0 fui:text-center fui:select-none fui:[&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
props.showWeekNumber
? "lib:[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
: "lib:[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
? "fui:[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
: "fui:[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
defaultClassNames.day,
),
range_start: cn(
"lib:relative lib:isolate lib:z-0 lib:rounded-l-(--cell-radius) lib:bg-muted lib:after:absolute lib:after:inset-y-0 lib:after:right-0 lib:after:w-4 lib:after:bg-muted",
"fui:relative fui:isolate fui:z-0 fui:rounded-l-(--cell-radius) fui:bg-muted fui:after:absolute fui:after:inset-y-0 fui:after:right-0 fui:after:w-4 fui:after:bg-muted",
defaultClassNames.range_start,
),
range_middle: cn("lib:rounded-none", defaultClassNames.range_middle),
range_middle: cn("fui:rounded-none", defaultClassNames.range_middle),
range_end: cn(
"lib:relative lib:isolate lib:z-0 lib:rounded-r-(--cell-radius) lib:bg-muted lib:after:absolute lib:after:inset-y-0 lib:after:left-0 lib:after:w-4 lib:after:bg-muted",
"fui:relative fui:isolate fui:z-0 fui:rounded-r-(--cell-radius) fui:bg-muted fui:after:absolute fui:after:inset-y-0 fui:after:left-0 fui:after:w-4 fui:after:bg-muted",
defaultClassNames.range_end,
),
today: cn(
"lib:rounded-(--cell-radius) lib:bg-muted lib:text-foreground lib:data-[selected=true]:rounded-none",
"fui:rounded-(--cell-radius) fui:bg-muted fui:text-foreground fui:data-[selected=true]:rounded-none",
defaultClassNames.today,
),
outside: cn(
"lib:text-muted-foreground lib:aria-selected:text-muted-foreground",
"fui:text-muted-foreground fui:aria-selected:text-muted-foreground",
defaultClassNames.outside,
),
disabled: cn(
"lib:text-muted-foreground lib:opacity-50",
"fui:text-muted-foreground fui:opacity-50",
defaultClassNames.disabled,
),
hidden: cn("lib:invisible", defaultClassNames.hidden),
hidden: cn("fui:invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
@@ -155,7 +155,7 @@ function Calendar({
if (orientation === "left") {
return (
<ChevronLeftIcon
className={cn("lib:size-4", className)}
className={cn("fui:size-4", className)}
{...props}
/>
);
@@ -164,7 +164,7 @@ function Calendar({
if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("lib:size-4", className)}
className={cn("fui:size-4", className)}
{...props}
/>
);
@@ -172,7 +172,7 @@ function Calendar({
return (
<ChevronDownIcon
className={cn("lib:size-4", className)}
className={cn("fui:size-4", className)}
{...props}
/>
);
@@ -183,7 +183,7 @@ function Calendar({
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="lib:flex lib:size-(--cell-size) lib:items-center lib:justify-center lib:text-center">
<div className="fui:flex fui:size-(--cell-size) fui:items-center fui:justify-center fui:text-center">
{children}
</div>
</td>
@@ -226,7 +226,7 @@ function CalendarDayButton({
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"lib:relative lib:isolate lib:z-10 lib:flex lib:aspect-square lib:size-auto lib:w-full lib:min-w-(--cell-size) lib:flex-col lib:gap-1 lib:border-0 lib:leading-none lib:font-normal lib:group-data-[focused=true]/day:relative lib:group-data-[focused=true]/day:z-10 lib:group-data-[focused=true]/day:border-ring lib:group-data-[focused=true]/day:ring-[3px] lib:group-data-[focused=true]/day:ring-ring/50 lib:data-[range-end=true]:rounded-(--cell-radius) lib:data-[range-end=true]:rounded-r-(--cell-radius) lib:data-[range-end=true]:bg-primary lib:data-[range-end=true]:text-primary-foreground lib:data-[range-middle=true]:rounded-none lib:data-[range-middle=true]:bg-muted lib:data-[range-middle=true]:text-foreground lib:data-[range-start=true]:rounded-(--cell-radius) lib:data-[range-start=true]:rounded-l-(--cell-radius) lib:data-[range-start=true]:bg-primary lib:data-[range-start=true]:text-primary-foreground lib:data-[selected-single=true]:bg-primary lib:data-[selected-single=true]:text-primary-foreground lib:dark:hover:text-foreground lib:[&>span]:text-xs lib:[&>span]:opacity-70",
"fui:relative fui:isolate fui:z-10 fui:flex fui:aspect-square fui:size-auto fui:w-full fui:min-w-(--cell-size) fui:flex-col fui:gap-1 fui:border-0 fui:leading-none fui:font-normal fui:group-data-[focused=true]/day:relative fui:group-data-[focused=true]/day:z-10 fui:group-data-[focused=true]/day:border-ring fui:group-data-[focused=true]/day:ring-[3px] fui:group-data-[focused=true]/day:ring-ring/50 fui:data-[range-end=true]:rounded-(--cell-radius) fui:data-[range-end=true]:rounded-r-(--cell-radius) fui:data-[range-end=true]:bg-primary fui:data-[range-end=true]:text-primary-foreground fui:data-[range-middle=true]:rounded-none fui:data-[range-middle=true]:bg-muted fui:data-[range-middle=true]:text-foreground fui:data-[range-start=true]:rounded-(--cell-radius) fui:data-[range-start=true]:rounded-l-(--cell-radius) fui:data-[range-start=true]:bg-primary fui:data-[range-start=true]:text-primary-foreground fui:data-[selected-single=true]:bg-primary fui:data-[selected-single=true]:text-primary-foreground fui:dark:hover:text-foreground fui:[&>span]:text-xs fui:[&>span]:opacity-70",
defaultClassNames.day,
className,
)}
+7 -7
View File
@@ -11,7 +11,7 @@ function Card({
data-slot="card"
data-size={size}
className={cn(
"lib:group/card lib:flex lib:flex-col lib:gap-(--card-spacing) lib:overflow-hidden lib:rounded-2xl lib:bg-card lib:py-(--card-spacing) lib:text-sm lib:text-card-foreground lib:ring-1 lib:ring-foreground/10 lib:[--card-spacing:--spacing(6)] lib:has-[>img:first-child]:pt-0 lib:data-[size=sm]:[--card-spacing:--spacing(4)] lib:*:[img:first-child]:rounded-t-xl lib:*:[img:last-child]:rounded-b-xl",
"fui:group/card fui:flex fui:flex-col fui:gap-(--card-spacing) fui:overflow-hidden fui:rounded-2xl fui:bg-card fui:py-(--card-spacing) fui:text-sm fui:text-card-foreground fui:ring-1 fui:ring-foreground/10 fui:[--card-spacing:--spacing(6)] fui:has-[>img:first-child]:pt-0 fui:data-[size=sm]:[--card-spacing:--spacing(4)] fui:*:[img:first-child]:rounded-t-xl fui:*:[img:last-child]:rounded-b-xl",
className,
)}
{...props}
@@ -24,7 +24,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-header"
className={cn(
"lib:group/card-header lib:@container/card-header lib:grid lib:auto-rows-min lib:items-start lib:gap-2 lib:rounded-t-xl lib:px-(--card-spacing) lib:has-data-[slot=card-action]:grid-cols-[1fr_auto] lib:has-data-[slot=card-description]:grid-rows-[auto_auto] lib:[.border-b]:pb-(--card-spacing)",
"fui:group/card-header fui:@container/card-header fui:grid fui:auto-rows-min fui:items-start fui:gap-2 fui:rounded-t-xl fui:px-(--card-spacing) fui:has-data-[slot=card-action]:grid-cols-[1fr_auto] fui:has-data-[slot=card-description]:grid-rows-[auto_auto] fui:[.border-b]:pb-(--card-spacing)",
className,
)}
{...props}
@@ -37,7 +37,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-title"
className={cn(
"lib:font-heading lib:text-base lib:font-medium",
"fui:font-heading fui:text-base fui:font-medium",
className,
)}
{...props}
@@ -49,7 +49,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("lib:text-sm lib:text-muted-foreground", className)}
className={cn("fui:text-sm fui:text-muted-foreground", className)}
{...props}
/>
);
@@ -60,7 +60,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-action"
className={cn(
"lib:col-start-2 lib:row-span-2 lib:row-start-1 lib:self-start lib:justify-self-end",
"fui:col-start-2 fui:row-span-2 fui:row-start-1 fui:self-start fui:justify-self-end",
className,
)}
{...props}
@@ -72,7 +72,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("lib:px-(--card-spacing)", className)}
className={cn("fui:px-(--card-spacing)", className)}
{...props}
/>
);
@@ -83,7 +83,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-footer"
className={cn(
"lib:flex lib:items-center lib:rounded-b-xl lib:px-(--card-spacing) lib:[.border-t]:pt-(--card-spacing)",
"fui:flex fui:items-center fui:rounded-b-xl fui:px-(--card-spacing) fui:[.border-t]:pt-(--card-spacing)",
className,
)}
{...props}
+14 -14
View File
@@ -117,7 +117,7 @@ function Carousel({
>
<div
onKeyDownCapture={handleKeyDown}
className={cn("lib:relative", className)}
className={cn("fui:relative", className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
@@ -135,13 +135,13 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
ref={carouselRef}
className="lib:overflow-hidden"
className="fui:overflow-hidden"
data-slot="carousel-content"
>
<div
className={cn(
"lib:flex",
orientation === "horizontal" ? "lib:-ml-4" : "lib:-mt-4 lib:flex-col",
"fui:flex",
orientation === "horizontal" ? "fui:-ml-4" : "fui:-mt-4 fui:flex-col",
className,
)}
{...props}
@@ -159,8 +159,8 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
aria-roledescription="slide"
data-slot="carousel-item"
className={cn(
"lib:min-w-0 lib:shrink-0 lib:grow-0 lib:basis-full",
orientation === "horizontal" ? "lib:pl-4" : "lib:pt-4",
"fui:min-w-0 fui:shrink-0 fui:grow-0 fui:basis-full",
orientation === "horizontal" ? "fui:pl-4" : "fui:pt-4",
className,
)}
{...props}
@@ -182,10 +182,10 @@ function CarouselPrevious({
variant={variant}
size={size}
className={cn(
"lib:absolute lib:touch-manipulation lib:rounded-full",
"fui:absolute fui:touch-manipulation fui:rounded-full",
orientation === "horizontal"
? "lib:inset-y-0 lib:-left-12 lib:my-auto"
: "lib:-top-12 lib:left-1/2 lib:-translate-x-1/2 lib:rotate-90",
? "fui:inset-y-0 fui:-left-12 fui:my-auto"
: "fui:-top-12 fui:left-1/2 fui:-translate-x-1/2 fui:rotate-90",
className,
)}
disabled={!canScrollPrev}
@@ -193,7 +193,7 @@ function CarouselPrevious({
{...props}
>
<ChevronLeftIcon />
<span className="lib:sr-only">Previous slide</span>
<span className="fui:sr-only">Previous slide</span>
</Button>
);
}
@@ -212,10 +212,10 @@ function CarouselNext({
variant={variant}
size={size}
className={cn(
"lib:absolute lib:touch-manipulation lib:rounded-full",
"fui:absolute fui:touch-manipulation fui:rounded-full",
orientation === "horizontal"
? "lib:inset-y-0 lib:-right-12 lib:my-auto"
: "lib:-bottom-12 lib:left-1/2 lib:-translate-x-1/2 lib:rotate-90",
? "fui:inset-y-0 fui:-right-12 fui:my-auto"
: "fui:-bottom-12 fui:left-1/2 fui:-translate-x-1/2 fui:rotate-90",
className,
)}
disabled={!canScrollNext}
@@ -223,7 +223,7 @@ function CarouselNext({
{...props}
>
<ChevronRightIcon />
<span className="lib:sr-only">Next slide</span>
<span className="fui:sr-only">Next slide</span>
</Button>
);
}
+17 -17
View File
@@ -64,7 +64,7 @@ function ChartContainer({
data-slot="chart"
data-chart={chartId}
className={cn(
"lib:flex lib:aspect-video lib:justify-center lib:text-xs lib:[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground lib:[&_.recharts-cartesian-grid_line[stroke=#ccc]]:stroke-border/50 lib:[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border lib:[&_.recharts-dot[stroke=#fff]]:stroke-transparent lib:[&_.recharts-layer]:outline-hidden lib:[&_.recharts-polar-grid_[stroke=#ccc]]:stroke-border lib:[&_.recharts-radial-bar-background-sector]:fill-muted lib:[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted lib:[&_.recharts-reference-line_[stroke=#ccc]]:stroke-border lib:[&_.recharts-sector]:outline-hidden lib:[&_.recharts-sector[stroke=#fff]]:stroke-transparent lib:[&_.recharts-surface]:outline-hidden",
"fui:flex fui:aspect-video fui:justify-center fui:text-xs fui:[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground fui:[&_.recharts-cartesian-grid_line[stroke=#ccc]]:stroke-border/50 fui:[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border fui:[&_.recharts-dot[stroke=#fff]]:stroke-transparent fui:[&_.recharts-layer]:outline-hidden fui:[&_.recharts-polar-grid_[stroke=#ccc]]:stroke-border fui:[&_.recharts-radial-bar-background-sector]:fill-muted fui:[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted fui:[&_.recharts-reference-line_[stroke=#ccc]]:stroke-border fui:[&_.recharts-sector]:outline-hidden fui:[&_.recharts-sector[stroke=#fff]]:stroke-transparent fui:[&_.recharts-surface]:outline-hidden",
className,
)}
{...props}
@@ -160,7 +160,7 @@ function ChartTooltipContent({
if (labelFormatter) {
return (
<div className={cn("lib:font-medium", labelClassName)}>
<div className={cn("fui:font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
);
@@ -170,7 +170,7 @@ function ChartTooltipContent({
return null;
}
return <div className={cn("lib:font-medium", labelClassName)}>{value}</div>;
return <div className={cn("fui:font-medium", labelClassName)}>{value}</div>;
}, [
label,
labelFormatter,
@@ -190,12 +190,12 @@ function ChartTooltipContent({
return (
<div
className={cn(
"lib:grid lib:min-w-32 lib:items-start lib:gap-1.5 lib:rounded-lg lib:border lib:border-border/50 lib:bg-background lib:px-2.5 lib:py-1.5 lib:text-xs lib:shadow-xl",
"fui:grid fui:min-w-32 fui:items-start fui:gap-1.5 fui:rounded-lg fui:border fui:border-border/50 fui:bg-background fui:px-2.5 fui:py-1.5 fui:text-xs fui:shadow-xl",
className,
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="lib:grid lib:gap-1.5">
<div className="fui:grid fui:gap-1.5">
{payload
.filter((item) => item.type !== "none")
.map((item, index) => {
@@ -207,8 +207,8 @@ function ChartTooltipContent({
<div
key={index}
className={cn(
"lib:flex lib:w-full lib:flex-wrap lib:items-stretch lib:gap-2 lib:[&>svg]:h-2.5 lib:[&>svg]:w-2.5 lib:[&>svg]:text-muted-foreground",
indicator === "dot" && "lib:items-center",
"fui:flex fui:w-full fui:flex-wrap fui:items-stretch fui:gap-2 fui:[&>svg]:h-2.5 fui:[&>svg]:w-2.5 fui:[&>svg]:text-muted-foreground",
indicator === "dot" && "fui:items-center",
)}
>
{formatter && item?.value !== undefined && item.name ? (
@@ -221,7 +221,7 @@ function ChartTooltipContent({
!hideIndicator && (
<div
className={cn(
"lib:shrink-0 lib:rounded-[2px] lib:border-(--color-border) lib:bg-(--color-bg)",
"fui:shrink-0 fui:rounded-[2px] fui:border-(--color-border) fui:bg-(--color-bg)",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
@@ -241,18 +241,18 @@ function ChartTooltipContent({
)}
<div
className={cn(
"lib:flex lib:flex-1 lib:justify-between lib:leading-none",
nestLabel ? "lib:items-end" : "lib:items-center",
"fui:flex fui:flex-1 fui:justify-between fui:leading-none",
nestLabel ? "fui:items-end" : "fui:items-center",
)}
>
<div className="lib:grid lib:gap-1.5">
<div className="fui:grid fui:gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="lib:text-muted-foreground">
<span className="fui:text-muted-foreground">
{itemConfig?.label ?? item.name}
</span>
</div>
{item.value != null && (
<span className="lib:font-mono lib:font-medium lib:text-foreground lib:tabular-nums">
<span className="fui:font-mono fui:font-medium fui:text-foreground fui:tabular-nums">
{typeof item.value === "number"
? item.value.toLocaleString()
: String(item.value)}
@@ -290,8 +290,8 @@ function ChartLegendContent({
return (
<div
className={cn(
"lib:flex lib:items-center lib:justify-center lib:gap-4",
verticalAlign === "top" ? "lib:pb-3" : "lib:pt-3",
"fui:flex fui:items-center fui:justify-center fui:gap-4",
verticalAlign === "top" ? "fui:pb-3" : "fui:pt-3",
className,
)}
>
@@ -305,14 +305,14 @@ function ChartLegendContent({
<div
key={index}
className={cn(
"lib:flex lib:items-center lib:gap-1.5 lib:[&>svg]:h-3 lib:[&>svg]:w-3 lib:[&>svg]:text-muted-foreground",
"fui:flex fui:items-center fui:gap-1.5 fui:[&>svg]:h-3 fui:[&>svg]:w-3 fui:[&>svg]:text-muted-foreground",
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="lib:h-2 lib:w-2 lib:shrink-0 lib:rounded-[2px]"
className="fui:h-2 fui:w-2 fui:shrink-0 fui:rounded-[2px]"
style={{
backgroundColor: item.color,
}}
+54 -3
View File
@@ -3,20 +3,22 @@
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
import { cn } from "cn";
import { CheckIcon } from "lucide-react";
import * as React from "react";
import { Label } from "@/components/label";
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"lib:peer lib:relative lib:flex lib:size-4 lib:shrink-0 lib:items-center lib:justify-center lib:rounded-[6px] lib:border lib:border-input lib:transition-shadow lib:outline-none lib:group-has-disabled/field:opacity-50 lib:group-has-[:focus-visible]/field-label:ring-0 lib:group-has-[:focus-visible]/field-label:not-data-checked:border-input lib:after:absolute lib:after:-inset-x-3 lib:after:-inset-y-2 lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:disabled:cursor-not-allowed lib:disabled:opacity-50 lib:aria-invalid:border-destructive lib:aria-invalid:ring-[3px] lib:aria-invalid:ring-destructive/20 lib:aria-invalid:aria-checked:border-primary lib:dark:bg-input/30 lib:dark:aria-invalid:border-destructive/50 lib:dark:aria-invalid:ring-destructive/40 lib:data-checked:border-primary lib:data-checked:bg-primary lib:data-checked:text-primary-foreground lib:group-has-[:focus-visible]/field-label:data-checked:border-primary lib:dark:data-checked:bg-primary",
"fui:peer fui:relative fui:flex fui:size-4 fui:shrink-0 fui:items-center fui:justify-center fui:rounded-[6px] fui:border fui:border-input fui:transition-shadow fui:outline-none fui:group-has-disabled/field:opacity-50 fui:group-has-[:focus-visible]/field-label:ring-0 fui:group-has-[:focus-visible]/field-label:not-data-checked:border-input fui:after:absolute fui:after:-inset-x-3 fui:after:-inset-y-2 fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:disabled:cursor-not-allowed fui:disabled:opacity-50 fui:aria-invalid:border-destructive fui:aria-invalid:ring-[3px] fui:aria-invalid:ring-destructive/20 fui:aria-invalid:aria-checked:border-primary fui:dark:bg-input/30 fui:dark:aria-invalid:border-destructive/50 fui:dark:aria-invalid:ring-destructive/40 fui:data-checked:border-primary fui:data-checked:bg-primary fui:data-checked:text-primary-foreground fui:group-has-[:focus-visible]/field-label:data-checked:border-primary fui:dark:data-checked:bg-primary",
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="lib:grid lib:place-content-center lib:text-current lib:transition-none lib:[&>svg]:size-3.5"
className="fui:grid fui:place-content-center fui:text-current fui:transition-none fui:[&>svg]:size-3.5"
>
<CheckIcon />
</CheckboxPrimitive.Indicator>
@@ -24,4 +26,53 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
);
}
export { Checkbox };
type CheckboxWithLabelProps = CheckboxPrimitive.Root.Props & {
label: React.ReactNode;
labelClassName?: string;
wrapperClassName?: string;
};
/**
* A checkbox and its caption as one labelled control.
*
* Keeps the id wiring — a generated id tying the label to the input — in one
* place instead of at every call site.
*
* Deliberately not built on `Field`: a caller may already be inside one, and
* a `Field` nested in a `Field` picks up the bordered card treatment.
*/
function CheckboxWithLabel({
id,
label,
disabled,
className,
labelClassName,
wrapperClassName,
...props
}: CheckboxWithLabelProps) {
const reactId = React.useId();
const inputId = id ?? reactId;
return (
<div
data-slot="checkbox-with-label"
data-disabled={disabled ? "" : undefined}
className={cn(
"fui:group fui:inline-flex fui:items-center fui:gap-2",
wrapperClassName,
)}
>
<Checkbox
id={inputId}
disabled={disabled}
className={className}
{...props}
/>
<Label htmlFor={inputId} className={labelClassName}>
{label}
</Label>
</div>
);
}
export { Checkbox, CheckboxWithLabel, type CheckboxWithLabelProps };
+14 -14
View File
@@ -19,7 +19,7 @@ function Command({
<CommandPrimitive
data-slot="command"
className={cn(
"lib:flex lib:size-full lib:flex-col lib:overflow-hidden lib:rounded-4xl lib:bg-popover lib:p-1 lib:text-popover-foreground",
"fui:flex fui:size-full fui:flex-col fui:overflow-hidden fui:rounded-4xl fui:bg-popover fui:p-1 fui:text-popover-foreground",
className,
)}
{...props}
@@ -43,13 +43,13 @@ function CommandDialog({
}) {
return (
<Dialog {...props}>
<DialogHeader className="lib:sr-only">
<DialogHeader className="fui:sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent
className={cn(
"lib:top-1/3 lib:translate-y-0 lib:overflow-hidden lib:rounded-4xl! lib:p-0",
"fui:top-1/3 fui:translate-y-0 fui:overflow-hidden fui:rounded-4xl! fui:p-0",
className,
)}
showCloseButton={showCloseButton}
@@ -65,18 +65,18 @@ function CommandInput({
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div data-slot="command-input-wrapper" className="lib:p-1 lib:pb-0">
<InputGroup className="lib:h-9 lib:bg-input/30">
<div data-slot="command-input-wrapper" className="fui:p-1 fui:pb-0">
<InputGroup className="fui:h-9 fui:bg-input/30">
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"lib:w-full lib:text-sm lib:outline-hidden lib:disabled:cursor-not-allowed lib:disabled:opacity-50",
"fui:w-full fui:text-sm fui:outline-hidden fui:disabled:cursor-not-allowed fui:disabled:opacity-50",
className,
)}
{...props}
/>
<InputGroupAddon>
<SearchIcon className="lib:size-4 lib:shrink-0 lib:opacity-50" />
<SearchIcon className="fui:size-4 fui:shrink-0 fui:opacity-50" />
</InputGroupAddon>
</InputGroup>
</div>
@@ -91,7 +91,7 @@ function CommandList({
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"lib:no-scrollbar lib:max-h-72 lib:scroll-py-1 lib:overflow-x-hidden lib:overflow-y-auto lib:outline-none",
"fui:no-scrollbar fui:max-h-72 fui:scroll-py-1 fui:overflow-x-hidden fui:overflow-y-auto fui:outline-none",
className,
)}
{...props}
@@ -106,7 +106,7 @@ function CommandEmpty({
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className={cn("lib:py-6 lib:text-center lib:text-sm", className)}
className={cn("fui:py-6 fui:text-center fui:text-sm", className)}
{...props}
/>
);
@@ -120,7 +120,7 @@ function CommandGroup({
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"lib:overflow-hidden lib:p-1 lib:text-foreground lib:**:[[cmdk-group-heading]]:px-3 lib:**:[[cmdk-group-heading]]:py-2 lib:**:[[cmdk-group-heading]]:text-xs lib:**:[[cmdk-group-heading]]:font-medium lib:**:[[cmdk-group-heading]]:text-muted-foreground",
"fui:overflow-hidden fui:p-1 fui:text-foreground fui:**:[[cmdk-group-heading]]:px-3 fui:**:[[cmdk-group-heading]]:py-2 fui:**:[[cmdk-group-heading]]:text-xs fui:**:[[cmdk-group-heading]]:font-medium fui:**:[[cmdk-group-heading]]:text-muted-foreground",
className,
)}
{...props}
@@ -135,7 +135,7 @@ function CommandSeparator({
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("lib:my-1 lib:h-px lib:bg-border/50", className)}
className={cn("fui:my-1 fui:h-px fui:bg-border/50", className)}
{...props}
/>
);
@@ -150,13 +150,13 @@ function CommandItem({
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"lib:group/command-item lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2 lib:rounded-lg lib:px-3 lib:py-2 lib:text-sm lib:outline-hidden lib:select-none lib:in-data-[slot=dialog-content]:rounded-2xl lib:data-[disabled=true]:pointer-events-none lib:data-[disabled=true]:opacity-50 lib:data-selected:bg-muted lib:data-selected:text-foreground lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4 lib:data-selected:*:[svg]:text-foreground",
"fui:group/command-item fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2 fui:rounded-lg fui:px-3 fui:py-2 fui:text-sm fui:outline-hidden fui:select-none fui:in-data-[slot=dialog-content]:rounded-2xl fui:data-[disabled=true]:pointer-events-none fui:data-[disabled=true]:opacity-50 fui:data-selected:bg-muted fui:data-selected:text-foreground fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4 fui:data-selected:*:[svg]:text-foreground",
className,
)}
{...props}
>
{children}
<CheckIcon className="lib:ml-auto lib:opacity-0 lib:group-has-data-[slot=command-shortcut]/command-item:hidden lib:group-data-[checked=true]/command-item:opacity-100" />
<CheckIcon className="fui:ml-auto fui:opacity-0 fui:group-has-data-[slot=command-shortcut]/command-item:hidden fui:group-data-[checked=true]/command-item:opacity-100" />
</CommandPrimitive.Item>
);
}
@@ -169,7 +169,7 @@ function CommandShortcut({
<span
data-slot="command-shortcut"
className={cn(
"lib:ml-auto lib:text-xs lib:tracking-widest lib:text-muted-foreground lib:group-data-selected/command-item:text-foreground",
"fui:ml-auto fui:text-xs fui:tracking-widest fui:text-muted-foreground fui:group-data-selected/command-item:text-foreground",
className,
)}
{...props}
+14 -14
View File
@@ -20,7 +20,7 @@ function ContextMenuTrigger({
return (
<ContextMenuPrimitive.Trigger
data-slot="context-menu-trigger"
className={cn("lib:select-none", className)}
className={cn("fui:select-none", className)}
{...props}
/>
);
@@ -41,7 +41,7 @@ function ContextMenuContent({
return (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Positioner
className="lib:isolate lib:z-50 lib:outline-none"
className="fui:isolate fui:z-50 fui:outline-none"
align={align}
alignOffset={alignOffset}
side={side}
@@ -50,7 +50,7 @@ function ContextMenuContent({
<ContextMenuPrimitive.Popup
data-slot="context-menu-content"
className={cn(
"lib: lib: lib:z-50 lib:max-h-(--available-height) lib:min-w-48 lib:origin-(--transform-origin) lib:overflow-x-hidden lib:overflow-y-auto lib:rounded-2xl lib:bg-popover lib:p-1 lib:text-popover-foreground lib:shadow-2xl lib:ring-1 lib:ring-foreground/5 lib:duration-100 lib:outline-none lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=inline-end]:slide-in-from-left-2 lib:data-[side=inline-start]:slide-in-from-right-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
"fui: fui: fui:z-50 fui:max-h-(--available-height) fui:min-w-48 fui:origin-(--transform-origin) fui:overflow-x-hidden fui:overflow-y-auto fui:rounded-2xl fui:bg-popover fui:p-1 fui:text-popover-foreground fui:shadow-2xl fui:ring-1 fui:ring-foreground/5 fui:duration-100 fui:outline-none fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=inline-end]:slide-in-from-left-2 fui:data-[side=inline-start]:slide-in-from-right-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
className,
)}
{...props}
@@ -78,7 +78,7 @@ function ContextMenuLabel({
data-slot="context-menu-label"
data-inset={inset}
className={cn(
"lib:px-3 lib:py-2.5 lib:text-xs lib:text-muted-foreground lib:data-inset:pl-9.5",
"fui:px-3 fui:py-2.5 fui:text-xs fui:text-muted-foreground fui:data-inset:pl-9.5",
className,
)}
{...props}
@@ -101,7 +101,7 @@ function ContextMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"lib:group/context-menu-item lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2.5 lib:rounded-xl lib:px-3 lib:py-2 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:data-inset:pl-9.5 lib:data-[variant=destructive]:text-destructive lib:data-[variant=destructive]:focus:bg-destructive/10 lib:data-[variant=destructive]:focus:text-destructive lib:dark:data-[variant=destructive]:focus:bg-destructive/20 lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4 lib:focus:*:[svg]:text-accent-foreground lib:data-[variant=destructive]:*:[svg]:text-destructive",
"fui:group/context-menu-item fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2.5 fui:rounded-xl fui:px-3 fui:py-2 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:data-inset:pl-9.5 fui:data-[variant=destructive]:text-destructive fui:data-[variant=destructive]:focus:bg-destructive/10 fui:data-[variant=destructive]:focus:text-destructive fui:dark:data-[variant=destructive]:focus:bg-destructive/20 fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4 fui:focus:*:[svg]:text-accent-foreground fui:data-[variant=destructive]:*:[svg]:text-destructive",
className,
)}
{...props}
@@ -128,13 +128,13 @@ function ContextMenuSubTrigger({
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"lib:flex lib:cursor-default lib:items-center lib:rounded-xl lib:px-3 lib:py-2 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:data-inset:pl-9.5 lib:data-open:bg-accent lib:data-open:text-accent-foreground lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:flex fui:cursor-default fui:items-center fui:rounded-xl fui:px-3 fui:py-2 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:data-inset:pl-9.5 fui:data-open:bg-accent fui:data-open:text-accent-foreground fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="lib:ml-auto" />
<ChevronRightIcon className="fui:ml-auto" />
</ContextMenuPrimitive.SubmenuTrigger>
);
}
@@ -145,7 +145,7 @@ function ContextMenuSubContent({
return (
<ContextMenuContent
data-slot="context-menu-sub-content"
className="lib: lib: lib:shadow-lg animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!"
className="fui: fui: fui:shadow-lg animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!"
side="right"
{...props}
/>
@@ -166,13 +166,13 @@ function ContextMenuCheckboxItem({
data-slot="context-menu-checkbox-item"
data-inset={inset}
className={cn(
"lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2 lib:rounded-xl lib:py-2 lib:pr-8 lib:pl-3 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:data-inset:pl-9.5 lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2 fui:rounded-xl fui:py-2 fui:pr-8 fui:pl-3 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:data-inset:pl-9.5 fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="lib:pointer-events-none lib:absolute lib:right-2">
<span className="fui:pointer-events-none fui:absolute fui:right-2">
<ContextMenuPrimitive.CheckboxItemIndicator>
<CheckIcon />
</ContextMenuPrimitive.CheckboxItemIndicator>
@@ -206,12 +206,12 @@ function ContextMenuRadioItem({
data-slot="context-menu-radio-item"
data-inset={inset}
className={cn(
"lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2 lib:rounded-xl lib:py-2 lib:pr-8 lib:pl-3 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:data-inset:pl-9.5 lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2 fui:rounded-xl fui:py-2 fui:pr-8 fui:pl-3 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:data-inset:pl-9.5 fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
>
<span className="lib:pointer-events-none lib:absolute lib:right-2">
<span className="fui:pointer-events-none fui:absolute fui:right-2">
<ContextMenuPrimitive.RadioItemIndicator>
<CheckIcon />
</ContextMenuPrimitive.RadioItemIndicator>
@@ -228,7 +228,7 @@ function ContextMenuSeparator({
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("lib:-mx-1 lib:my-1 lib:h-px lib:bg-border/50", className)}
className={cn("fui:-mx-1 fui:my-1 fui:h-px fui:bg-border/50", className)}
{...props}
/>
);
@@ -242,7 +242,7 @@ function ContextMenuShortcut({
<span
data-slot="context-menu-shortcut"
className={cn(
"lib:ml-auto lib:text-xs lib:tracking-widest lib:text-muted-foreground lib:group-focus/context-menu-item:text-accent-foreground",
"fui:ml-auto fui:text-xs fui:tracking-widest fui:text-muted-foreground fui:group-focus/context-menu-item:text-accent-foreground",
className,
)}
{...props}
+171 -81
View File
@@ -74,12 +74,6 @@ import {
TableRow,
} from "@/components/table";
/**
* Feature set for `DataTable`. TanStack Table v9 is opt-in: features and their
* row models are registered here, and the resulting type parameterises every
* column definition. Row-model factories take no arguments, and the core row
* model is implicit — never register it.
*/
const dataTableFeatures = tableFeatures({
columnFilteringFeature,
columnVisibilityFeature,
@@ -120,39 +114,21 @@ type DataTableInstance<TData extends Record<string, any>> = ReactTable<
TData
>;
/**
* Typed column helper bound to the `DataTable` feature set.
*
* @example
* const column = createDataTableColumnHelper<Invoice>();
* const columns = column.columns([
* column.accessor("amount", { header: "Amount", sortFn: "alphanumeric" }),
* ]);
*/
// biome-ignore lint/suspicious/noExplicitAny: mirrors TanStack's RowData constraint.
function createDataTableColumnHelper<TData extends Record<string, any>>() {
return createColumnHelper<DataTableFeatures, TData>();
}
/* -------------------------------------------------------------------------- */
/* column header */
/* -------------------------------------------------------------------------- */
type DataTableColumnHeaderProps<
// biome-ignore lint/suspicious/noExplicitAny: mirrors TanStack's RowData constraint.
TData extends Record<string, any>,
TValue,
> = React.ComponentProps<"div"> & {
/** `Column` is invariant in `TData`/`TValue`, so both must be generic here. */
column: Column<DataTableFeatures, TData, TValue>;
title: string;
align?: "start" | "end";
};
/**
* Sortable header cell. Renders plain text when the column cannot be sorted so
* it stays out of the tab order.
*/
function DataTableColumnHeader<
// biome-ignore lint/suspicious/noExplicitAny: mirrors TanStack's RowData constraint.
TData extends Record<string, any>,
@@ -170,7 +146,7 @@ function DataTableColumnHeader<
return (
<div
data-slot="data-table-column-header"
className={cn(align === "end" && "lib:text-right", className)}
className={cn(align === "end" && "fui:text-right", className)}
{...props}
>
{title}
@@ -189,8 +165,8 @@ function DataTableColumnHeader<
<div
data-slot="data-table-column-header"
className={cn(
"lib:flex lib:items-center",
align === "end" && "lib:justify-end",
"fui:flex fui:items-center",
align === "end" && "fui:justify-end",
className,
)}
{...props}
@@ -206,26 +182,20 @@ function DataTableColumnHeader<
? `${title}, sorted descending`
: `${title}, not sorted`
}
className="lib:-mx-2 lib:h-7 lib:gap-1 lib:font-medium lib:data-[sorted=true]:text-foreground"
className="fui:-mx-2 fui:h-7 fui:gap-1 fui:font-medium fui:data-[sorted=true]:text-foreground"
data-sorted={sorted !== false}
>
{title}
<Icon
data-icon="inline-end"
className={cn(sorted === false && "lib:opacity-50")}
className={cn(sorted === false && "fui:opacity-50")}
/>
</Button>
</div>
);
}
/* -------------------------------------------------------------------------- */
/* selection column */
/* -------------------------------------------------------------------------- */
/**
* Ready-made checkbox column. Spread it as the first entry of `columns`.
*/
/** Checkbox column. Spread it as the first entry of `columns`. */
function dataTableSelectColumn<
// biome-ignore lint/suspicious/noExplicitAny: mirrors TanStack's RowData constraint.
TData extends Record<string, any>,
@@ -257,10 +227,6 @@ function dataTableSelectColumn<
};
}
/* -------------------------------------------------------------------------- */
/* data table */
/* -------------------------------------------------------------------------- */
// biome-ignore lint/suspicious/noExplicitAny: mirrors TanStack's RowData constraint.
type DataTableProps<TData extends Record<string, any>> = Omit<
React.ComponentProps<"div">,
@@ -283,6 +249,59 @@ type DataTableProps<TData extends Record<string, any>> = Omit<
empty?: React.ReactNode;
/** Extra controls rendered in the toolbar, after the search field. */
toolbar?: React.ReactNode;
/** Optional detail row rendered directly below each data row. */
renderSubComponent?: (row: DataTableRow<TData>) => React.ReactNode;
/* ------------------------------ server-driven ----------------------------- */
/*
* Each of the three concerns below is controlled when its state prop is
* passed, and the table then stops doing that work itself — TanStack's
* `manualSorting` / `manualPagination` / `manualFiltering`. Leave them out
* and everything happens client-side over `data`, as before.
*
* This is what a table whose rows come back a page at a time needs: sorting
* or filtering the rows already fetched would only reorder the current
* window while presenting it as the whole result.
*/
/**
* Sort state owned by the caller. Implies the sort is the server's, unless
* `manualSorting` says otherwise.
*/
sorting?: SortingState;
onSortingChange?: (sorting: SortingState) => void;
/**
* Page state owned by the caller. Implies paging is the server's, unless
* `manualPagination` says otherwise.
*/
pagination?: PaginationState;
onPaginationChange?: (pagination: PaginationState) => void;
/**
* Total unpaged rows, as reported by the server. Required alongside
* `pagination` — without it the table cannot know how many pages exist, and
* "Next" would stop working after the first one.
*/
rowCount?: number;
/**
* Search term owned by the caller. Implies filtering is the server's, unless
* `manualFiltering` says otherwise.
*/
globalFilter?: string;
onGlobalFilterChange?: (value: string) => void;
/*
* Who does the work, when that is not the same question as who holds the
* state. Each defaults to "the server" when the matching state prop is
* passed, which is the common case.
*
* Set one to `false` alongside its state prop for a table that holds the
* complete result but keeps the state elsewhere — a listing that persists
* its search in the URL, say. The table then still sorts, filters and pages
* its rows itself, and the caller only stores the state.
*/
manualSorting?: boolean;
manualPagination?: boolean;
manualFiltering?: boolean;
};
function DataTable<
@@ -300,7 +319,18 @@ function DataTable<
onRowSelectionChange,
empty = "No results.",
toolbar,
renderSubComponent,
className,
sorting: sortingProp,
onSortingChange: onSortingChangeProp,
pagination: paginationProp,
onPaginationChange: onPaginationChangeProp,
rowCount,
globalFilter: globalFilterProp,
onGlobalFilterChange: onGlobalFilterChangeProp,
manualSorting: manualSortingProp,
manualPagination: manualPaginationProp,
manualFiltering: manualFilteringProp,
...props
}: DataTableProps<TData>) {
const [sorting, setSorting] = React.useState<SortingState>([]);
@@ -316,26 +346,65 @@ function DataTable<
pageSize: pageSize === false ? Infinity : pageSize,
});
// Server-driven by default whenever the caller holds the state, but a
// caller with the complete result can opt back out.
const manualSorting = manualSortingProp ?? sortingProp !== undefined;
const manualPagination = manualPaginationProp ?? paginationProp !== undefined;
const manualFiltering = manualFilteringProp ?? globalFilterProp !== undefined;
/**
* TanStack hands updaters through as `T | ((old: T) => T)`, so resolve them
* against the current value before calling the caller's plain setter.
*/
const resolve = <T,>(updater: T | ((old: T) => T), current: T): T =>
typeof updater === "function"
? (updater as (old: T) => T)(current)
: updater;
const table = useTable({
features: dataTableFeatures,
columns,
data,
getRowId,
enableRowSelection,
manualSorting,
manualPagination,
manualFiltering,
// Only meaningful under `manualPagination`; TanStack derives it from the
// row count otherwise.
rowCount: manualPagination ? rowCount : undefined,
state: {
sorting,
sorting: sortingProp ?? sorting,
columnFilters,
globalFilter,
globalFilter: globalFilterProp ?? globalFilter,
columnVisibility,
rowSelection,
pagination,
pagination: paginationProp ?? pagination,
},
onSortingChange: (updater) => {
if (onSortingChangeProp) {
onSortingChangeProp(resolve(updater, sortingProp ?? sorting));
return;
}
setSorting(updater);
},
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
onGlobalFilterChange: (updater) => {
if (onGlobalFilterChangeProp) {
onGlobalFilterChangeProp(resolve(updater, globalFilterProp ?? ""));
return;
}
setGlobalFilter(updater);
},
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
onPaginationChange: (updater) => {
if (onPaginationChangeProp) {
onPaginationChangeProp(resolve(updater, paginationProp ?? pagination));
return;
}
setPagination(updater);
},
});
const selectedRows = table.getSelectedRowModel().rows;
@@ -349,17 +418,21 @@ function DataTable<
const rows = table.getRowModel().rows;
const showToolbar = searchable !== false || hideableColumns || !!toolbar;
const totalRows =
manualPagination && rowCount !== undefined
? rowCount
: table.getFilteredRowModel().rows.length;
return (
<div
data-slot="data-table"
className={cn("lib:flex lib:flex-col lib:gap-3", className)}
className={cn("fui:flex fui:flex-col fui:gap-3", className)}
{...props}
>
{showToolbar && (
<div className="lib:flex lib:flex-wrap lib:items-center lib:gap-2">
<div className="fui:flex fui:flex-wrap fui:items-end fui:gap-2">
{searchable !== false && (
<InputGroup className="lib:w-full lib:sm:w-64">
<InputGroup className="fui:w-full fui:sm:w-64">
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
@@ -378,13 +451,13 @@ function DataTable<
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="outline" size="sm" className="lib:ml-auto">
<Button variant="outline" size="sm" className="fui:ml-auto">
<Settings2Icon data-icon="inline-start" />
Columns
</Button>
}
/>
<DropdownMenuContent align="end" className="lib:w-44">
<DropdownMenuContent align="end" className="fui:w-44">
<DropdownMenuGroup>
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
</DropdownMenuGroup>
@@ -401,7 +474,7 @@ function DataTable<
column.toggleVisibility(!!value)
}
onSelect={(event) => event.preventDefault()}
className="lib:capitalize"
className="fui:capitalize"
>
{column.id}
</DropdownMenuCheckboxItem>
@@ -413,7 +486,7 @@ function DataTable<
</div>
)}
<div className="lib:overflow-hidden lib:rounded-lg lib:border">
<div className="fui:overflow-hidden fui:rounded-lg fui:border fui:bg-card">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
@@ -440,50 +513,67 @@ function DataTable<
</TableHeader>
<TableBody>
{rows.length === 0 ? (
<TableRow className="lib:hover:bg-transparent">
<TableRow className="fui:hover:bg-transparent">
<TableCell
colSpan={table.getAllLeafColumns().length}
className="lib:h-28 lib:text-center lib:text-muted-foreground"
className="fui:h-28 fui:text-center fui:text-muted-foreground"
>
{empty}
</TableCell>
</TableRow>
) : (
rows.map((row: DataTableRow<TData>) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() ? "selected" : undefined}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
<table.FlexRender cell={cell} />
</TableCell>
))}
</TableRow>
))
rows.map((row: DataTableRow<TData>) => {
const subComponent = renderSubComponent?.(row);
return (
<React.Fragment key={row.id}>
<TableRow
data-state={row.getIsSelected() ? "selected" : undefined}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
<table.FlexRender cell={cell} />
</TableCell>
))}
</TableRow>
{subComponent && (
<TableRow className="fui:hover:bg-transparent">
<TableCell
colSpan={row.getVisibleCells().length}
className="fui:bg-muted/50 fui:p-0"
>
{subComponent}
</TableCell>
</TableRow>
)}
</React.Fragment>
);
})
)}
</TableBody>
</Table>
</div>
{pageSize !== false && (
<div className="lib:flex lib:flex-wrap lib:items-center lib:justify-between lib:gap-3">
<p className="lib:text-sm lib:text-muted-foreground">
<div className="fui:flex fui:flex-wrap fui:items-center fui:justify-between fui:gap-3">
<p className="fui:text-sm fui:text-muted-foreground">
{/* Under `manualPagination` the row model holds only the page that
was fetched, so the server's total is the honest figure. */}
{enableRowSelection
? `${selectedRows.length} of ${table.getFilteredRowModel().rows.length} row(s) selected`
: `${table.getFilteredRowModel().rows.length} row(s)`}
? `${selectedRows.length} of ${totalRows} row(s) selected`
: `${totalRows} row(s)`}
</p>
<div className="lib:flex lib:items-center lib:gap-4">
<div className="lib:flex lib:items-center lib:gap-2">
<span className="lib:text-sm lib:text-muted-foreground">
<div className="fui:flex fui:items-center fui:gap-4">
<div className="fui:flex fui:items-center fui:gap-2">
<span className="fui:text-sm fui:text-muted-foreground">
Rows per page
</span>
<Select
value={String(table.state.pagination.pageSize)}
onValueChange={(value) => table.setPageSize(Number(value))}
>
<SelectTrigger size="sm" className="lib:w-18">
<SelectTrigger size="sm" className="fui:w-18">
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -498,12 +588,12 @@ function DataTable<
</Select>
</div>
<span className="lib:text-sm lib:tabular-nums lib:text-muted-foreground">
<span className="fui:text-sm fui:tabular-nums fui:text-muted-foreground">
Page {table.state.pagination.pageIndex + 1} of{" "}
{Math.max(table.getPageCount(), 1)}
</span>
<div className="lib:flex lib:items-center lib:gap-1">
<div className="fui:flex fui:items-center fui:gap-1">
<Button
variant="outline"
size="icon-sm"
@@ -511,7 +601,7 @@ function DataTable<
disabled={!table.getCanPreviousPage()}
>
<ChevronsLeftIcon />
<span className="lib:sr-only">First page</span>
<span className="fui:sr-only">First page</span>
</Button>
<Button
variant="outline"
@@ -520,7 +610,7 @@ function DataTable<
disabled={!table.getCanPreviousPage()}
>
<ChevronLeftIcon />
<span className="lib:sr-only">Previous page</span>
<span className="fui:sr-only">Previous page</span>
</Button>
<Button
variant="outline"
@@ -529,7 +619,7 @@ function DataTable<
disabled={!table.getCanNextPage()}
>
<ChevronRightIcon />
<span className="lib:sr-only">Next page</span>
<span className="fui:sr-only">Next page</span>
</Button>
<Button
variant="outline"
@@ -538,7 +628,7 @@ function DataTable<
disabled={!table.getCanNextPage()}
>
<ChevronsRightIcon />
<span className="lib:sr-only">Last page</span>
<span className="fui:sr-only">Last page</span>
</Button>
</div>
</div>
+8 -8
View File
@@ -30,7 +30,7 @@ function DialogOverlay({
<DialogPrimitive.Backdrop
data-slot="dialog-overlay"
className={cn(
"lib:fixed lib:inset-0 lib:isolate lib:z-50 lib:bg-black/80 lib:duration-100 lib:supports-backdrop-filter:backdrop-blur-xs lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-closed:animate-out lib:data-closed:fade-out-0",
"fui:fixed fui:inset-0 fui:isolate fui:z-50 fui:bg-black/80 fui:duration-100 fui:supports-backdrop-filter:backdrop-blur-xs fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-closed:animate-out fui:data-closed:fade-out-0",
className,
)}
{...props}
@@ -52,7 +52,7 @@ function DialogContent({
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
"lib:fixed lib:top-1/2 lib:left-1/2 lib:z-50 lib:grid lib:w-full lib:max-w-[calc(100%-2rem)] lib:-translate-x-1/2 lib:-translate-y-1/2 lib:gap-6 lib:rounded-4xl lib:bg-popover lib:p-6 lib:text-sm lib:text-popover-foreground lib:ring-1 lib:ring-foreground/5 lib:duration-100 lib:outline-none lib:sm:max-w-md lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95",
"fui:fixed fui:top-1/2 fui:left-1/2 fui:z-50 fui:grid fui:w-full fui:max-w-[calc(100%-2rem)] fui:-translate-x-1/2 fui:-translate-y-1/2 fui:gap-6 fui:rounded-4xl fui:bg-popover fui:p-6 fui:text-sm fui:text-popover-foreground fui:ring-1 fui:ring-foreground/5 fui:duration-100 fui:outline-none fui:sm:max-w-md fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95",
className,
)}
{...props}
@@ -64,13 +64,13 @@ function DialogContent({
render={
<Button
variant="ghost"
className="lib:absolute lib:top-4 lib:right-4"
className="fui:absolute fui:top-4 fui:right-4"
size="icon-sm"
/>
}
>
<XIcon />
<span className="lib:sr-only">Close</span>
<span className="fui:sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Popup>
@@ -82,7 +82,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("lib:flex lib:flex-col lib:gap-2", className)}
className={cn("fui:flex fui:flex-col fui:gap-2", className)}
{...props}
/>
);
@@ -100,7 +100,7 @@ function DialogFooter({
<div
data-slot="dialog-footer"
className={cn(
"lib:flex lib:flex-col-reverse lib:gap-2 lib:sm:flex-row lib:sm:justify-end",
"fui:flex fui:flex-col-reverse fui:gap-2 fui:sm:flex-row fui:sm:justify-end",
className,
)}
{...props}
@@ -120,7 +120,7 @@ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn(
"lib:font-heading lib:text-base lib:leading-none lib:font-medium",
"fui:font-heading fui:text-base fui:leading-none fui:font-medium",
className,
)}
{...props}
@@ -136,7 +136,7 @@ function DialogDescription({
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn(
"lib:text-sm lib:text-muted-foreground lib:*:[a]:underline lib:*:[a]:underline-offset-3 lib:*:[a]:hover:text-foreground",
"fui:text-sm fui:text-muted-foreground fui:*:[a]:underline fui:*:[a]:underline-offset-3 fui:*:[a]:hover:text-foreground",
className,
)}
{...props}
+7 -7
View File
@@ -36,7 +36,7 @@ function DrawerOverlay({
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
"lib:fixed lib:inset-0 lib:z-50 lib:bg-black/80 lib:supports-backdrop-filter:backdrop-blur-xs lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-closed:animate-out lib:data-closed:fade-out-0",
"fui:fixed fui:inset-0 fui:z-50 fui:bg-black/80 fui:supports-backdrop-filter:backdrop-blur-xs fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-closed:animate-out fui:data-closed:fade-out-0",
className,
)}
{...props}
@@ -55,12 +55,12 @@ function DrawerContent({
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
"lib:group/drawer-content lib:fixed lib:z-50 lib:flex lib:h-auto lib:flex-col lib:bg-transparent lib:p-4 lib:text-sm lib:text-popover-foreground lib:before:absolute lib:before:inset-2 lib:before:-z-10 lib:before:rounded-4xl lib:before:border lib:before:border-border lib:before:bg-popover lib:data-[vaul-drawer-direction=bottom]:inset-x-0 lib:data-[vaul-drawer-direction=bottom]:bottom-0 lib:data-[vaul-drawer-direction=bottom]:mt-24 lib:data-[vaul-drawer-direction=bottom]:max-h-[80vh] lib:data-[vaul-drawer-direction=left]:inset-y-0 lib:data-[vaul-drawer-direction=left]:left-0 lib:data-[vaul-drawer-direction=left]:w-3/4 lib:data-[vaul-drawer-direction=right]:inset-y-0 lib:data-[vaul-drawer-direction=right]:right-0 lib:data-[vaul-drawer-direction=right]:w-3/4 lib:data-[vaul-drawer-direction=top]:inset-x-0 lib:data-[vaul-drawer-direction=top]:top-0 lib:data-[vaul-drawer-direction=top]:mb-24 lib:data-[vaul-drawer-direction=top]:max-h-[80vh] lib:data-[vaul-drawer-direction=left]:sm:max-w-sm lib:data-[vaul-drawer-direction=right]:sm:max-w-sm",
"fui:group/drawer-content fui:fixed fui:z-50 fui:flex fui:h-auto fui:flex-col fui:bg-transparent fui:p-4 fui:text-sm fui:text-popover-foreground fui:before:absolute fui:before:inset-2 fui:before:-z-10 fui:before:rounded-4xl fui:before:border fui:before:border-border fui:before:bg-popover fui:data-[vaul-drawer-direction=bottom]:inset-x-0 fui:data-[vaul-drawer-direction=bottom]:bottom-0 fui:data-[vaul-drawer-direction=bottom]:mt-24 fui:data-[vaul-drawer-direction=bottom]:max-h-[80vh] fui:data-[vaul-drawer-direction=left]:inset-y-0 fui:data-[vaul-drawer-direction=left]:left-0 fui:data-[vaul-drawer-direction=left]:w-3/4 fui:data-[vaul-drawer-direction=right]:inset-y-0 fui:data-[vaul-drawer-direction=right]:right-0 fui:data-[vaul-drawer-direction=right]:w-3/4 fui:data-[vaul-drawer-direction=top]:inset-x-0 fui:data-[vaul-drawer-direction=top]:top-0 fui:data-[vaul-drawer-direction=top]:mb-24 fui:data-[vaul-drawer-direction=top]:max-h-[80vh] fui:data-[vaul-drawer-direction=left]:sm:max-w-sm fui:data-[vaul-drawer-direction=right]:sm:max-w-sm",
className,
)}
{...props}
>
<div className="lib:mx-auto lib:mt-4 lib:hidden lib:h-1.5 lib:w-[100px] lib:shrink-0 lib:rounded-full lib:bg-muted lib:group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
<div className="fui:mx-auto fui:mt-4 fui:hidden fui:h-1.5 fui:w-[100px] fui:shrink-0 fui:rounded-full fui:bg-muted fui:group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
@@ -72,7 +72,7 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="drawer-header"
className={cn(
"lib:flex lib:flex-col lib:gap-0.5 lib:p-4 lib:group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center lib:group-data-[vaul-drawer-direction=top]/drawer-content:text-center lib:md:gap-1.5 lib:md:text-left",
"fui:flex fui:flex-col fui:gap-0.5 fui:p-4 fui:group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center fui:group-data-[vaul-drawer-direction=top]/drawer-content:text-center fui:md:gap-1.5 fui:md:text-left",
className,
)}
{...props}
@@ -85,7 +85,7 @@ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="drawer-footer"
className={cn(
"lib:mt-auto lib:flex lib:flex-col lib:gap-2 lib:p-4",
"fui:mt-auto fui:flex fui:flex-col fui:gap-2 fui:p-4",
className,
)}
{...props}
@@ -101,7 +101,7 @@ function DrawerTitle({
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn(
"lib:font-heading lib:text-base lib:font-medium lib:text-foreground",
"fui:font-heading fui:text-base fui:font-medium fui:text-foreground",
className,
)}
{...props}
@@ -116,7 +116,7 @@ function DrawerDescription({
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn("lib:text-sm lib:text-muted-foreground", className)}
className={cn("fui:text-sm fui:text-muted-foreground", className)}
{...props}
/>
);
+13 -13
View File
@@ -30,7 +30,7 @@ function DropdownMenuContent({
return (
<MenuPrimitive.Portal>
<MenuPrimitive.Positioner
className="lib:isolate lib:z-50 lib:outline-none"
className="fui:isolate fui:z-50 fui:outline-none"
align={align}
alignOffset={alignOffset}
side={side}
@@ -39,7 +39,7 @@ function DropdownMenuContent({
<MenuPrimitive.Popup
data-slot="dropdown-menu-content"
className={cn(
"lib: lib: lib:z-50 lib:max-h-(--available-height) lib:w-(--anchor-width) lib:min-w-48 lib:origin-(--transform-origin) lib:overflow-x-hidden lib:overflow-y-auto lib:rounded-2xl lib:bg-popover lib:p-1 lib:text-popover-foreground lib:shadow-2xl lib:ring-1 lib:ring-foreground/5 lib:duration-100 lib:outline-none lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=inline-end]:slide-in-from-left-2 lib:data-[side=inline-start]:slide-in-from-right-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:dark:ring-foreground/10 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:overflow-hidden lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
"fui: fui: fui:z-50 fui:max-h-(--available-height) fui:w-(--anchor-width) fui:min-w-48 fui:origin-(--transform-origin) fui:overflow-x-hidden fui:overflow-y-auto fui:rounded-2xl fui:bg-popover fui:p-1 fui:text-popover-foreground fui:shadow-2xl fui:ring-1 fui:ring-foreground/5 fui:duration-100 fui:outline-none fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=inline-end]:slide-in-from-left-2 fui:data-[side=inline-start]:slide-in-from-right-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:dark:ring-foreground/10 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:overflow-hidden fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
className,
)}
{...props}
@@ -65,7 +65,7 @@ function DropdownMenuLabel({
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"lib:px-3 lib:py-2.5 lib:text-xs lib:text-muted-foreground lib:data-inset:pl-9.5",
"fui:px-3 fui:py-2.5 fui:text-xs fui:text-muted-foreground fui:data-inset:pl-9.5",
className,
)}
{...props}
@@ -88,7 +88,7 @@ function DropdownMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"lib:group/dropdown-menu-item lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2.5 lib:rounded-xl lib:px-3 lib:py-2 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:not-data-[variant=destructive]:focus:**:text-accent-foreground lib:data-inset:pl-9.5 lib:data-[variant=destructive]:text-destructive lib:data-[variant=destructive]:focus:bg-destructive/10 lib:data-[variant=destructive]:focus:text-destructive lib:dark:data-[variant=destructive]:focus:bg-destructive/20 lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4 lib:data-[variant=destructive]:*:[svg]:text-destructive",
"fui:group/dropdown-menu-item fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2.5 fui:rounded-xl fui:px-3 fui:py-2 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:not-data-[variant=destructive]:focus:**:text-accent-foreground fui:data-inset:pl-9.5 fui:data-[variant=destructive]:text-destructive fui:data-[variant=destructive]:focus:bg-destructive/10 fui:data-[variant=destructive]:focus:text-destructive fui:dark:data-[variant=destructive]:focus:bg-destructive/20 fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4 fui:data-[variant=destructive]:*:[svg]:text-destructive",
className,
)}
{...props}
@@ -113,13 +113,13 @@ function DropdownMenuSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"lib:flex lib:cursor-default lib:items-center lib:gap-2 lib:rounded-xl lib:px-3 lib:py-2 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:not-data-[variant=destructive]:focus:**:text-accent-foreground lib:data-inset:pl-9.5 lib:data-popup-open:bg-accent lib:data-popup-open:text-accent-foreground lib:data-open:bg-accent lib:data-open:text-accent-foreground lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:flex fui:cursor-default fui:items-center fui:gap-2 fui:rounded-xl fui:px-3 fui:py-2 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:not-data-[variant=destructive]:focus:**:text-accent-foreground fui:data-inset:pl-9.5 fui:data-popup-open:bg-accent fui:data-popup-open:text-accent-foreground fui:data-open:bg-accent fui:data-open:text-accent-foreground fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="lib:ml-auto" />
<ChevronRightIcon className="fui:ml-auto" />
</MenuPrimitive.SubmenuTrigger>
);
}
@@ -136,7 +136,7 @@ function DropdownMenuSubContent({
<DropdownMenuContent
data-slot="dropdown-menu-sub-content"
className={cn(
"lib: lib: lib:w-auto lib:min-w-36 lib:rounded-2xl lib:bg-popover lib:p-1 lib:text-popover-foreground lib:shadow-2xl lib:ring-1 lib:ring-foreground/5 lib:duration-100 lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
"fui: fui: fui:w-auto fui:min-w-36 fui:rounded-2xl fui:bg-popover fui:p-1 fui:text-popover-foreground fui:shadow-2xl fui:ring-1 fui:ring-foreground/5 fui:duration-100 fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
className,
)}
align={align}
@@ -162,14 +162,14 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item"
data-inset={inset}
className={cn(
"lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2.5 lib:rounded-xl lib:py-2 lib:pr-8 lib:pl-3 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:focus:**:text-accent-foreground lib:data-inset:pl-9.5 lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2.5 fui:rounded-xl fui:py-2 fui:pr-8 fui:pl-3 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:focus:**:text-accent-foreground fui:data-inset:pl-9.5 fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span
className="lib:pointer-events-none lib:absolute lib:right-2 lib:flex lib:items-center lib:justify-center"
className="fui:pointer-events-none fui:absolute fui:right-2 fui:flex fui:items-center fui:justify-center"
data-slot="dropdown-menu-checkbox-item-indicator"
>
<MenuPrimitive.CheckboxItemIndicator>
@@ -203,13 +203,13 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item"
data-inset={inset}
className={cn(
"lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2.5 lib:rounded-xl lib:py-2 lib:pr-8 lib:pl-3 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:focus:**:text-accent-foreground lib:data-inset:pl-9.5 lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2.5 fui:rounded-xl fui:py-2 fui:pr-8 fui:pl-3 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:focus:**:text-accent-foreground fui:data-inset:pl-9.5 fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
>
<span
className="lib:pointer-events-none lib:absolute lib:right-2 lib:flex lib:items-center lib:justify-center"
className="fui:pointer-events-none fui:absolute fui:right-2 fui:flex fui:items-center fui:justify-center"
data-slot="dropdown-menu-radio-item-indicator"
>
<MenuPrimitive.RadioItemIndicator>
@@ -228,7 +228,7 @@ function DropdownMenuSeparator({
return (
<MenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("lib:-mx-1 lib:my-1 lib:h-px lib:bg-border/50", className)}
className={cn("fui:-mx-1 fui:my-1 fui:h-px fui:bg-border/50", className)}
{...props}
/>
);
@@ -242,7 +242,7 @@ function DropdownMenuShortcut({
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"lib:ml-auto lib:text-xs lib:tracking-widest lib:text-muted-foreground lib:group-focus/dropdown-menu-item:text-accent-foreground",
"fui:ml-auto fui:text-xs fui:tracking-widest fui:text-muted-foreground fui:group-focus/dropdown-menu-item:text-accent-foreground",
className,
)}
{...props}
+8 -8
View File
@@ -6,7 +6,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="empty"
className={cn(
"lib:flex lib:w-full lib:min-w-0 lib:flex-1 lib:flex-col lib:items-center lib:justify-center lib:gap-4 lib:rounded-lg lib:border-dashed lib:p-12 lib:text-center lib:text-balance",
"fui:flex fui:w-full fui:min-w-0 fui:flex-1 fui:flex-col fui:items-center fui:justify-center fui:gap-4 fui:rounded-lg fui:border-dashed fui:p-12 fui:text-center fui:text-balance",
className,
)}
{...props}
@@ -19,7 +19,7 @@ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="empty-header"
className={cn(
"lib:flex lib:max-w-sm lib:flex-col lib:items-center lib:gap-2",
"fui:flex fui:max-w-sm fui:flex-col fui:items-center fui:gap-2",
className,
)}
{...props}
@@ -28,12 +28,12 @@ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
}
const emptyMediaVariants = cva(
"lib:mb-2 lib:flex lib:shrink-0 lib:items-center lib:justify-center lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0",
"fui:mb-2 fui:flex fui:shrink-0 fui:items-center fui:justify-center fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0",
{
variants: {
variant: {
default: "lib:bg-transparent",
icon: "lib:flex lib:size-10 lib:shrink-0 lib:items-center lib:justify-center lib:rounded-lg lib:bg-muted lib:text-foreground lib:[&_svg:not([class*=size-])]:size-6",
default: "fui:bg-transparent",
icon: "fui:flex fui:size-10 fui:shrink-0 fui:items-center fui:justify-center fui:rounded-lg fui:bg-muted fui:text-foreground fui:[&_svg:not([class*=size-])]:size-6",
},
},
defaultVariants: {
@@ -62,7 +62,7 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="empty-title"
className={cn(
"lib:font-heading lib:text-lg lib:font-medium lib:tracking-tight",
"fui:font-heading fui:text-lg fui:font-medium fui:tracking-tight",
className,
)}
{...props}
@@ -75,7 +75,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
<div
data-slot="empty-description"
className={cn(
"lib:text-sm/relaxed lib:text-muted-foreground lib:[&>a]:underline lib:[&>a]:underline-offset-4 lib:[&>a:hover]:text-primary",
"fui:text-sm/relaxed fui:text-muted-foreground fui:[&>a]:underline fui:[&>a]:underline-offset-4 fui:[&>a:hover]:text-primary",
className,
)}
{...props}
@@ -88,7 +88,7 @@ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="empty-content"
className={cn(
"lib:flex lib:w-full lib:max-w-sm lib:min-w-0 lib:flex-col lib:items-center lib:gap-4 lib:text-sm lib:text-balance",
"fui:flex fui:w-full fui:max-w-sm fui:min-w-0 fui:flex-col fui:items-center fui:gap-4 fui:text-sm fui:text-balance",
className,
)}
{...props}
+19 -19
View File
@@ -12,7 +12,7 @@ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
<fieldset
data-slot="field-set"
className={cn(
"lib:flex lib:flex-col lib:gap-6 lib:has-[>[data-slot=checkbox-group]]:gap-3 lib:has-[>[data-slot=radio-group]]:gap-3",
"fui:flex fui:flex-col fui:gap-6 fui:has-[>[data-slot=checkbox-group]]:gap-3 fui:has-[>[data-slot=radio-group]]:gap-3",
className,
)}
{...props}
@@ -30,7 +30,7 @@ function FieldLegend({
data-slot="field-legend"
data-variant={variant}
className={cn(
"lib:mb-3 lib:font-medium lib:data-[variant=label]:text-sm lib:data-[variant=legend]:text-base",
"fui:mb-3 fui:font-medium fui:data-[variant=label]:text-sm fui:data-[variant=legend]:text-base",
className,
)}
{...props}
@@ -43,7 +43,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="field-group"
className={cn(
"lib:group/field-group lib:@container/field-group lib:flex lib:w-full lib:flex-col lib:gap-7 lib:data-[slot=checkbox-group]:gap-3 lib:*:data-[slot=field-group]:gap-4",
"fui:group/field-group fui:@container/field-group fui:flex fui:w-full fui:flex-col fui:gap-7 fui:data-[slot=checkbox-group]:gap-3 fui:*:data-[slot=field-group]:gap-4",
className,
)}
{...props}
@@ -52,15 +52,15 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
}
const fieldVariants = cva(
"lib:group/field lib:flex lib:w-full lib:gap-3 lib:data-[invalid=true]:text-destructive",
"fui:group/field fui:flex fui:w-full fui:gap-3 fui:data-[invalid=true]:text-destructive",
{
variants: {
orientation: {
vertical: "lib:flex-col lib:*:w-full lib:[&>.sr-only]:w-auto",
vertical: "fui:flex-col fui:*:w-full fui:[&>.sr-only]:w-auto",
horizontal:
"lib:flex-row lib:items-center lib:has-[>[data-slot=field-content]]:items-start lib:*:data-[slot=field-label]:flex-auto lib:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
"fui:flex-row fui:items-center fui:has-[>[data-slot=field-content]]:items-start fui:*:data-[slot=field-label]:flex-auto fui:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
responsive:
"lib:flex-col lib:*:w-full lib:@md/field-group:flex-row lib:@md/field-group:items-center lib:@md/field-group:*:w-auto lib:@md/field-group:has-[>[data-slot=field-content]]:items-start lib:@md/field-group:*:data-[slot=field-label]:flex-auto lib:[&>.sr-only]:w-auto lib:@md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
"fui:flex-col fui:*:w-full fui:@md/field-group:flex-row fui:@md/field-group:items-center fui:@md/field-group:*:w-auto fui:@md/field-group:has-[>[data-slot=field-content]]:items-start fui:@md/field-group:*:data-[slot=field-label]:flex-auto fui:[&>.sr-only]:w-auto fui:@md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
},
},
defaultVariants: {
@@ -90,7 +90,7 @@ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="field-content"
className={cn(
"lib:group/field-content lib:flex lib:flex-1 lib:flex-col lib:gap-1 lib:leading-snug",
"fui:group/field-content fui:flex fui:flex-1 fui:flex-col fui:gap-1 fui:leading-snug",
className,
)}
{...props}
@@ -106,8 +106,8 @@ function FieldLabel({
<Label
data-slot="field-label"
className={cn(
"lib:group/field-label lib:peer/field-label lib:flex lib:w-fit lib:gap-2 lib:leading-snug lib:group-data-[disabled=true]/field:opacity-50 lib:has-data-checked:border-primary/30 lib:has-data-checked:bg-primary/5 lib:has-[>[data-slot=field]]:rounded-xl lib:has-[>[data-slot=field]]:border lib:has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-input/40 lib:has-[>[data-slot=field]]:has-[:focus-visible]:border-ring lib:has-[>[data-slot=field]]:has-[:focus-visible]:ring-[3px] lib:has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 lib:*:data-[slot=field]:p-4 lib:dark:has-data-checked:border-primary/20 lib:dark:has-data-checked:bg-primary/10",
"lib:has-[>[data-slot=field]]:w-full lib:has-[>[data-slot=field]]:flex-col",
"fui:group/field-label fui:peer/field-label fui:flex fui:w-fit fui:gap-2 fui:leading-snug fui:group-data-[disabled=true]/field:opacity-50 fui:has-data-checked:border-primary/30 fui:has-data-checked:bg-primary/5 fui:has-[>[data-slot=field]]:rounded-xl fui:has-[>[data-slot=field]]:border fui:has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-input/40 fui:has-[>[data-slot=field]]:has-[:focus-visible]:border-ring fui:has-[>[data-slot=field]]:has-[:focus-visible]:ring-[3px] fui:has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 fui:*:data-[slot=field]:p-4 fui:dark:has-data-checked:border-primary/20 fui:dark:has-data-checked:bg-primary/10",
"fui:has-[>[data-slot=field]]:w-full fui:has-[>[data-slot=field]]:flex-col",
className,
)}
{...props}
@@ -120,7 +120,7 @@ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="field-label"
className={cn(
"lib:flex lib:w-fit lib:items-center lib:gap-2 lib:text-sm lib:font-medium lib:group-data-[disabled=true]/field:opacity-50",
"fui:flex fui:w-fit fui:items-center fui:gap-2 fui:text-sm fui:font-medium fui:group-data-[disabled=true]/field:opacity-50",
className,
)}
{...props}
@@ -133,9 +133,9 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
<p
data-slot="field-description"
className={cn(
"lib:text-left lib:text-sm lib:leading-normal lib:font-normal lib:text-muted-foreground lib:group-has-data-horizontal/field:text-balance lib:[[data-variant=legend]+&]:-mt-1.5",
"lib:last:mt-0 lib:nth-last-2:-mt-1",
"lib:[&>a]:underline lib:[&>a]:underline-offset-4 lib:[&>a:hover]:text-primary",
"fui:text-left fui:text-sm fui:leading-normal fui:font-normal fui:text-muted-foreground fui:group-has-data-horizontal/field:text-balance fui:[[data-variant=legend]+&]:-mt-1.5",
"fui:last:mt-0 fui:nth-last-2:-mt-1",
"fui:[&>a]:underline fui:[&>a]:underline-offset-4 fui:[&>a:hover]:text-primary",
className,
)}
{...props}
@@ -155,15 +155,15 @@ function FieldSeparator({
data-slot="field-separator"
data-content={!!children}
className={cn(
"lib:relative lib:-my-2 lib:h-5 lib:text-sm lib:group-data-[variant=outline]/field-group:-mb-2",
"fui:relative fui:-my-2 fui:h-5 fui:text-sm fui:group-data-[variant=outline]/field-group:-mb-2",
className,
)}
{...props}
>
<Separator className="lib:absolute lib:inset-0 lib:top-1/2" />
<Separator className="fui:absolute fui:inset-0 fui:top-1/2" />
{children && (
<span
className="lib:relative lib:mx-auto lib:block lib:w-fit lib:bg-background lib:px-2 lib:text-muted-foreground"
className="fui:relative fui:mx-auto fui:block fui:w-fit fui:bg-background fui:px-2 fui:text-muted-foreground"
data-slot="field-separator-content"
>
{children}
@@ -199,7 +199,7 @@ function FieldError({
}
return (
<ul className="lib:ml-4 lib:flex lib:list-disc lib:flex-col lib:gap-1">
<ul className="fui:ml-4 fui:flex fui:list-disc fui:flex-col fui:gap-1">
{uniqueErrors.map(
(error, index) =>
error?.message && <li key={index}>{error.message}</li>,
@@ -217,7 +217,7 @@ function FieldError({
role="alert"
data-slot="field-error"
className={cn(
"lib:text-sm lib:font-normal lib:text-destructive",
"fui:text-sm fui:font-normal fui:text-destructive",
className,
)}
{...props}
+427
View File
@@ -0,0 +1,427 @@
import { cn } from "cn";
import { format } from "date-fns";
import { CalendarIcon, SearchIcon, XIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/button";
import { Calendar } from "@/components/calendar";
import { Checkbox } from "@/components/checkbox";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/input-group";
import { Label } from "@/components/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/popover";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/select";
/**
* Filter controls for a listing, meant for `DataTable`'s `toolbar` slot.
*
* Every control is named by a label above it. A filter's *value* is a poor
* substitute for its name — a row of dropdowns all reading "Any" says nothing
* about what any of them narrow — so the label carries the name and the
* control carries only the value.
*
* The controls are uncontrolled as to *when* they apply: each calls back on
* change, and the caller decides whether that hits a server immediately or is
* debounced. Text input almost always wants debouncing.
*/
type FilterOption<T extends string = string> = {
value: T;
label: string;
};
/**
* The row of filters.
*
* Belongs in `DataTable`'s `toolbar` so the filters share a line with the
* table's column menu: given a `toolbar`, `DataTable` renders that row whether
* or not anything else is in it, and a row holding only the column button
* reads as a stray gap.
*
* `flex-1` claims the space beside it, pushing the column menu to the far end.
* `items-end` puts every control on a shared bottom edge, so a checkbox with
* no label above it still lines up with the labelled selects.
*/
function FilterBar({
className,
children,
actions,
}: {
className?: string;
children: React.ReactNode;
actions?: React.ReactNode;
}) {
return (
<div
data-slot="filter-bar"
className={cn(
"fui:flex fui:flex-1 fui:flex-wrap fui:items-end fui:gap-3",
className,
)}
>
{children}
{actions ? (
<div className="fui:ml-auto fui:flex fui:items-end fui:gap-2">
{actions}
</div>
) : null}
</div>
);
}
/** A labelled filter control. The label sits above and is bound to the input. */
function FilterField({
label,
htmlFor,
className,
children,
}: {
label: string;
htmlFor?: string;
className?: string;
children: React.ReactNode;
}) {
return (
<div
data-slot="filter-field"
className={cn("fui:flex fui:flex-col fui:gap-1", className)}
>
<Label
htmlFor={htmlFor}
className="fui:text-xs fui:font-medium fui:whitespace-nowrap fui:text-muted-foreground"
>
{label}
</Label>
{children}
</div>
);
}
/** Free-text filter, with the same search affordance `DataTable` uses. */
function FilterSearch({
label = "Search",
value,
onValueChange,
placeholder,
className,
}: {
label?: string;
value: string;
onValueChange: (value: string) => void;
placeholder?: string;
className?: string;
}) {
const id = React.useId();
return (
<FilterField
label={label}
htmlFor={id}
className={cn("fui:w-64", className)}
>
<InputGroup>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupInput
id={id}
value={value}
placeholder={placeholder}
onChange={(event) => onValueChange(event.target.value)}
/>
</InputGroup>
</FilterField>
);
}
/**
* Single-choice filter.
*
* Derives base-ui's `items` map from `options`, which is what makes the
* trigger show the selected option's *label*. Without it `Select.Value`
* renders the raw value, so an enum filter reads "multibuy" rather than
* "Multibuy".
*/
function FilterSelect<T extends string>({
label,
value,
onValueChange,
options,
placeholder = "Any",
className,
}: {
label: string;
value: T;
onValueChange: (value: T) => void;
options: ReadonlyArray<FilterOption<T>>;
placeholder?: string;
className?: string;
}) {
const id = React.useId();
const items = React.useMemo(
() =>
Object.fromEntries(options.map((option) => [option.value, option.label])),
[options],
);
return (
<FilterField label={label} htmlFor={id}>
<Select
items={items}
value={value}
onValueChange={(next) => onValueChange(next as T)}
>
<SelectTrigger id={id} size="sm" className={cn("fui:w-44", className)}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FilterField>
);
}
/**
* A period, with either bound optional.
*
* Both ends are independent: "since 1 March", "up to 31 March" and "1–31
* March" are all expressible, and so is neither.
*/
type FilterPeriod = {
from?: Date;
to?: Date;
};
function formatBound(date: Date) {
return format(date, "d MMM yyyy");
}
function describePeriod(period: FilterPeriod | undefined, placeholder: string) {
if (period?.from && period.to) {
return `${formatBound(period.from)} – ${formatBound(period.to)}`;
}
if (period?.from) return `From ${formatBound(period.from)}`;
if (period?.to) return `Until ${formatBound(period.to)}`;
return placeholder;
}
/**
* Period filter: one control for a span, in place of a "from" box and a "to"
* box.
*
* Two calendars in single-date mode rather than one in range mode, because a
* range calendar can only be drawn start-then-end: it cannot express "up to
* this date" with no start, and it forces a start-only pick to collapse into a
* single day. Here each bound is set and cleared on its own.
*
* Each calendar disables the days that would invert the period, so an
* unusable span cannot be selected in the first place.
*
* Values are exchanged as `Date`s. A caller storing the period in a URL or a
* query converts at its own boundary, since the format that belongs there is
* the caller's concern.
*/
function FilterDateRange({
label,
value,
onValueChange,
placeholder = "Any time",
className,
}: {
label: string;
value: FilterPeriod | undefined;
onValueChange: (period: FilterPeriod | undefined) => void;
placeholder?: string;
className?: string;
}) {
const id = React.useId();
const update = (next: FilterPeriod) =>
onValueChange(next.from || next.to ? next : undefined);
return (
<FilterField label={label} htmlFor={id}>
<Popover>
<PopoverTrigger
render={
<Button
id={id}
type="button"
variant="outline"
size="sm"
// `font-normal` and the muted empty state make it read as a
// field showing a value, not as an action.
className={cn(
"fui:w-64 fui:justify-start fui:font-normal",
!value?.from && !value?.to && "fui:text-muted-foreground",
className,
)}
>
<CalendarIcon />
{describePeriod(value, placeholder)}
</Button>
}
/>
<PopoverContent align="start" className="fui:w-auto fui:p-0">
<div className="fui:flex fui:flex-col fui:divide-y fui:sm:flex-row fui:sm:divide-x fui:sm:divide-y-0">
<PeriodBound
title="From"
selected={value?.from}
defaultMonth={value?.from ?? value?.to}
disabled={value?.to ? { after: value.to } : undefined}
onSelect={(from) => update({ from, to: value?.to })}
/>
<PeriodBound
title="To"
selected={value?.to}
defaultMonth={value?.to ?? value?.from}
disabled={value?.from ? { before: value.from } : undefined}
onSelect={(to) => update({ from: value?.from, to })}
/>
</div>
</PopoverContent>
</Popover>
</FilterField>
);
}
function PeriodBound({
title,
selected,
defaultMonth,
disabled,
onSelect,
}: {
title: string;
selected: Date | undefined;
defaultMonth: Date | undefined;
disabled: React.ComponentProps<typeof Calendar>["disabled"];
onSelect: (date: Date | undefined) => void;
}) {
return (
<div className="fui:flex fui:flex-col">
<div className="fui:flex fui:items-center fui:justify-between fui:gap-2 fui:px-3 fui:pt-3">
<span className="fui:text-xs fui:font-medium fui:text-muted-foreground">
{title}
</span>
{selected ? (
<Button
type="button"
variant="ghost"
size="xs"
onClick={() => onSelect(undefined)}
>
<XIcon />
Clear
</Button>
) : null}
</div>
<Calendar
mode="single"
selected={selected}
defaultMonth={defaultMonth}
disabled={disabled}
onSelect={onSelect}
/>
</div>
);
}
/**
* Boolean filter.
*
* Its caption follows the box rather than sitting above it: a checkbox reads
* as "[x] Active only", and a label overhead would detach the words from the
* control they toggle. The height matches the controls beside it so the shared
* bottom edge still holds.
*/
function FilterToggle({
label,
checked,
onCheckedChange,
}: {
label: string;
checked: boolean;
onCheckedChange: (checked: boolean) => void;
}) {
const id = React.useId();
return (
<div
data-slot="filter-toggle"
className="fui:flex fui:h-8 fui:items-center fui:gap-2"
>
<Checkbox
id={id}
checked={checked}
onCheckedChange={(next) => onCheckedChange(!!next)}
/>
<Label
htmlFor={id}
className="fui:text-sm fui:font-normal fui:whitespace-nowrap"
>
{label}
</Label>
</div>
);
}
/**
* Clears every filter at once.
*
* Rendered only when something is actually filtered: where filters apply on
* change it is easy to narrow a result and have no obvious way back, while a
* permanently visible "Clear" is another dead control in the row.
*/
function FilterReset({
active,
onReset,
label = "Clear filters",
}: {
active: boolean;
onReset: () => void;
label?: string;
}) {
if (!active) return null;
return (
<Button
type="button"
variant="ghost"
size="sm"
onClick={onReset}
className="fui:h-8"
>
<XIcon />
{label}
</Button>
);
}
export {
FilterBar,
FilterDateRange,
FilterField,
type FilterOption,
type FilterPeriod,
FilterReset,
FilterSearch,
FilterSelect,
FilterToggle,
};
+1 -1
View File
@@ -104,7 +104,7 @@ function FormLabel({
<FieldLabel
data-slot="form-label"
data-error={!!error}
className={cn("lib:data-[error=true]:text-destructive", className)}
className={cn("fui:data-[error=true]:text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
+2 -2
View File
@@ -30,12 +30,12 @@ function HoverCardContent({
alignOffset={alignOffset}
side={side}
sideOffset={sideOffset}
className="lib:isolate lib:z-50"
className="fui:isolate fui:z-50"
>
<PreviewCardPrimitive.Popup
data-slot="hover-card-content"
className={cn(
"lib:z-50 lib:w-72 lib:origin-(--transform-origin) lib:rounded-2xl lib:bg-popover lib:p-4 lib:text-sm lib:text-popover-foreground lib:shadow-2xl lib:ring-1 lib:ring-foreground/5 lib:outline-hidden lib:duration-100 lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=inline-end]:slide-in-from-left-2 lib:data-[side=inline-start]:slide-in-from-right-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95",
"fui:z-50 fui:w-72 fui:origin-(--transform-origin) fui:rounded-2xl fui:bg-popover fui:p-4 fui:text-sm fui:text-popover-foreground fui:shadow-2xl fui:ring-1 fui:ring-foreground/5 fui:outline-hidden fui:duration-100 fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=inline-end]:slide-in-from-left-2 fui:data-[side=inline-start]:slide-in-from-right-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95",
className,
)}
{...props}
+14 -14
View File
@@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
data-slot="input-group"
role="group"
className={cn(
"lib:group/input-group lib:relative lib:flex lib:h-9 lib:w-full lib:min-w-0 lib:items-center lib:rounded-4xl lib:border lib:border-input lib:bg-input/30 lib:transition-colors lib:outline-none lib:in-data-[slot=combobox-content]:focus-within:border-inherit lib:in-data-[slot=combobox-content]:focus-within:ring-0 lib:has-data-[align=block-end]:rounded-2xl lib:has-data-[align=block-start]:rounded-2xl lib:has-[[data-slot=input-group-control]:focus-visible]:border-ring lib:has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] lib:has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 lib:has-[[data-slot][aria-invalid=true]]:border-destructive lib:has-[[data-slot][aria-invalid=true]]:ring-[3px] lib:has-[[data-slot][aria-invalid=true]]:ring-destructive/20 lib:has-[textarea]:rounded-xl lib:has-[>[data-align=block-end]]:h-auto lib:has-[>[data-align=block-end]]:flex-col lib:has-[>[data-align=block-start]]:h-auto lib:has-[>[data-align=block-start]]:flex-col lib:has-[>textarea]:h-auto lib:dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 lib:has-[>[data-align=block-end]]:[&>input]:pt-3 lib:has-[>[data-align=block-start]]:[&>input]:pb-3 lib:has-[>[data-align=inline-end]]:[&>input]:pr-1.5 lib:has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
"fui:group/input-group fui:relative fui:flex fui:h-9 fui:w-full fui:min-w-0 fui:items-center fui:rounded-4xl fui:border fui:border-input fui:bg-input/30 fui:transition-colors fui:outline-none fui:in-data-[slot=combobox-content]:focus-within:border-inherit fui:in-data-[slot=combobox-content]:focus-within:ring-0 fui:has-data-[align=block-end]:rounded-2xl fui:has-data-[align=block-start]:rounded-2xl fui:has-[[data-slot=input-group-control]:focus-visible]:border-ring fui:has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] fui:has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 fui:has-[[data-slot][aria-invalid=true]]:border-destructive fui:has-[[data-slot][aria-invalid=true]]:ring-[3px] fui:has-[[data-slot][aria-invalid=true]]:ring-destructive/20 fui:has-[textarea]:rounded-xl fui:has-[>[data-align=block-end]]:h-auto fui:has-[>[data-align=block-end]]:flex-col fui:has-[>[data-align=block-start]]:h-auto fui:has-[>[data-align=block-start]]:flex-col fui:has-[>textarea]:h-auto fui:dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 fui:has-[>[data-align=block-end]]:[&>input]:pt-3 fui:has-[>[data-align=block-start]]:[&>input]:pb-3 fui:has-[>[data-align=inline-end]]:[&>input]:pr-1.5 fui:has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
className,
)}
{...props}
@@ -23,18 +23,18 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
}
const inputGroupAddonVariants = cva(
"lib:flex lib:h-auto lib:cursor-text lib:items-center lib:justify-center lib:gap-2 lib:py-2 lib:text-sm lib:font-medium lib:text-muted-foreground lib:select-none lib:group-data-[disabled=true]/input-group:opacity-50 lib:**:data-[slot=kbd]:rounded-4xl lib:**:data-[slot=kbd]:bg-muted-foreground/10 lib:**:data-[slot=kbd]:px-1.5 lib:[&>svg:not([class*=size-])]:size-4",
"fui:flex fui:h-auto fui:cursor-text fui:items-center fui:justify-center fui:gap-2 fui:py-2 fui:text-sm fui:font-medium fui:text-muted-foreground fui:select-none fui:group-data-[disabled=true]/input-group:opacity-50 fui:**:data-[slot=kbd]:rounded-4xl fui:**:data-[slot=kbd]:bg-muted-foreground/10 fui:**:data-[slot=kbd]:px-1.5 fui:[&>svg:not([class*=size-])]:size-4",
{
variants: {
align: {
"inline-start":
"lib:order-first lib:pl-3 lib:has-[>button]:-ml-1 lib:has-[>kbd]:ml-[-0.15rem]",
"fui:order-first fui:pl-3 fui:has-[>button]:-ml-1 fui:has-[>kbd]:ml-[-0.15rem]",
"inline-end":
"lib:order-last lib:pr-3 lib:has-[>button]:-mr-1 lib:has-[>kbd]:mr-[-0.15rem]",
"fui:order-last fui:pr-3 fui:has-[>button]:-mr-1 fui:has-[>kbd]:mr-[-0.15rem]",
"block-start":
"lib:order-first lib:w-full lib:justify-start lib:px-3 lib:pt-3 lib:group-has-[>input]/input-group:pt-3 lib:[.border-b]:pb-3",
"fui:order-first fui:w-full fui:justify-start fui:px-3 fui:pt-3 fui:group-has-[>input]/input-group:pt-3 fui:[.border-b]:pb-3",
"block-end":
"lib:order-last lib:w-full lib:justify-start lib:px-3 lib:pb-3 lib:group-has-[>input]/input-group:pb-3 lib:[.border-t]:pt-3",
"fui:order-last fui:w-full fui:justify-start fui:px-3 fui:pb-3 fui:group-has-[>input]/input-group:pb-3 fui:[.border-t]:pt-3",
},
},
defaultVariants: {
@@ -66,14 +66,14 @@ function InputGroupAddon({
}
const inputGroupButtonVariants = cva(
"lib:flex lib:items-center lib:gap-2 lib:rounded-4xl lib:text-sm lib:shadow-none",
"fui:flex fui:items-center fui:gap-2 fui:rounded-4xl fui:text-sm fui:shadow-none",
{
variants: {
size: {
xs: "lib:h-6 lib:gap-1 lib:px-1.5 lib:[&>svg:not([class*=size-])]:size-3.5",
sm: "lib:",
"icon-xs": "lib:size-6 lib:p-0 lib:has-[>svg]:p-0",
"icon-sm": "lib:size-8 lib:p-0 lib:has-[>svg]:p-0",
xs: "fui:h-6 fui:gap-1 fui:px-1.5 fui:[&>svg:not([class*=size-])]:size-3.5",
sm: "fui:",
"icon-xs": "fui:size-6 fui:p-0 fui:has-[>svg]:p-0",
"icon-sm": "fui:size-8 fui:p-0 fui:has-[>svg]:p-0",
},
},
defaultVariants: {
@@ -105,7 +105,7 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"lib:flex lib:items-center lib:gap-2 lib:text-sm lib:text-muted-foreground lib:[&_svg]:pointer-events-none lib:[&_svg:not([class*=size-])]:size-4",
"fui:flex fui:items-center fui:gap-2 fui:text-sm fui:text-muted-foreground fui:[&_svg]:pointer-events-none fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
@@ -121,7 +121,7 @@ function InputGroupInput({
<Input
data-slot="input-group-control"
className={cn(
"lib:flex-1 lib:rounded-none lib:border-0 lib:bg-transparent lib:shadow-none lib:ring-0 lib:focus-visible:ring-0 lib:aria-invalid:ring-0 lib:dark:bg-transparent",
"fui:flex-1 fui:rounded-none fui:border-0 fui:bg-transparent fui:shadow-none fui:ring-0 fui:focus-visible:ring-0 fui:aria-invalid:ring-0 fui:dark:bg-transparent",
className,
)}
{...props}
@@ -137,7 +137,7 @@ function InputGroupTextarea({
<Textarea
data-slot="input-group-control"
className={cn(
"lib:flex-1 lib:resize-none lib:rounded-none lib:border-0 lib:bg-transparent lib:py-2 lib:shadow-none lib:ring-0 lib:focus-visible:ring-0 lib:aria-invalid:ring-0 lib:dark:bg-transparent",
"fui:flex-1 fui:resize-none fui:rounded-none fui:border-0 fui:bg-transparent fui:py-2 fui:shadow-none fui:ring-0 fui:focus-visible:ring-0 fui:aria-invalid:ring-0 fui:dark:bg-transparent",
className,
)}
{...props}
+6 -6
View File
@@ -18,7 +18,7 @@ function InputOTP({
containerClassName,
)}
spellCheck={false}
className={cn("lib:disabled:cursor-not-allowed", className)}
className={cn("fui:disabled:cursor-not-allowed", className)}
{...props}
/>
);
@@ -29,7 +29,7 @@ function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="input-otp-group"
className={cn(
"lib:flex lib:items-center lib:rounded-4xl lib:has-aria-invalid:border-destructive lib:has-aria-invalid:ring-[3px] lib:has-aria-invalid:ring-destructive/20 lib:dark:has-aria-invalid:ring-destructive/40",
"fui:flex fui:items-center fui:rounded-4xl fui:has-aria-invalid:border-destructive fui:has-aria-invalid:ring-[3px] fui:has-aria-invalid:ring-destructive/20 fui:dark:has-aria-invalid:ring-destructive/40",
className,
)}
{...props}
@@ -52,15 +52,15 @@ function InputOTPSlot({
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
"lib:relative lib:flex lib:size-9 lib:items-center lib:justify-center lib:border-y lib:border-r lib:border-input lib:bg-input/30 lib:text-sm lib:transition-all lib:outline-none lib:first:rounded-l-4xl lib:first:border-l lib:last:rounded-r-4xl lib:aria-invalid:border-destructive lib:data-[active=true]:z-10 lib:data-[active=true]:border-ring lib:data-[active=true]:ring-[3px] lib:data-[active=true]:ring-ring/50 lib:data-[active=true]:aria-invalid:border-destructive lib:data-[active=true]:aria-invalid:ring-destructive/20 lib:dark:data-[active=true]:aria-invalid:ring-destructive/40",
"fui:relative fui:flex fui:size-9 fui:items-center fui:justify-center fui:border-y fui:border-r fui:border-input fui:bg-input/30 fui:text-sm fui:transition-all fui:outline-none fui:first:rounded-l-4xl fui:first:border-l fui:last:rounded-r-4xl fui:aria-invalid:border-destructive fui:data-[active=true]:z-10 fui:data-[active=true]:border-ring fui:data-[active=true]:ring-[3px] fui:data-[active=true]:ring-ring/50 fui:data-[active=true]:aria-invalid:border-destructive fui:data-[active=true]:aria-invalid:ring-destructive/20 fui:dark:data-[active=true]:aria-invalid:ring-destructive/40",
className,
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="lib:pointer-events-none lib:absolute lib:inset-0 lib:flex lib:items-center lib:justify-center">
<div className="lib:h-4 lib:w-px lib:animate-caret-blink lib:bg-foreground lib:duration-1000" />
<div className="fui:pointer-events-none fui:absolute fui:inset-0 fui:flex fui:items-center fui:justify-center">
<div className="fui:h-4 fui:w-px fui:animate-caret-blink fui:bg-foreground fui:duration-1000" />
</div>
)}
</div>
@@ -71,7 +71,7 @@ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-otp-separator"
className="lib:flex lib:items-center lib:[&_svg:not([class*=size-])]:size-4"
className="fui:flex fui:items-center fui:[&_svg:not([class*=size-])]:size-4"
role="separator"
{...props}
>
+1 -1
View File
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
"lib:h-9 lib:w-full lib:min-w-0 lib:rounded-4xl lib:border lib:border-input lib:bg-input/30 lib:px-3 lib:py-1 lib:text-base lib:transition-colors lib:outline-none lib:file:inline-flex lib:file:h-7 lib:file:border-0 lib:file:bg-transparent lib:file:text-sm lib:file:font-medium lib:file:text-foreground lib:placeholder:text-muted-foreground lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:disabled:pointer-events-none lib:disabled:cursor-not-allowed lib:disabled:opacity-50 lib:aria-invalid:border-destructive lib:aria-invalid:ring-[3px] lib:aria-invalid:ring-destructive/20 lib:md:text-sm lib:dark:aria-invalid:border-destructive/50 lib:dark:aria-invalid:ring-destructive/40",
"fui:h-9 fui:w-full fui:min-w-0 fui:rounded-4xl fui:border fui:border-input fui:bg-input/30 fui:px-3 fui:py-1 fui:text-base fui:transition-colors fui:outline-none fui:file:inline-flex fui:file:h-7 fui:file:border-0 fui:file:bg-transparent fui:file:text-sm fui:file:font-medium fui:file:text-foreground fui:placeholder:text-muted-foreground fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:disabled:pointer-events-none fui:disabled:cursor-not-allowed fui:disabled:opacity-50 fui:aria-invalid:border-destructive fui:aria-invalid:ring-[3px] fui:aria-invalid:ring-destructive/20 fui:md:text-sm fui:dark:aria-invalid:border-destructive/50 fui:dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
+19 -19
View File
@@ -12,7 +12,7 @@ function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
role="list"
data-slot="item-group"
className={cn(
"lib:group/item-group lib:flex lib:w-full lib:flex-col lib:gap-4 lib:has-data-[size=sm]:gap-2.5 lib:has-data-[size=xs]:gap-2",
"fui:group/item-group fui:flex fui:w-full fui:flex-col fui:gap-4 fui:has-data-[size=sm]:gap-2.5 fui:has-data-[size=xs]:gap-2",
className,
)}
{...props}
@@ -28,25 +28,25 @@ function ItemSeparator({
<Separator
data-slot="item-separator"
orientation="horizontal"
className={cn("lib:my-2", className)}
className={cn("fui:my-2", className)}
{...props}
/>
);
}
const itemVariants = cva(
"lib:group/item lib:flex lib:w-full lib:flex-wrap lib:items-center lib:rounded-2xl lib:border lib:text-sm lib:transition-colors lib:duration-100 lib:outline-none lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:[a]:transition-colors lib:[a]:hover:bg-muted",
"fui:group/item fui:flex fui:w-full fui:flex-wrap fui:items-center fui:rounded-2xl fui:border fui:text-sm fui:transition-colors fui:duration-100 fui:outline-none fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:[a]:transition-colors fui:[a]:hover:bg-muted",
{
variants: {
variant: {
default: "lib:border-transparent",
outline: "lib:border-border",
muted: "lib:border-transparent lib:bg-muted/50",
default: "fui:border-transparent",
outline: "fui:border-border",
muted: "fui:border-transparent fui:bg-muted/50",
},
size: {
default: "lib:gap-3.5 lib:px-4 lib:py-3.5",
sm: "lib:gap-3.5 lib:px-3.5 lib:py-3",
xs: "lib:gap-2.5 lib:px-3 lib:py-2.5 lib:in-data-[slot=dropdown-menu-content]:p-0",
default: "fui:gap-3.5 fui:px-4 fui:py-3.5",
sm: "fui:gap-3.5 fui:px-3.5 fui:py-3",
xs: "fui:gap-2.5 fui:px-3 fui:py-2.5 fui:in-data-[slot=dropdown-menu-content]:p-0",
},
},
defaultVariants: {
@@ -81,14 +81,14 @@ function Item({
}
const itemMediaVariants = cva(
"lib:flex lib:shrink-0 lib:items-center lib:justify-center lib:gap-2 lib:group-has-data-[slot=item-description]/item:translate-y-0.5 lib:group-has-data-[slot=item-description]/item:self-start lib:[&_svg]:pointer-events-none",
"fui:flex fui:shrink-0 fui:items-center fui:justify-center fui:gap-2 fui:group-has-data-[slot=item-description]/item:translate-y-0.5 fui:group-has-data-[slot=item-description]/item:self-start fui:[&_svg]:pointer-events-none",
{
variants: {
variant: {
default: "lib:bg-transparent",
icon: "lib:[&_svg:not([class*=size-])]:size-4",
default: "fui:bg-transparent",
icon: "fui:[&_svg:not([class*=size-])]:size-4",
image:
"lib:size-10 lib:overflow-hidden lib:rounded-lg lib:group-data-[size=sm]/item:size-8 lib:group-data-[size=xs]/item:size-6 lib:group-data-[size=xs]/item:rounded-md lib:[&_img]:size-full lib:[&_img]:object-cover",
"fui:size-10 fui:overflow-hidden fui:rounded-lg fui:group-data-[size=sm]/item:size-8 fui:group-data-[size=xs]/item:size-6 fui:group-data-[size=xs]/item:rounded-md fui:[&_img]:size-full fui:[&_img]:object-cover",
},
},
defaultVariants: {
@@ -117,7 +117,7 @@ function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="item-content"
className={cn(
"lib:flex lib:flex-1 lib:flex-col lib:gap-1 lib:group-data-[size=xs]/item:gap-0.5 lib:[&+[data-slot=item-content]]:flex-none",
"fui:flex fui:flex-1 fui:flex-col fui:gap-1 fui:group-data-[size=xs]/item:gap-0.5 fui:[&+[data-slot=item-content]]:flex-none",
className,
)}
{...props}
@@ -130,7 +130,7 @@ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="item-title"
className={cn(
"lib:line-clamp-1 lib:flex lib:w-fit lib:items-center lib:gap-2 lib:text-sm lib:leading-snug lib:font-medium lib:underline-offset-4",
"fui:line-clamp-1 fui:flex fui:w-fit fui:items-center fui:gap-2 fui:text-sm fui:leading-snug fui:font-medium fui:underline-offset-4",
className,
)}
{...props}
@@ -143,7 +143,7 @@ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
<p
data-slot="item-description"
className={cn(
"lib:line-clamp-2 lib:text-left lib:text-sm lib:font-normal lib:text-muted-foreground lib:[&>a]:underline lib:[&>a]:underline-offset-4 lib:[&>a:hover]:text-primary",
"fui:line-clamp-2 fui:text-left fui:text-sm fui:font-normal fui:text-muted-foreground fui:[&>a]:underline fui:[&>a]:underline-offset-4 fui:[&>a:hover]:text-primary",
className,
)}
{...props}
@@ -155,7 +155,7 @@ function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-actions"
className={cn("lib:flex lib:items-center lib:gap-2", className)}
className={cn("fui:flex fui:items-center fui:gap-2", className)}
{...props}
/>
);
@@ -166,7 +166,7 @@ function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="item-header"
className={cn(
"lib:flex lib:basis-full lib:items-center lib:justify-between lib:gap-2",
"fui:flex fui:basis-full fui:items-center fui:justify-between fui:gap-2",
className,
)}
{...props}
@@ -179,7 +179,7 @@ function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="item-footer"
className={cn(
"lib:flex lib:basis-full lib:items-center lib:justify-between lib:gap-2",
"fui:flex fui:basis-full fui:items-center fui:justify-between fui:gap-2",
className,
)}
{...props}
+2 -2
View File
@@ -5,7 +5,7 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
<kbd
data-slot="kbd"
className={cn(
"lib:pointer-events-none lib:inline-flex lib:h-5 lib:w-fit lib:min-w-5 lib:items-center lib:justify-center lib:gap-1 lib:rounded-sm lib:bg-muted lib:px-1 lib:font-sans lib:text-xs lib:font-medium lib:text-muted-foreground lib:select-none lib:in-data-[slot=tooltip-content]:bg-background/20 lib:in-data-[slot=tooltip-content]:text-background lib:dark:in-data-[slot=tooltip-content]:bg-background/10 lib:[&_svg:not([class*=size-])]:size-3",
"fui:pointer-events-none fui:inline-flex fui:h-5 fui:w-fit fui:min-w-5 fui:items-center fui:justify-center fui:gap-1 fui:rounded-sm fui:bg-muted fui:px-1 fui:font-sans fui:text-xs fui:font-medium fui:text-muted-foreground fui:select-none fui:in-data-[slot=tooltip-content]:bg-background/20 fui:in-data-[slot=tooltip-content]:text-background fui:dark:in-data-[slot=tooltip-content]:bg-background/10 fui:[&_svg:not([class*=size-])]:size-3",
className,
)}
{...props}
@@ -17,7 +17,7 @@ function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<kbd
data-slot="kbd-group"
className={cn("lib:inline-flex lib:items-center lib:gap-1", className)}
className={cn("fui:inline-flex fui:items-center fui:gap-1", className)}
{...props}
/>
);
+1 -1
View File
@@ -8,7 +8,7 @@ function Label({ className, ...props }: React.ComponentProps<"label">) {
<label
data-slot="label"
className={cn(
"lib:flex lib:items-center lib:gap-2 lib:text-sm lib:leading-none lib:font-medium lib:select-none lib:group-data-[disabled=true]:pointer-events-none lib:group-data-[disabled=true]:opacity-50 lib:peer-disabled:cursor-not-allowed lib:peer-disabled:opacity-50",
"fui:flex fui:items-center fui:gap-2 fui:text-sm fui:leading-none fui:font-medium fui:select-none fui:group-data-[disabled=true]:pointer-events-none fui:group-data-[disabled=true]:opacity-50 fui:peer-disabled:cursor-not-allowed fui:peer-disabled:opacity-50",
className,
)}
{...props}
+13 -13
View File
@@ -26,7 +26,7 @@ function Menubar({ className, ...props }: MenubarPrimitive.Props) {
<MenubarPrimitive
data-slot="menubar"
className={cn(
"lib:flex lib:h-9 lib:items-center lib:rounded-4xl lib:border lib:p-1",
"fui:flex fui:h-9 fui:items-center fui:rounded-4xl fui:border fui:p-1",
className,
)}
{...props}
@@ -58,7 +58,7 @@ function MenubarTrigger({
<DropdownMenuTrigger
data-slot="menubar-trigger"
className={cn(
"lib:flex lib:items-center lib:rounded-xl lib:px-2 lib:py-0.75 lib:text-sm lib:font-medium lib:outline-hidden lib:select-none lib:hover:bg-muted lib:aria-expanded:bg-muted",
"fui:flex fui:items-center fui:rounded-xl fui:px-2 fui:py-0.75 fui:text-sm fui:font-medium fui:outline-hidden fui:select-none fui:hover:bg-muted fui:aria-expanded:bg-muted",
className,
)}
{...props}
@@ -80,7 +80,7 @@ function MenubarContent({
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"lib: lib: lib:min-w-48 lib:rounded-2xl lib:bg-popover lib:p-1 lib:text-popover-foreground lib:shadow-2xl lib:ring-1 lib:ring-foreground/5 lib:duration-100 lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=inline-end]:slide-in-from-left-2 lib:data-[side=inline-start]:slide-in-from-right-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
"fui: fui: fui:min-w-48 fui:rounded-2xl fui:bg-popover fui:p-1 fui:text-popover-foreground fui:shadow-2xl fui:ring-1 fui:ring-foreground/5 fui:duration-100 fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=inline-end]:slide-in-from-left-2 fui:data-[side=inline-start]:slide-in-from-right-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
className,
)}
{...props}
@@ -100,7 +100,7 @@ function MenubarItem({
data-inset={inset}
data-variant={variant}
className={cn(
"lib:group/menubar-item lib:gap-2.5 lib:rounded-xl lib:px-3 lib:py-2 lib:text-sm lib:focus:bg-accent lib:focus:text-accent-foreground lib:not-data-[variant=destructive]:focus:**:text-accent-foreground lib:data-inset:pl-9.5 lib:data-[variant=destructive]:text-destructive lib:data-[variant=destructive]:focus:bg-destructive/10 lib:data-[variant=destructive]:focus:text-destructive lib:dark:data-[variant=destructive]:focus:bg-destructive/20 lib:data-disabled:opacity-50 lib:[&_svg:not([class*=size-])]:size-4 lib:data-[variant=destructive]:*:[svg]:text-destructive!",
"fui:group/menubar-item fui:gap-2.5 fui:rounded-xl fui:px-3 fui:py-2 fui:text-sm fui:focus:bg-accent fui:focus:text-accent-foreground fui:not-data-[variant=destructive]:focus:**:text-accent-foreground fui:data-inset:pl-9.5 fui:data-[variant=destructive]:text-destructive fui:data-[variant=destructive]:focus:bg-destructive/10 fui:data-[variant=destructive]:focus:text-destructive fui:dark:data-[variant=destructive]:focus:bg-destructive/20 fui:data-disabled:opacity-50 fui:[&_svg:not([class*=size-])]:size-4 fui:data-[variant=destructive]:*:[svg]:text-destructive!",
className,
)}
{...props}
@@ -122,13 +122,13 @@ function MenubarCheckboxItem({
data-slot="menubar-checkbox-item"
data-inset={inset}
className={cn(
"lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2.5 lib:rounded-xl lib:py-2 lib:pr-3 lib:pl-9.5 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:focus:**:text-accent-foreground lib:data-inset:pl-9.5 lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0",
"fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2.5 fui:rounded-xl fui:py-2 fui:pr-3 fui:pl-9.5 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:focus:**:text-accent-foreground fui:data-inset:pl-9.5 fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0",
className,
)}
checked={checked}
{...props}
>
<span className="lib:pointer-events-none lib:absolute lib:left-3 lib:flex lib:size-4 lib:items-center lib:justify-center lib:[&_svg:not([class*=size-])]:size-4">
<span className="fui:pointer-events-none fui:absolute fui:left-3 fui:flex fui:size-4 fui:items-center fui:justify-center fui:[&_svg:not([class*=size-])]:size-4">
<MenuPrimitive.CheckboxItemIndicator>
<CheckIcon />
</MenuPrimitive.CheckboxItemIndicator>
@@ -157,12 +157,12 @@ function MenubarRadioItem({
data-slot="menubar-radio-item"
data-inset={inset}
className={cn(
"lib:relative lib:flex lib:cursor-default lib:items-center lib:gap-2.5 lib:rounded-xl lib:py-2 lib:pr-3 lib:pl-9.5 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:focus:**:text-accent-foreground lib:data-inset:pl-9.5 lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:relative fui:flex fui:cursor-default fui:items-center fui:gap-2.5 fui:rounded-xl fui:py-2 fui:pr-3 fui:pl-9.5 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:focus:**:text-accent-foreground fui:data-inset:pl-9.5 fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
>
<span className="lib:pointer-events-none lib:absolute lib:left-3 lib:flex lib:size-4 lib:items-center lib:justify-center lib:[&_svg:not([class*=size-])]:size-4">
<span className="fui:pointer-events-none fui:absolute fui:left-3 fui:flex fui:size-4 fui:items-center fui:justify-center fui:[&_svg:not([class*=size-])]:size-4">
<MenuPrimitive.RadioItemIndicator>
<CheckIcon />
</MenuPrimitive.RadioItemIndicator>
@@ -184,7 +184,7 @@ function MenubarLabel({
data-slot="menubar-label"
data-inset={inset}
className={cn(
"lib:px-3.5 lib:py-2.5 lib:text-xs lib:text-muted-foreground lib:data-inset:pl-9.5",
"fui:px-3.5 fui:py-2.5 fui:text-xs fui:text-muted-foreground fui:data-inset:pl-9.5",
className,
)}
{...props}
@@ -199,7 +199,7 @@ function MenubarSeparator({
return (
<DropdownMenuSeparator
data-slot="menubar-separator"
className={cn("lib:-mx-1 lib:my-1 lib:h-px lib:bg-border/50", className)}
className={cn("fui:-mx-1 fui:my-1 fui:h-px fui:bg-border/50", className)}
{...props}
/>
);
@@ -213,7 +213,7 @@ function MenubarShortcut({
<DropdownMenuShortcut
data-slot="menubar-shortcut"
className={cn(
"lib:ml-auto lib:text-xs lib:tracking-widest lib:text-muted-foreground lib:group-focus/menubar-item:text-accent-foreground",
"fui:ml-auto fui:text-xs fui:tracking-widest fui:text-muted-foreground fui:group-focus/menubar-item:text-accent-foreground",
className,
)}
{...props}
@@ -239,7 +239,7 @@ function MenubarSubTrigger({
data-slot="menubar-sub-trigger"
data-inset={inset}
className={cn(
"lib:gap-2 lib:rounded-xl lib:px-3 lib:py-2 lib:text-sm lib:focus:bg-accent lib:focus:text-accent-foreground lib:data-inset:pl-9.5 lib:data-open:bg-accent lib:data-open:text-accent-foreground lib:[&_svg:not([class*=size-])]:size-4",
"fui:gap-2 fui:rounded-xl fui:px-3 fui:py-2 fui:text-sm fui:focus:bg-accent fui:focus:text-accent-foreground fui:data-inset:pl-9.5 fui:data-open:bg-accent fui:data-open:text-accent-foreground fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
@@ -255,7 +255,7 @@ function MenubarSubContent({
<DropdownMenuSubContent
data-slot="menubar-sub-content"
className={cn(
"lib: lib: lib:min-w-32 lib:rounded-2xl lib:bg-popover lib:p-1 lib:text-popover-foreground lib:shadow-2xl lib:ring-1 lib:ring-foreground/5 lib:duration-100 lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
"fui: fui: fui:min-w-32 fui:rounded-2xl fui:bg-popover fui:p-1 fui:text-popover-foreground fui:shadow-2xl fui:ring-1 fui:ring-foreground/5 fui:duration-100 fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
className,
)}
{...props}
+13 -13
View File
@@ -14,7 +14,7 @@ function NavigationMenu({
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
className={cn(
"lib:group/navigation-menu lib:relative lib:flex lib:max-w-max lib:flex-1 lib:items-center lib:justify-center",
"fui:group/navigation-menu fui:relative fui:flex fui:max-w-max fui:flex-1 fui:items-center fui:justify-center",
className,
)}
{...props}
@@ -33,7 +33,7 @@ function NavigationMenuList({
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn(
"lib:group lib:flex lib:flex-1 lib:list-none lib:items-center lib:justify-center lib:gap-0",
"fui:group fui:flex fui:flex-1 fui:list-none fui:items-center fui:justify-center fui:gap-0",
className,
)}
{...props}
@@ -48,14 +48,14 @@ function NavigationMenuItem({
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn("lib:relative", className)}
className={cn("fui:relative", className)}
{...props}
/>
);
}
const navigationMenuTriggerStyle = cva(
"lib:group/navigation-menu-trigger lib:inline-flex lib:h-9 lib:w-max lib:items-center lib:justify-center lib:rounded-2xl lib:px-4.5 lib:py-2.5 lib:text-sm lib:font-medium lib:transition-all lib:outline-none lib:hover:bg-muted lib:focus:bg-muted lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:focus-visible:outline-1 lib:disabled:pointer-events-none lib:disabled:opacity-50 lib:data-popup-open:bg-muted/50 lib:data-popup-open:hover:bg-muted lib:data-open:bg-muted/50 lib:data-open:hover:bg-muted lib:data-open:focus:bg-muted",
"fui:group/navigation-menu-trigger fui:inline-flex fui:h-9 fui:w-max fui:items-center fui:justify-center fui:rounded-2xl fui:px-4.5 fui:py-2.5 fui:text-sm fui:font-medium fui:transition-all fui:outline-none fui:hover:bg-muted fui:focus:bg-muted fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:focus-visible:outline-1 fui:disabled:pointer-events-none fui:disabled:opacity-50 fui:data-popup-open:bg-muted/50 fui:data-popup-open:hover:bg-muted fui:data-open:bg-muted/50 fui:data-open:hover:bg-muted fui:data-open:focus:bg-muted",
);
function NavigationMenuTrigger({
@@ -66,12 +66,12 @@ function NavigationMenuTrigger({
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), "lib:group", className)}
className={cn(navigationMenuTriggerStyle(), "fui:group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="lib:relative lib:top-px lib:ml-1 lib:size-3 lib:transition lib:duration-300 lib:group-data-popup-open/navigation-menu-trigger:rotate-180 lib:group-data-open/navigation-menu-trigger:rotate-180"
className="fui:relative fui:top-px fui:ml-1 fui:size-3 fui:transition fui:duration-300 fui:group-data-popup-open/navigation-menu-trigger:rotate-180 fui:group-data-open/navigation-menu-trigger:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
@@ -86,7 +86,7 @@ function NavigationMenuContent({
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"lib:data-ending-style:data-activation-direction=left:translate-x-[50%] lib:data-ending-style:data-activation-direction=right:translate-x-[-50%] lib:data-starting-style:data-activation-direction=left:translate-x-[-50%] lib:data-starting-style:data-activation-direction=right:translate-x-[50%] lib:isolate lib:z-50 lib:h-full lib:w-auto lib:p-2.5 lib:pr-3 lib:transition-[opacity,transform,translate] lib:duration-[0.35s] lib:ease-[cubic-bezier(0.22,1,0.36,1)] lib:group-data-[viewport=false]/navigation-menu:rounded-2xl lib:group-data-[viewport=false]/navigation-menu:bg-popover lib:group-data-[viewport=false]/navigation-menu:text-popover-foreground lib:group-data-[viewport=false]/navigation-menu:shadow-2xl lib:group-data-[viewport=false]/navigation-menu:ring-1 lib:group-data-[viewport=false]/navigation-menu:ring-foreground/5 lib:group-data-[viewport=false]/navigation-menu:duration-300 lib:data-ending-style:opacity-0 lib:data-starting-style:opacity-0 lib:data-[motion=from-end]:slide-in-from-right-52 lib:data-[motion=from-start]:slide-in-from-left-52 lib:data-[motion=to-end]:slide-out-to-right-52 lib:data-[motion=to-start]:slide-out-to-left-52 lib:data-[motion^=from-]:animate-in lib:data-[motion^=from-]:fade-in lib:data-[motion^=to-]:animate-out lib:data-[motion^=to-]:fade-out lib:**:data-[slot=navigation-menu-link]:focus:ring-0 lib:**:data-[slot=navigation-menu-link]:focus:outline-none lib:group-data-[viewport=false]/navigation-menu:data-open:animate-in lib:group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 lib:group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 lib:group-data-[viewport=false]/navigation-menu:data-closed:animate-out lib:group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 lib:group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
"fui:data-ending-style:data-activation-direction=left:translate-x-[50%] fui:data-ending-style:data-activation-direction=right:translate-x-[-50%] fui:data-starting-style:data-activation-direction=left:translate-x-[-50%] fui:data-starting-style:data-activation-direction=right:translate-x-[50%] fui:isolate fui:z-50 fui:h-full fui:w-auto fui:p-2.5 fui:pr-3 fui:transition-[opacity,transform,translate] fui:duration-[0.35s] fui:ease-[cubic-bezier(0.22,1,0.36,1)] fui:group-data-[viewport=false]/navigation-menu:rounded-2xl fui:group-data-[viewport=false]/navigation-menu:bg-popover fui:group-data-[viewport=false]/navigation-menu:text-popover-foreground fui:group-data-[viewport=false]/navigation-menu:shadow-2xl fui:group-data-[viewport=false]/navigation-menu:ring-1 fui:group-data-[viewport=false]/navigation-menu:ring-foreground/5 fui:group-data-[viewport=false]/navigation-menu:duration-300 fui:data-ending-style:opacity-0 fui:data-starting-style:opacity-0 fui:data-[motion=from-end]:slide-in-from-right-52 fui:data-[motion=from-start]:slide-in-from-left-52 fui:data-[motion=to-end]:slide-out-to-right-52 fui:data-[motion=to-start]:slide-out-to-left-52 fui:data-[motion^=from-]:animate-in fui:data-[motion^=from-]:fade-in fui:data-[motion^=to-]:animate-out fui:data-[motion^=to-]:fade-out fui:**:data-[slot=navigation-menu-link]:focus:ring-0 fui:**:data-[slot=navigation-menu-link]:focus:outline-none fui:group-data-[viewport=false]/navigation-menu:data-open:animate-in fui:group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 fui:group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 fui:group-data-[viewport=false]/navigation-menu:data-closed:animate-out fui:group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 fui:group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
className,
)}
{...props}
@@ -110,13 +110,13 @@ function NavigationMenuPositioner({
align={align}
alignOffset={alignOffset}
className={cn(
"lib:isolate lib:z-50 lib:h-(--positioner-height) lib:w-(--positioner-width) lib:max-w-(--available-width) lib:transition-[top,left,right,bottom] lib:duration-[0.35s] lib:ease-[cubic-bezier(0.22,1,0.36,1)] lib:data-instant:transition-none lib:data-[side=bottom]:before:top-[-10px] lib:data-[side=bottom]:before:right-0 lib:data-[side=bottom]:before:left-0",
"fui:isolate fui:z-50 fui:h-(--positioner-height) fui:w-(--positioner-width) fui:max-w-(--available-width) fui:transition-[top,left,right,bottom] fui:duration-[0.35s] fui:ease-[cubic-bezier(0.22,1,0.36,1)] fui:data-instant:transition-none fui:data-[side=bottom]:before:top-[-10px] fui:data-[side=bottom]:before:right-0 fui:data-[side=bottom]:before:left-0",
className,
)}
{...props}
>
<NavigationMenuPrimitive.Popup className="lib:data-[ending-style]:easing-[ease] lib:xs:w-(--popup-width) lib:relative lib:h-(--popup-height) lib:w-(--popup-width) lib:origin-(--transform-origin) lib:rounded-2xl lib:bg-popover lib:text-popover-foreground lib:shadow lib:ring-1 lib:ring-foreground/5 lib:transition-[opacity,transform,width,height,scale,translate] lib:duration-[0.35s] lib:ease-[cubic-bezier(0.22,1,0.36,1)] lib:outline-none lib:data-ending-style:scale-90 lib:data-ending-style:opacity-0 lib:data-ending-style:duration-150 lib:data-starting-style:scale-90 lib:data-starting-style:opacity-0">
<NavigationMenuPrimitive.Viewport className="lib:relative lib:size-full lib:overflow-hidden" />
<NavigationMenuPrimitive.Popup className="fui:data-[ending-style]:easing-[ease] fui:xs:w-(--popup-width) fui:relative fui:h-(--popup-height) fui:w-(--popup-width) fui:origin-(--transform-origin) fui:rounded-2xl fui:bg-popover fui:text-popover-foreground fui:shadow fui:ring-1 fui:ring-foreground/5 fui:transition-[opacity,transform,width,height,scale,translate] fui:duration-[0.35s] fui:ease-[cubic-bezier(0.22,1,0.36,1)] fui:outline-none fui:data-ending-style:scale-90 fui:data-ending-style:opacity-0 fui:data-ending-style:duration-150 fui:data-starting-style:scale-90 fui:data-starting-style:opacity-0">
<NavigationMenuPrimitive.Viewport className="fui:relative fui:size-full fui:overflow-hidden" />
</NavigationMenuPrimitive.Popup>
</NavigationMenuPrimitive.Positioner>
</NavigationMenuPrimitive.Portal>
@@ -131,7 +131,7 @@ function NavigationMenuLink({
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"lib:flex lib:items-center lib:gap-1.5 lib:rounded-2xl lib:p-3 lib:text-sm lib:transition-all lib:outline-none lib:hover:bg-muted lib:focus:bg-muted lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:focus-visible:outline-1 lib:in-data-[slot=navigation-menu-content]:rounded-xl lib:data-[active=true]:bg-muted/50 lib:data-[active=true]:hover:bg-muted lib:data-[active=true]:focus:bg-muted lib:[&_svg:not([class*=size-])]:size-4",
"fui:flex fui:items-center fui:gap-1.5 fui:rounded-2xl fui:p-3 fui:text-sm fui:transition-all fui:outline-none fui:hover:bg-muted fui:focus:bg-muted fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:focus-visible:outline-1 fui:in-data-[slot=navigation-menu-content]:rounded-xl fui:data-[active=true]:bg-muted/50 fui:data-[active=true]:hover:bg-muted fui:data-[active=true]:focus:bg-muted fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
@@ -147,12 +147,12 @@ function NavigationMenuIndicator({
<NavigationMenuPrimitive.Icon
data-slot="navigation-menu-indicator"
className={cn(
"lib:top-full lib:z-1 lib:flex lib:h-1.5 lib:items-end lib:justify-center lib:overflow-hidden lib:data-[state=hidden]:animate-out lib:data-[state=hidden]:fade-out lib:data-[state=visible]:animate-in lib:data-[state=visible]:fade-in",
"fui:top-full fui:z-1 fui:flex fui:h-1.5 fui:items-end fui:justify-center fui:overflow-hidden fui:data-[state=hidden]:animate-out fui:data-[state=hidden]:fade-out fui:data-[state=visible]:animate-in fui:data-[state=visible]:fade-in",
className,
)}
{...props}
>
<div className="lib:relative lib:top-[60%] lib:h-2 lib:w-2 lib:rotate-45 lib:rounded-tl-sm lib:bg-border lib:shadow-md" />
<div className="fui:relative fui:top-[60%] fui:h-2 fui:w-2 fui:rotate-45 fui:rounded-tl-sm fui:bg-border fui:shadow-md" />
</NavigationMenuPrimitive.Icon>
);
}
+8 -8
View File
@@ -13,7 +13,7 @@ function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
aria-label="pagination"
data-slot="pagination"
className={cn(
"lib:mx-auto lib:flex lib:w-full lib:justify-center",
"fui:mx-auto fui:flex fui:w-full fui:justify-center",
className,
)}
{...props}
@@ -28,7 +28,7 @@ function PaginationContent({
return (
<ul
data-slot="pagination-content"
className={cn("lib:flex lib:items-center lib:gap-1", className)}
className={cn("fui:flex fui:items-center fui:gap-1", className)}
{...props}
/>
);
@@ -75,11 +75,11 @@ function PaginationPrevious({
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("lib:pl-2!", className)}
className={cn("fui:pl-2!", className)}
{...props}
>
<ChevronLeftIcon data-icon="inline-start" />
<span className="lib:hidden lib:sm:block">{text}</span>
<span className="fui:hidden fui:sm:block">{text}</span>
</PaginationLink>
);
}
@@ -93,10 +93,10 @@ function PaginationNext({
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("lib:pr-2!", className)}
className={cn("fui:pr-2!", className)}
{...props}
>
<span className="lib:hidden lib:sm:block">{text}</span>
<span className="fui:hidden fui:sm:block">{text}</span>
<ChevronRightIcon data-icon="inline-end" />
</PaginationLink>
);
@@ -111,13 +111,13 @@ function PaginationEllipsis({
aria-hidden
data-slot="pagination-ellipsis"
className={cn(
"lib:flex lib:size-9 lib:items-center lib:justify-center lib:[&_svg:not([class*=size-])]:size-4",
"fui:flex fui:size-9 fui:items-center fui:justify-center fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
>
<MoreHorizontalIcon />
<span className="lib:sr-only">More pages</span>
<span className="fui:sr-only">More pages</span>
</span>
);
}
+5 -5
View File
@@ -29,12 +29,12 @@ function PopoverContent({
alignOffset={alignOffset}
side={side}
sideOffset={sideOffset}
className="lib:isolate lib:z-50"
className="fui:isolate fui:z-50"
>
<PopoverPrimitive.Popup
data-slot="popover-content"
className={cn(
"lib:z-50 lib:flex lib:w-72 lib:origin-(--transform-origin) lib:flex-col lib:gap-4 lib:rounded-2xl lib:bg-popover lib:p-4 lib:text-sm lib:text-popover-foreground lib:shadow-2xl lib:ring-1 lib:ring-foreground/5 lib:outline-hidden lib:duration-100 lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=inline-end]:slide-in-from-left-2 lib:data-[side=inline-start]:slide-in-from-right-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95",
"fui:z-50 fui:flex fui:w-72 fui:origin-(--transform-origin) fui:flex-col fui:gap-4 fui:rounded-2xl fui:bg-popover fui:p-4 fui:text-sm fui:text-popover-foreground fui:shadow-2xl fui:ring-1 fui:ring-foreground/5 fui:outline-hidden fui:duration-100 fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=inline-end]:slide-in-from-left-2 fui:data-[side=inline-start]:slide-in-from-right-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95",
className,
)}
{...props}
@@ -48,7 +48,7 @@ function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="popover-header"
className={cn("lib:flex lib:flex-col lib:gap-1 lib:text-sm", className)}
className={cn("fui:flex fui:flex-col fui:gap-1 fui:text-sm", className)}
{...props}
/>
);
@@ -58,7 +58,7 @@ function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
return (
<PopoverPrimitive.Title
data-slot="popover-title"
className={cn("lib:text-base lib:font-medium", className)}
className={cn("fui:text-base fui:font-medium", className)}
{...props}
/>
);
@@ -71,7 +71,7 @@ function PopoverDescription({
return (
<PopoverPrimitive.Description
data-slot="popover-description"
className={cn("lib:text-muted-foreground", className)}
className={cn("fui:text-muted-foreground", className)}
{...props}
/>
);
+5 -5
View File
@@ -11,7 +11,7 @@ function Progress({
<ProgressPrimitive.Root
value={value}
data-slot="progress"
className={cn("lib:flex lib:flex-wrap lib:gap-3", className)}
className={cn("fui:flex fui:flex-wrap fui:gap-3", className)}
{...props}
>
{children}
@@ -26,7 +26,7 @@ function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
return (
<ProgressPrimitive.Track
className={cn(
"lib:relative lib:flex lib:h-3 lib:w-full lib:items-center lib:overflow-x-hidden lib:rounded-4xl lib:bg-muted",
"fui:relative fui:flex fui:h-3 fui:w-full fui:items-center fui:overflow-x-hidden fui:rounded-4xl fui:bg-muted",
className,
)}
data-slot="progress-track"
@@ -42,7 +42,7 @@ function ProgressIndicator({
return (
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className={cn("lib:h-full lib:bg-primary lib:transition-all", className)}
className={cn("fui:h-full fui:bg-primary fui:transition-all", className)}
{...props}
/>
);
@@ -51,7 +51,7 @@ function ProgressIndicator({
function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
return (
<ProgressPrimitive.Label
className={cn("lib:text-sm lib:font-medium", className)}
className={cn("fui:text-sm fui:font-medium", className)}
data-slot="progress-label"
{...props}
/>
@@ -62,7 +62,7 @@ function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
return (
<ProgressPrimitive.Value
className={cn(
"lib:ml-auto lib:text-sm lib:text-muted-foreground lib:tabular-nums",
"fui:ml-auto fui:text-sm fui:text-muted-foreground fui:tabular-nums",
className,
)}
data-slot="progress-value"
+4 -4
View File
@@ -8,7 +8,7 @@ function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
return (
<RadioGroupPrimitive
data-slot="radio-group"
className={cn("lib:grid lib:w-full lib:gap-3", className)}
className={cn("fui:grid fui:w-full fui:gap-3", className)}
{...props}
/>
);
@@ -19,16 +19,16 @@ function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
<RadioPrimitive.Root
data-slot="radio-group-item"
className={cn(
"lib:group/radio-group-item lib:peer lib:relative lib:flex lib:aspect-square lib:size-4 lib:shrink-0 lib:rounded-full lib:border lib:border-input lib:outline-none lib:group-has-[:focus-visible]/field-label:ring-0 lib:group-has-[:focus-visible]/field-label:not-data-checked:border-input lib:after:absolute lib:after:-inset-x-3 lib:after:-inset-y-2 lib:focus-visible:border-ring lib:focus-visible:ring-3 lib:focus-visible:ring-ring/50 lib:disabled:cursor-not-allowed lib:disabled:opacity-50 lib:aria-invalid:border-destructive lib:aria-invalid:ring-3 lib:aria-invalid:ring-destructive/20 lib:aria-invalid:aria-checked:border-primary lib:dark:bg-input/30 lib:dark:aria-invalid:border-destructive/50 lib:dark:aria-invalid:ring-destructive/40 lib:data-checked:border-primary lib:data-checked:bg-primary lib:data-checked:text-primary-foreground lib:group-has-[:focus-visible]/field-label:data-checked:border-primary lib:dark:data-checked:bg-primary",
"fui:group/radio-group-item fui:peer fui:relative fui:flex fui:aspect-square fui:size-4 fui:shrink-0 fui:rounded-full fui:border fui:border-input fui:outline-none fui:group-has-[:focus-visible]/field-label:ring-0 fui:group-has-[:focus-visible]/field-label:not-data-checked:border-input fui:after:absolute fui:after:-inset-x-3 fui:after:-inset-y-2 fui:focus-visible:border-ring fui:focus-visible:ring-3 fui:focus-visible:ring-ring/50 fui:disabled:cursor-not-allowed fui:disabled:opacity-50 fui:aria-invalid:border-destructive fui:aria-invalid:ring-3 fui:aria-invalid:ring-destructive/20 fui:aria-invalid:aria-checked:border-primary fui:dark:bg-input/30 fui:dark:aria-invalid:border-destructive/50 fui:dark:aria-invalid:ring-destructive/40 fui:data-checked:border-primary fui:data-checked:bg-primary fui:data-checked:text-primary-foreground fui:group-has-[:focus-visible]/field-label:data-checked:border-primary fui:dark:data-checked:bg-primary",
className,
)}
{...props}
>
<RadioPrimitive.Indicator
data-slot="radio-group-indicator"
className="lib:flex lib:size-4 lib:items-center lib:justify-center"
className="fui:flex fui:size-4 fui:items-center fui:justify-center"
>
<span className="lib:absolute lib:top-1/2 lib:left-1/2 lib:size-2 lib:-translate-x-1/2 lib:-translate-y-1/2 lib:rounded-full lib:bg-primary-foreground" />
<span className="fui:absolute fui:top-1/2 fui:left-1/2 fui:size-2 fui:-translate-x-1/2 fui:-translate-y-1/2 fui:rounded-full fui:bg-primary-foreground" />
</RadioPrimitive.Indicator>
</RadioPrimitive.Root>
);
+3 -3
View File
@@ -9,7 +9,7 @@ function ResizablePanelGroup({
<ResizablePrimitive.Group
data-slot="resizable-panel-group"
className={cn(
"lib:flex lib:h-full lib:w-full lib:aria-[orientation=vertical]:flex-col",
"fui:flex fui:h-full fui:w-full fui:aria-[orientation=vertical]:flex-col",
className,
)}
{...props}
@@ -32,13 +32,13 @@ function ResizableHandle({
<ResizablePrimitive.Separator
data-slot="resizable-handle"
className={cn(
"lib:relative lib:flex lib:w-px lib:items-center lib:justify-center lib:bg-border lib:ring-offset-background lib:after:absolute lib:after:inset-y-0 lib:after:left-1/2 lib:after:w-1 lib:after:-translate-x-1/2 lib:focus-visible:ring-1 lib:focus-visible:ring-ring lib:focus-visible:outline-hidden lib:aria-[orientation=horizontal]:h-px lib:aria-[orientation=horizontal]:w-full lib:aria-[orientation=horizontal]:after:left-0 lib:aria-[orientation=horizontal]:after:h-1 lib:aria-[orientation=horizontal]:after:w-full lib:aria-[orientation=horizontal]:after:translate-x-0 lib:aria-[orientation=horizontal]:after:-translate-y-1/2 lib:[&[aria-orientation=horizontal]>div]:rotate-90",
"fui:relative fui:flex fui:w-px fui:items-center fui:justify-center fui:bg-border fui:ring-offset-background fui:after:absolute fui:after:inset-y-0 fui:after:left-1/2 fui:after:w-1 fui:after:-translate-x-1/2 fui:focus-visible:ring-1 fui:focus-visible:ring-ring fui:focus-visible:outline-hidden fui:aria-[orientation=horizontal]:h-px fui:aria-[orientation=horizontal]:w-full fui:aria-[orientation=horizontal]:after:left-0 fui:aria-[orientation=horizontal]:after:h-1 fui:aria-[orientation=horizontal]:after:w-full fui:aria-[orientation=horizontal]:after:translate-x-0 fui:aria-[orientation=horizontal]:after:-translate-y-1/2 fui:[&[aria-orientation=horizontal]>div]:rotate-90",
className,
)}
{...props}
>
{withHandle && (
<div className="lib:z-10 lib:flex lib:h-6 lib:w-1 lib:shrink-0 lib:rounded-lg lib:bg-border" />
<div className="fui:z-10 fui:flex fui:h-6 fui:w-1 fui:shrink-0 fui:rounded-lg fui:bg-border" />
)}
</ResizablePrimitive.Separator>
);
+4 -4
View File
@@ -9,12 +9,12 @@ function ScrollArea({
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("lib:relative", className)}
className={cn("fui:relative", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="lib:size-full lib:rounded-[inherit] lib:transition-[color,box-shadow] lib:outline-none lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:focus-visible:outline-1"
className="fui:size-full fui:rounded-[inherit] fui:transition-[color,box-shadow] fui:outline-none fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
@@ -35,14 +35,14 @@ function ScrollBar({
data-orientation={orientation}
orientation={orientation}
className={cn(
"lib:flex lib:touch-none lib:p-px lib:transition-colors lib:select-none lib:data-horizontal:h-2.5 lib:data-horizontal:flex-col lib:data-horizontal:border-t lib:data-horizontal:border-t-transparent lib:data-vertical:h-full lib:data-vertical:w-2.5 lib:data-vertical:border-l lib:data-vertical:border-l-transparent",
"fui:flex fui:touch-none fui:p-px fui:transition-colors fui:select-none fui:data-horizontal:h-2.5 fui:data-horizontal:flex-col fui:data-horizontal:border-t fui:data-horizontal:border-t-transparent fui:data-vertical:h-full fui:data-vertical:w-2.5 fui:data-vertical:border-l fui:data-vertical:border-l-transparent",
className,
)}
{...props}
>
<ScrollAreaPrimitive.Thumb
data-slot="scroll-area-thumb"
className="lib:relative lib:flex-1 lib:rounded-full lib:bg-border"
className="fui:relative fui:flex-1 fui:rounded-full fui:bg-border"
/>
</ScrollAreaPrimitive.Scrollbar>
);
+14 -14
View File
@@ -11,7 +11,7 @@ function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
return (
<SelectPrimitive.Group
data-slot="select-group"
className={cn("lib:scroll-my-1 lib:p-1", className)}
className={cn("fui:scroll-my-1 fui:p-1", className)}
{...props}
/>
);
@@ -21,7 +21,7 @@ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
return (
<SelectPrimitive.Value
data-slot="select-value"
className={cn("lib:flex lib:flex-1 lib:text-left", className)}
className={cn("fui:flex fui:flex-1 fui:text-left", className)}
{...props}
/>
);
@@ -40,7 +40,7 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={cn(
"lib:flex lib:w-fit lib:items-center lib:justify-between lib:gap-1.5 lib:rounded-4xl lib:border lib:border-input lib:bg-input/30 lib:px-3 lib:py-2 lib:text-sm lib:whitespace-nowrap lib:transition-colors lib:outline-none lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:disabled:cursor-not-allowed lib:disabled:opacity-50 lib:aria-invalid:border-destructive lib:aria-invalid:ring-[3px] lib:aria-invalid:ring-destructive/20 lib:data-placeholder:text-muted-foreground lib:data-[size=default]:h-9 lib:data-[size=sm]:h-8 lib:*:data-[slot=select-value]:line-clamp-1 lib:*:data-[slot=select-value]:flex lib:*:data-[slot=select-value]:items-center lib:*:data-[slot=select-value]:gap-1.5 lib:dark:hover:bg-input/50 lib:dark:aria-invalid:border-destructive/50 lib:dark:aria-invalid:ring-destructive/40 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:flex fui:w-fit fui:items-center fui:justify-between fui:gap-1.5 fui:rounded-4xl fui:border fui:border-input fui:bg-input/30 fui:px-3 fui:py-2 fui:text-sm fui:whitespace-nowrap fui:transition-colors fui:outline-none fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:disabled:cursor-not-allowed fui:disabled:opacity-50 fui:aria-invalid:border-destructive fui:aria-invalid:ring-[3px] fui:aria-invalid:ring-destructive/20 fui:data-placeholder:text-muted-foreground fui:data-[size=default]:h-9 fui:data-[size=sm]:h-8 fui:*:data-[slot=select-value]:line-clamp-1 fui:*:data-[slot=select-value]:flex fui:*:data-[slot=select-value]:items-center fui:*:data-[slot=select-value]:gap-1.5 fui:dark:hover:bg-input/50 fui:dark:aria-invalid:border-destructive/50 fui:dark:aria-invalid:ring-destructive/40 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
@@ -48,7 +48,7 @@ function SelectTrigger({
{children}
<SelectPrimitive.Icon
render={
<ChevronDownIcon className="lib:pointer-events-none lib:size-4 lib:text-muted-foreground" />
<ChevronDownIcon className="fui:pointer-events-none fui:size-4 fui:text-muted-foreground" />
}
/>
</SelectPrimitive.Trigger>
@@ -77,13 +77,13 @@ function SelectContent({
align={align}
alignOffset={alignOffset}
alignItemWithTrigger={alignItemWithTrigger}
className="lib:isolate lib:z-50"
className="fui:isolate fui:z-50"
>
<SelectPrimitive.Popup
data-slot="select-content"
data-align-trigger={alignItemWithTrigger}
className={cn(
"lib: lib: lib:relative lib:isolate lib:z-50 lib:max-h-(--available-height) lib:w-(--anchor-width) lib:min-w-36 lib:origin-(--transform-origin) lib:overflow-x-hidden lib:overflow-y-auto lib:rounded-2xl lib:bg-popover lib:text-popover-foreground lib:shadow-2xl lib:ring-1 lib:ring-foreground/5 lib:duration-100 lib:data-[align-trigger=true]:animate-none lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=inline-end]:slide-in-from-left-2 lib:data-[side=inline-start]:slide-in-from-right-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
"fui: fui: fui:relative fui:isolate fui:z-50 fui:max-h-(--available-height) fui:w-(--anchor-width) fui:min-w-36 fui:origin-(--transform-origin) fui:overflow-x-hidden fui:overflow-y-auto fui:rounded-2xl fui:bg-popover fui:text-popover-foreground fui:shadow-2xl fui:ring-1 fui:ring-foreground/5 fui:duration-100 fui:data-[align-trigger=true]:animate-none fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=inline-end]:slide-in-from-left-2 fui:data-[side=inline-start]:slide-in-from-right-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
className,
)}
{...props}
@@ -105,7 +105,7 @@ function SelectLabel({
<SelectPrimitive.GroupLabel
data-slot="select-label"
className={cn(
"lib:px-3 lib:py-2.5 lib:text-xs lib:text-muted-foreground",
"fui:px-3 fui:py-2.5 fui:text-xs fui:text-muted-foreground",
className,
)}
{...props}
@@ -122,20 +122,20 @@ function SelectItem({
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"lib:relative lib:flex lib:w-full lib:cursor-default lib:items-center lib:gap-2.5 lib:rounded-xl lib:py-2 lib:pr-8 lib:pl-3 lib:text-sm lib:outline-hidden lib:select-none lib:focus:bg-accent lib:focus:text-accent-foreground lib:not-data-[variant=destructive]:focus:**:text-accent-foreground lib:data-disabled:pointer-events-none lib:data-disabled:opacity-50 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4 lib:*:[span]:last:flex lib:*:[span]:last:items-center lib:*:[span]:last:gap-2",
"fui:relative fui:flex fui:w-full fui:cursor-default fui:items-center fui:gap-2.5 fui:rounded-xl fui:py-2 fui:pr-8 fui:pl-3 fui:text-sm fui:outline-hidden fui:select-none fui:focus:bg-accent fui:focus:text-accent-foreground fui:not-data-[variant=destructive]:focus:**:text-accent-foreground fui:data-disabled:pointer-events-none fui:data-disabled:opacity-50 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4 fui:*:[span]:last:flex fui:*:[span]:last:items-center fui:*:[span]:last:gap-2",
className,
)}
{...props}
>
<SelectPrimitive.ItemText className="lib:flex lib:flex-1 lib:shrink-0 lib:gap-2 lib:whitespace-nowrap">
<SelectPrimitive.ItemText className="fui:flex fui:flex-1 fui:shrink-0 fui:gap-2 fui:whitespace-nowrap">
{children}
</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator
render={
<span className="lib:pointer-events-none lib:absolute lib:right-2 lib:flex lib:size-4 lib:items-center lib:justify-center" />
<span className="fui:pointer-events-none fui:absolute fui:right-2 fui:flex fui:size-4 fui:items-center fui:justify-center" />
}
>
<CheckIcon className="lib:pointer-events-none" />
<CheckIcon className="fui:pointer-events-none" />
</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
);
@@ -149,7 +149,7 @@ function SelectSeparator({
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn(
"lib:pointer-events-none lib:-mx-1 lib:my-1 lib:h-px lib:bg-border/50",
"fui:pointer-events-none fui:-mx-1 fui:my-1 fui:h-px fui:bg-border/50",
className,
)}
{...props}
@@ -165,7 +165,7 @@ function SelectScrollUpButton({
<SelectPrimitive.ScrollUpArrow
data-slot="select-scroll-up-button"
className={cn(
"lib:top-0 lib:z-10 lib:flex lib:w-full lib:cursor-default lib:items-center lib:justify-center lib:bg-popover lib:py-1 lib:[&_svg:not([class*=size-])]:size-4",
"fui:top-0 fui:z-10 fui:flex fui:w-full fui:cursor-default fui:items-center fui:justify-center fui:bg-popover fui:py-1 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
@@ -183,7 +183,7 @@ function SelectScrollDownButton({
<SelectPrimitive.ScrollDownArrow
data-slot="select-scroll-down-button"
className={cn(
"lib:bottom-0 lib:z-10 lib:flex lib:w-full lib:cursor-default lib:items-center lib:justify-center lib:bg-popover lib:py-1 lib:[&_svg:not([class*=size-])]:size-4",
"fui:bottom-0 fui:z-10 fui:flex fui:w-full fui:cursor-default fui:items-center fui:justify-center fui:bg-popover fui:py-1 fui:[&_svg:not([class*=size-])]:size-4",
className,
)}
{...props}
+1 -1
View File
@@ -13,7 +13,7 @@ function Separator({
data-slot="separator"
orientation={orientation}
className={cn(
"lib:shrink-0 lib:bg-border lib:data-horizontal:h-px lib:data-horizontal:w-full lib:data-vertical:w-px lib:data-vertical:self-stretch",
"fui:shrink-0 fui:bg-border fui:data-horizontal:h-px fui:data-horizontal:w-full fui:data-vertical:w-px fui:data-vertical:self-stretch",
className,
)}
{...props}
+8 -8
View File
@@ -25,7 +25,7 @@ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
<SheetPrimitive.Backdrop
data-slot="sheet-overlay"
className={cn(
"lib:fixed lib:inset-0 lib:z-50 lib:bg-black/80 lib:transition-opacity lib:duration-150 lib:data-ending-style:opacity-0 lib:data-starting-style:opacity-0 lib:supports-backdrop-filter:backdrop-blur-xs",
"fui:fixed fui:inset-0 fui:z-50 fui:bg-black/80 fui:transition-opacity fui:duration-150 fui:data-ending-style:opacity-0 fui:data-starting-style:opacity-0 fui:supports-backdrop-filter:backdrop-blur-xs",
className,
)}
{...props}
@@ -50,7 +50,7 @@ function SheetContent({
data-slot="sheet-content"
data-side={side}
className={cn(
"lib:fixed lib:z-50 lib:flex lib:flex-col lib:bg-popover lib:bg-clip-padding lib:text-sm lib:text-popover-foreground lib:shadow-lg lib:transition lib:duration-200 lib:ease-in-out lib:data-ending-style:opacity-0 lib:data-starting-style:opacity-0 lib:data-[side=bottom]:inset-x-0 lib:data-[side=bottom]:bottom-0 lib:data-[side=bottom]:h-auto lib:data-[side=bottom]:border-t lib:data-[side=bottom]:data-ending-style:translate-y-[2.5rem] lib:data-[side=bottom]:data-starting-style:translate-y-[2.5rem] lib:data-[side=left]:inset-y-0 lib:data-[side=left]:left-0 lib:data-[side=left]:h-full lib:data-[side=left]:w-3/4 lib:data-[side=left]:border-r lib:data-[side=left]:data-ending-style:translate-x-[-2.5rem] lib:data-[side=left]:data-starting-style:translate-x-[-2.5rem] lib:data-[side=right]:inset-y-0 lib:data-[side=right]:right-0 lib:data-[side=right]:h-full lib:data-[side=right]:w-3/4 lib:data-[side=right]:border-l lib:data-[side=right]:data-ending-style:translate-x-[2.5rem] lib:data-[side=right]:data-starting-style:translate-x-[2.5rem] lib:data-[side=top]:inset-x-0 lib:data-[side=top]:top-0 lib:data-[side=top]:h-auto lib:data-[side=top]:border-b lib:data-[side=top]:data-ending-style:translate-y-[-2.5rem] lib:data-[side=top]:data-starting-style:translate-y-[-2.5rem] lib:data-[side=left]:sm:max-w-sm lib:data-[side=right]:sm:max-w-sm",
"fui:fixed fui:z-50 fui:flex fui:flex-col fui:bg-popover fui:bg-clip-padding fui:text-sm fui:text-popover-foreground fui:shadow-lg fui:transition fui:duration-200 fui:ease-in-out fui:data-ending-style:opacity-0 fui:data-starting-style:opacity-0 fui:data-[side=bottom]:inset-x-0 fui:data-[side=bottom]:bottom-0 fui:data-[side=bottom]:h-auto fui:data-[side=bottom]:border-t fui:data-[side=bottom]:data-ending-style:translate-y-[2.5rem] fui:data-[side=bottom]:data-starting-style:translate-y-[2.5rem] fui:data-[side=left]:inset-y-0 fui:data-[side=left]:left-0 fui:data-[side=left]:h-full fui:data-[side=left]:w-3/4 fui:data-[side=left]:border-r fui:data-[side=left]:data-ending-style:translate-x-[-2.5rem] fui:data-[side=left]:data-starting-style:translate-x-[-2.5rem] fui:data-[side=right]:inset-y-0 fui:data-[side=right]:right-0 fui:data-[side=right]:h-full fui:data-[side=right]:w-3/4 fui:data-[side=right]:border-l fui:data-[side=right]:data-ending-style:translate-x-[2.5rem] fui:data-[side=right]:data-starting-style:translate-x-[2.5rem] fui:data-[side=top]:inset-x-0 fui:data-[side=top]:top-0 fui:data-[side=top]:h-auto fui:data-[side=top]:border-b fui:data-[side=top]:data-ending-style:translate-y-[-2.5rem] fui:data-[side=top]:data-starting-style:translate-y-[-2.5rem] fui:data-[side=left]:sm:max-w-sm fui:data-[side=right]:sm:max-w-sm",
className,
)}
{...props}
@@ -62,13 +62,13 @@ function SheetContent({
render={
<Button
variant="ghost"
className="lib:absolute lib:top-4 lib:right-4"
className="fui:absolute fui:top-4 fui:right-4"
size="icon-sm"
/>
}
>
<XIcon />
<span className="lib:sr-only">Close</span>
<span className="fui:sr-only">Close</span>
</SheetPrimitive.Close>
)}
</SheetPrimitive.Popup>
@@ -80,7 +80,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("lib:flex lib:flex-col lib:gap-1.5 lib:p-6", className)}
className={cn("fui:flex fui:flex-col fui:gap-1.5 fui:p-6", className)}
{...props}
/>
);
@@ -91,7 +91,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="sheet-footer"
className={cn(
"lib:mt-auto lib:flex lib:flex-col lib:gap-2 lib:p-6",
"fui:mt-auto fui:flex fui:flex-col fui:gap-2 fui:p-6",
className,
)}
{...props}
@@ -104,7 +104,7 @@ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn(
"lib:font-heading lib:text-base lib:font-medium lib:text-foreground",
"fui:font-heading fui:text-base fui:font-medium fui:text-foreground",
className,
)}
{...props}
@@ -119,7 +119,7 @@ function SheetDescription({
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("lib:text-sm lib:text-muted-foreground", className)}
className={cn("fui:text-sm fui:text-muted-foreground", className)}
{...props}
/>
);
+48 -48
View File
@@ -133,7 +133,7 @@ function SidebarProvider({
} as React.CSSProperties
}
className={cn(
"lib:group/sidebar-wrapper lib:flex lib:min-h-svh lib:w-full lib:has-data-[variant=inset]:bg-sidebar",
"fui:group/sidebar-wrapper fui:flex fui:min-h-svh fui:w-full fui:has-data-[variant=inset]:bg-sidebar",
className,
)}
{...props}
@@ -164,7 +164,7 @@ function Sidebar({
<div
data-slot="sidebar"
className={cn(
"lib:flex lib:h-full lib:w-(--sidebar-width) lib:flex-col lib:bg-sidebar lib:text-sidebar-foreground",
"fui:flex fui:h-full fui:w-(--sidebar-width) fui:flex-col fui:bg-sidebar fui:text-sidebar-foreground",
className,
)}
{...props}
@@ -182,7 +182,7 @@ function Sidebar({
data-sidebar="sidebar"
data-slot="sidebar"
data-mobile="true"
className="lib:w-(--sidebar-width) lib:bg-sidebar lib:p-0 lib:text-sidebar-foreground lib:[&>button]:hidden"
className="fui:w-(--sidebar-width) fui:bg-sidebar fui:p-0 fui:text-sidebar-foreground fui:[&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
@@ -190,11 +190,11 @@ function Sidebar({
}
side={side}
>
<SheetHeader className="lib:sr-only">
<SheetHeader className="fui:sr-only">
<SheetTitle>Sidebar</SheetTitle>
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
</SheetHeader>
<div className="lib:flex lib:h-full lib:w-full lib:flex-col">
<div className="fui:flex fui:h-full fui:w-full fui:flex-col">
{children}
</div>
</SheetContent>
@@ -204,7 +204,7 @@ function Sidebar({
return (
<div
className="lib:group lib:peer lib:hidden lib:text-sidebar-foreground lib:md:block"
className="fui:group fui:peer fui:hidden fui:text-sidebar-foreground fui:md:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
@@ -215,23 +215,23 @@ function Sidebar({
<div
data-slot="sidebar-gap"
className={cn(
"lib:relative lib:w-(--sidebar-width) lib:bg-transparent lib:transition-[width] lib:duration-200 lib:ease-linear",
"lib:group-data-[collapsible=offcanvas]:w-0",
"lib:group-data-[side=right]:rotate-180",
"fui:relative fui:w-(--sidebar-width) fui:bg-transparent fui:transition-[width] fui:duration-200 fui:ease-linear",
"fui:group-data-[collapsible=offcanvas]:w-0",
"fui:group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
? "lib:group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
: "lib:group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
? "fui:group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
: "fui:group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
)}
/>
<div
data-slot="sidebar-container"
data-side={side}
className={cn(
"lib:fixed lib:inset-y-0 lib:z-10 lib:hidden lib:h-svh lib:w-(--sidebar-width) lib:transition-[left,right,width] lib:duration-200 lib:ease-linear lib:data-[side=left]:left-0 lib:data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] lib:data-[side=right]:right-0 lib:data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] lib:md:flex",
"fui:fixed fui:inset-y-0 fui:z-10 fui:hidden fui:h-svh fui:w-(--sidebar-width) fui:transition-[left,right,width] fui:duration-200 fui:ease-linear fui:data-[side=left]:left-0 fui:data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] fui:data-[side=right]:right-0 fui:data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] fui:md:flex",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "lib:p-2 lib:group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
: "lib:group-data-[collapsible=icon]:w-(--sidebar-width-icon) lib:group-data-[side=left]:border-r lib:group-data-[side=right]:border-l",
? "fui:p-2 fui:group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
: "fui:group-data-[collapsible=icon]:w-(--sidebar-width-icon) fui:group-data-[side=left]:border-r fui:group-data-[side=right]:border-l",
className,
)}
{...props}
@@ -239,7 +239,7 @@ function Sidebar({
<div
data-sidebar="sidebar"
data-slot="sidebar-inner"
className="lib:flex lib:size-full lib:flex-col lib:bg-sidebar lib:group-data-[variant=floating]:rounded-lg lib:group-data-[variant=floating]:shadow-sm lib:group-data-[variant=floating]:ring-1 lib:group-data-[variant=floating]:ring-sidebar-border"
className="fui:flex fui:size-full fui:flex-col fui:bg-sidebar fui:group-data-[variant=floating]:rounded-lg fui:group-data-[variant=floating]:shadow-sm fui:group-data-[variant=floating]:ring-1 fui:group-data-[variant=floating]:ring-sidebar-border"
>
{children}
</div>
@@ -269,7 +269,7 @@ function SidebarTrigger({
{...props}
>
<PanelLeftIcon />
<span className="lib:sr-only">Toggle Sidebar</span>
<span className="fui:sr-only">Toggle Sidebar</span>
</Button>
);
}
@@ -286,12 +286,12 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"lib:absolute lib:inset-y-0 lib:z-20 lib:hidden lib:w-4 lib:transition-all lib:ease-linear lib:group-data-[side=left]:-right-4 lib:group-data-[side=right]:left-0 lib:after:absolute lib:after:inset-y-0 lib:after:start-1/2 lib:after:w-[2px] lib:hover:after:bg-sidebar-border lib:sm:flex lib:ltr:-translate-x-1/2 lib:rtl:-translate-x-1/2",
"lib:in-data-[side=left]:cursor-w-resize lib:in-data-[side=right]:cursor-e-resize",
"lib:[[data-side=left][data-state=collapsed]_&]:cursor-e-resize lib:[[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"lib:group-data-[collapsible=offcanvas]:translate-x-0 lib:group-data-[collapsible=offcanvas]:after:left-full lib:hover:group-data-[collapsible=offcanvas]:bg-sidebar",
"lib:[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"lib:[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
"fui:absolute fui:inset-y-0 fui:z-20 fui:hidden fui:w-4 fui:transition-all fui:ease-linear fui:group-data-[side=left]:-right-4 fui:group-data-[side=right]:left-0 fui:after:absolute fui:after:inset-y-0 fui:after:start-1/2 fui:after:w-[2px] fui:hover:after:bg-sidebar-border fui:sm:flex fui:ltr:-translate-x-1/2 fui:rtl:-translate-x-1/2",
"fui:in-data-[side=left]:cursor-w-resize fui:in-data-[side=right]:cursor-e-resize",
"fui:[[data-side=left][data-state=collapsed]_&]:cursor-e-resize fui:[[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"fui:group-data-[collapsible=offcanvas]:translate-x-0 fui:group-data-[collapsible=offcanvas]:after:left-full fui:hover:group-data-[collapsible=offcanvas]:bg-sidebar",
"fui:[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"fui:[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className,
)}
{...props}
@@ -304,7 +304,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
<main
data-slot="sidebar-inset"
className={cn(
"lib:relative lib:flex lib:w-full lib:flex-1 lib:flex-col lib:bg-background lib:md:peer-data-[variant=inset]:m-2 lib:md:peer-data-[variant=inset]:ml-0 lib:md:peer-data-[variant=inset]:rounded-xl lib:md:peer-data-[variant=inset]:shadow-sm lib:md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
"fui:relative fui:flex fui:w-full fui:flex-1 fui:flex-col fui:bg-background fui:md:peer-data-[variant=inset]:m-2 fui:md:peer-data-[variant=inset]:ml-0 fui:md:peer-data-[variant=inset]:rounded-xl fui:md:peer-data-[variant=inset]:shadow-sm fui:md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className,
)}
{...props}
@@ -321,7 +321,7 @@ function SidebarInput({
data-slot="sidebar-input"
data-sidebar="input"
className={cn(
"lib:h-8 lib:w-full lib:bg-background lib:shadow-none",
"fui:h-8 fui:w-full fui:bg-background fui:shadow-none",
className,
)}
{...props}
@@ -335,7 +335,7 @@ function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sidebar-header"
data-sidebar="header"
className={cn(
"lib:flex lib:flex-col lib:gap-2 lib:p-2 lib:[--radius:var(--radius-xl)]",
"fui:flex fui:flex-col fui:gap-2 fui:p-2 fui:[--radius:var(--radius-xl)]",
className,
)}
{...props}
@@ -348,7 +348,7 @@ function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="sidebar-footer"
data-sidebar="footer"
className={cn("lib:flex lib:flex-col lib:gap-2 lib:p-2", className)}
className={cn("fui:flex fui:flex-col fui:gap-2 fui:p-2", className)}
{...props}
/>
);
@@ -362,7 +362,7 @@ function SidebarSeparator({
<Separator
data-slot="sidebar-separator"
data-sidebar="separator"
className={cn("lib:mx-2 lib:w-auto lib:bg-sidebar-border", className)}
className={cn("fui:mx-2 fui:w-auto fui:bg-sidebar-border", className)}
{...props}
/>
);
@@ -374,7 +374,7 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sidebar-content"
data-sidebar="content"
className={cn(
"lib:no-scrollbar lib:flex lib:min-h-0 lib:flex-1 lib:flex-col lib:gap-2 lib:overflow-auto lib:[--radius:var(--radius-xl)] lib:group-data-[collapsible=icon]:overflow-hidden",
"fui:no-scrollbar fui:flex fui:min-h-0 fui:flex-1 fui:flex-col fui:gap-2 fui:overflow-auto fui:[--radius:var(--radius-xl)] fui:group-data-[collapsible=icon]:overflow-hidden",
className,
)}
{...props}
@@ -388,7 +388,7 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
data-slot="sidebar-group"
data-sidebar="group"
className={cn(
"lib:relative lib:flex lib:w-full lib:min-w-0 lib:flex-col lib:p-2",
"fui:relative fui:flex fui:w-full fui:min-w-0 fui:flex-col fui:p-2",
className,
)}
{...props}
@@ -406,7 +406,7 @@ function SidebarGroupLabel({
props: mergeProps<"div">(
{
className: cn(
"flex h-8 shrink-0 items-center rounded-md px-3 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"fui:flex fui:h-8 fui:shrink-0 fui:items-center fui:rounded-md fui:px-3 fui:text-xs fui:font-medium fui:text-sidebar-foreground/70 fui:ring-sidebar-ring fui:outline-hidden fui:transition-[margin,opacity] fui:duration-200 fui:ease-linear fui:group-data-[collapsible=icon]:-mt-8 fui:group-data-[collapsible=icon]:opacity-0 fui:focus-visible:ring-2 fui:[&>svg]:size-4 fui:[&>svg]:shrink-0",
className,
),
},
@@ -430,7 +430,7 @@ function SidebarGroupAction({
props: mergeProps<"button">(
{
className: cn(
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
"fui:absolute fui:top-3.5 fui:right-3 fui:flex fui:aspect-square fui:w-5 fui:items-center fui:justify-center fui:rounded-md fui:p-0 fui:text-sidebar-foreground fui:ring-sidebar-ring fui:outline-hidden fui:transition-transform fui:group-data-[collapsible=icon]:hidden fui:after:absolute fui:after:-inset-2 fui:hover:bg-sidebar-accent fui:hover:text-sidebar-accent-foreground fui:focus-visible:ring-2 fui:md:after:hidden fui:[&>svg]:size-4 fui:[&>svg]:shrink-0",
className,
),
},
@@ -452,7 +452,7 @@ function SidebarGroupContent({
<div
data-slot="sidebar-group-content"
data-sidebar="group-content"
className={cn("lib:w-full lib:text-sm", className)}
className={cn("fui:w-full fui:text-sm", className)}
{...props}
/>
);
@@ -464,7 +464,7 @@ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
data-slot="sidebar-menu"
data-sidebar="menu"
className={cn(
"lib:flex lib:w-full lib:min-w-0 lib:flex-col lib:gap-1",
"fui:flex fui:w-full fui:min-w-0 fui:flex-col fui:gap-1",
className,
)}
{...props}
@@ -477,26 +477,26 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
<li
data-slot="sidebar-menu-item"
data-sidebar="menu-item"
className={cn("lib:group/menu-item lib:relative", className)}
className={cn("fui:group/menu-item fui:relative", className)}
{...props}
/>
);
}
const sidebarMenuButtonVariants = cva(
"lib:peer/menu-button lib:group/menu-button lib:flex lib:w-full lib:items-center lib:gap-2 lib:overflow-hidden lib:rounded-lg lib:px-3 lib:py-2 lib:text-left lib:text-sm lib:ring-sidebar-ring lib:outline-hidden lib:transition-[width,height,padding] lib:group-has-data-[sidebar=menu-action]/menu-item:pr-8 lib:group-data-[collapsible=icon]:size-8! lib:group-data-[collapsible=icon]:p-2! lib:hover:bg-sidebar-accent lib:hover:text-sidebar-accent-foreground lib:focus-visible:ring-2 lib:active:bg-sidebar-accent lib:active:text-sidebar-accent-foreground lib:disabled:pointer-events-none lib:disabled:opacity-50 lib:aria-disabled:pointer-events-none lib:aria-disabled:opacity-50 lib:data-open:hover:bg-sidebar-accent lib:data-open:hover:text-sidebar-accent-foreground lib:data-active:bg-sidebar-accent lib:data-active:font-medium lib:data-active:text-sidebar-accent-foreground lib:[&_svg]:size-4 lib:[&_svg]:shrink-0 lib:[&>span:last-child]:truncate",
"fui:peer/menu-button fui:group/menu-button fui:flex fui:w-full fui:items-center fui:gap-2 fui:overflow-hidden fui:rounded-lg fui:px-3 fui:py-2 fui:text-left fui:text-sm fui:ring-sidebar-ring fui:outline-hidden fui:transition-[width,height,padding] fui:group-has-data-[sidebar=menu-action]/menu-item:pr-8 fui:group-data-[collapsible=icon]:size-8! fui:group-data-[collapsible=icon]:p-2! fui:hover:bg-sidebar-accent fui:hover:text-sidebar-accent-foreground fui:focus-visible:ring-2 fui:active:bg-sidebar-accent fui:active:text-sidebar-accent-foreground fui:disabled:pointer-events-none fui:disabled:opacity-50 fui:aria-disabled:pointer-events-none fui:aria-disabled:opacity-50 fui:data-open:hover:bg-sidebar-accent fui:data-open:hover:text-sidebar-accent-foreground fui:data-active:bg-sidebar-accent fui:data-active:font-medium fui:data-active:text-sidebar-accent-foreground fui:[&_svg]:size-4 fui:[&_svg]:shrink-0 fui:[&>span:last-child]:truncate",
{
variants: {
variant: {
default:
"lib:hover:bg-sidebar-accent lib:hover:text-sidebar-accent-foreground",
"fui:hover:bg-sidebar-accent fui:hover:text-sidebar-accent-foreground",
outline:
"lib:bg-background lib:shadow-[0_0_0_1px_var(--sidebar-border)] lib:hover:bg-sidebar-accent lib:hover:text-sidebar-accent-foreground lib:hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
"fui:bg-background fui:shadow-[0_0_0_1px_var(--sidebar-border)] fui:hover:bg-sidebar-accent fui:hover:text-sidebar-accent-foreground fui:hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
},
size: {
default: "lib:h-9 lib:text-sm",
sm: "lib:h-8 lib:text-xs",
lg: "lib:h-14 lib:px-3 lib:text-sm lib:group-data-[collapsible=icon]:p-0!",
default: "fui:h-9 fui:text-sm",
sm: "fui:h-8 fui:text-xs",
lg: "fui:h-14 fui:px-3 fui:text-sm fui:group-data-[collapsible=icon]:p-0!",
},
},
defaultVariants: {
@@ -574,7 +574,7 @@ function SidebarMenuAction({
props: mergeProps<"button">(
{
className: cn(
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-2 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0",
"fui:absolute fui:top-1.5 fui:right-1 fui:flex fui:aspect-square fui:w-5 fui:items-center fui:justify-center fui:rounded-md fui:p-0 fui:text-sidebar-foreground fui:ring-sidebar-ring fui:outline-hidden fui:transition-transform fui:group-data-[collapsible=icon]:hidden fui:peer-hover/menu-button:text-sidebar-accent-foreground fui:peer-data-[size=default]/menu-button:top-2 fui:peer-data-[size=lg]/menu-button:top-2.5 fui:peer-data-[size=sm]/menu-button:top-1 fui:after:absolute fui:after:-inset-2 fui:hover:bg-sidebar-accent fui:hover:text-sidebar-accent-foreground fui:focus-visible:ring-2 fui:md:after:hidden fui:[&>svg]:size-4 fui:[&>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,
@@ -599,7 +599,7 @@ function SidebarMenuBadge({
data-slot="sidebar-menu-badge"
data-sidebar="menu-badge"
className={cn(
"lib:pointer-events-none lib:absolute lib:right-1 lib:flex lib:h-5 lib:min-w-5 lib:items-center lib:justify-center lib:rounded-md lib:px-1 lib:text-xs lib:font-medium lib:text-sidebar-foreground lib:tabular-nums lib:select-none lib:group-data-[collapsible=icon]:hidden lib:peer-hover/menu-button:text-sidebar-accent-foreground lib:peer-data-[size=default]/menu-button:top-1.5 lib:peer-data-[size=lg]/menu-button:top-2.5 lib:peer-data-[size=sm]/menu-button:top-1 lib:peer-data-active/menu-button:text-sidebar-accent-foreground",
"fui:pointer-events-none fui:absolute fui:right-1 fui:flex fui:h-5 fui:min-w-5 fui:items-center fui:justify-center fui:rounded-md fui:px-1 fui:text-xs fui:font-medium fui:text-sidebar-foreground fui:tabular-nums fui:select-none fui:group-data-[collapsible=icon]:hidden fui:peer-hover/menu-button:text-sidebar-accent-foreground fui:peer-data-[size=default]/menu-button:top-1.5 fui:peer-data-[size=lg]/menu-button:top-2.5 fui:peer-data-[size=sm]/menu-button:top-1 fui:peer-data-active/menu-button:text-sidebar-accent-foreground",
className,
)}
{...props}
@@ -624,19 +624,19 @@ function SidebarMenuSkeleton({
data-slot="sidebar-menu-skeleton"
data-sidebar="menu-skeleton"
className={cn(
"lib:flex lib:h-8 lib:items-center lib:gap-2 lib:rounded-md lib:px-2",
"fui:flex fui:h-8 fui:items-center fui:gap-2 fui:rounded-md fui:px-2",
className,
)}
{...props}
>
{showIcon && (
<Skeleton
className="lib:size-4 lib:rounded-md"
className="fui:size-4 fui:rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton
className="lib:h-4 lib:max-w-(--skeleton-width) lib:flex-1"
className="fui:h-4 fui:max-w-(--skeleton-width) fui:flex-1"
data-sidebar="menu-skeleton-text"
style={
{
@@ -654,7 +654,7 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
data-slot="sidebar-menu-sub"
data-sidebar="menu-sub"
className={cn(
"lib:mx-3.5 lib:flex lib:min-w-0 lib:translate-x-px lib:flex-col lib:gap-1 lib:border-l lib:border-sidebar-border lib:px-2.5 lib:py-0.5 lib:group-data-[collapsible=icon]:hidden",
"fui:mx-3.5 fui:flex fui:min-w-0 fui:translate-x-px fui:flex-col fui:gap-1 fui:border-l fui:border-sidebar-border fui:px-2.5 fui:py-0.5 fui:group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
@@ -670,7 +670,7 @@ function SidebarMenuSubItem({
<li
data-slot="sidebar-menu-sub-item"
data-sidebar="menu-sub-item"
className={cn("lib:group/menu-sub-item lib:relative", className)}
className={cn("fui:group/menu-sub-item fui:relative", className)}
{...props}
/>
);
@@ -692,7 +692,7 @@ function SidebarMenuSubButton({
props: mergeProps<"a">(
{
className: cn(
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"fui:flex fui:h-7 fui:min-w-0 fui:-translate-x-px fui:items-center fui:gap-2 fui:overflow-hidden fui:rounded-md fui:px-2 fui:text-sidebar-foreground fui:ring-sidebar-ring fui:outline-hidden fui:group-data-[collapsible=icon]:hidden fui:hover:bg-sidebar-accent fui:hover:text-sidebar-accent-foreground fui:focus-visible:ring-2 fui:active:bg-sidebar-accent fui:active:text-sidebar-accent-foreground fui:disabled:pointer-events-none fui:disabled:opacity-50 fui:aria-disabled:pointer-events-none fui:aria-disabled:opacity-50 fui:data-[size=md]:text-sm fui:data-[size=sm]:text-xs fui:data-active:bg-sidebar-accent fui:data-active:text-sidebar-accent-foreground fui:[&>span:last-child]:truncate fui:[&>svg]:size-4 fui:[&>svg]:shrink-0 fui:[&>svg]:text-sidebar-accent-foreground",
className,
),
},
+1 -1
View File
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn("lib:animate-pulse lib:rounded-xl lib:bg-muted", className)}
className={cn("fui:animate-pulse fui:rounded-xl fui:bg-muted", className)}
{...props}
/>
);
+5 -5
View File
@@ -18,7 +18,7 @@ function Slider({
return (
<SliderPrimitive.Root
className={cn(
"lib:data-horizontal:w-full lib:data-vertical:h-full",
"fui:data-horizontal:w-full fui:data-vertical:h-full",
className,
)}
data-slot="slider"
@@ -29,21 +29,21 @@ function Slider({
thumbAlignment="edge"
{...props}
>
<SliderPrimitive.Control className="lib:relative lib:flex lib:w-full lib:touch-none lib:items-center lib:select-none lib:data-disabled:opacity-50 lib:data-vertical:h-full lib:data-vertical:min-h-40 lib:data-vertical:w-auto lib:data-vertical:flex-col">
<SliderPrimitive.Control className="fui:relative fui:flex fui:w-full fui:touch-none fui:items-center fui:select-none fui:data-disabled:opacity-50 fui:data-vertical:h-full fui:data-vertical:min-h-40 fui:data-vertical:w-auto fui:data-vertical:flex-col">
<SliderPrimitive.Track
data-slot="slider-track"
className="lib:relative lib:grow lib:overflow-hidden lib:rounded-4xl lib:bg-muted lib:select-none lib:data-horizontal:h-3 lib:data-horizontal:w-full lib:data-vertical:h-full lib:data-vertical:w-3"
className="fui:relative fui:grow fui:overflow-hidden fui:rounded-4xl fui:bg-muted fui:select-none fui:data-horizontal:h-3 fui:data-horizontal:w-full fui:data-vertical:h-full fui:data-vertical:w-3"
>
<SliderPrimitive.Indicator
data-slot="slider-range"
className="lib:bg-primary lib:select-none lib:data-horizontal:h-full lib:data-vertical:w-full"
className="fui:bg-primary fui:select-none fui:data-horizontal:h-full fui:data-vertical:w-full"
/>
</SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => (
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
className="lib:block lib:size-4 lib:shrink-0 lib:rounded-4xl lib:border lib:border-primary lib:bg-white lib:shadow-sm lib:ring-ring/50 lib:transition-colors lib:select-none lib:hover:ring-4 lib:focus-visible:ring-4 lib:focus-visible:outline-hidden lib:disabled:pointer-events-none lib:disabled:opacity-50"
className="fui:block fui:size-4 fui:shrink-0 fui:rounded-4xl fui:border fui:border-primary fui:bg-white fui:shadow-sm fui:ring-ring/50 fui:transition-colors fui:select-none fui:hover:ring-4 fui:focus-visible:ring-4 fui:focus-visible:outline-hidden fui:disabled:pointer-events-none fui:disabled:opacity-50"
/>
))}
</SliderPrimitive.Control>
+6 -6
View File
@@ -16,13 +16,13 @@ const Toaster = ({ ...props }: ToasterProps) => {
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="lib:toaster lib:group"
className="fui:toaster fui:group"
icons={{
success: <CircleCheckIcon className="lib:size-4" />,
info: <InfoIcon className="lib:size-4" />,
warning: <TriangleAlertIcon className="lib:size-4" />,
error: <OctagonXIcon className="lib:size-4" />,
loading: <Loader2Icon className="lib:size-4 lib:animate-spin" />,
success: <CircleCheckIcon className="fui:size-4" />,
info: <InfoIcon className="fui:size-4" />,
warning: <TriangleAlertIcon className="fui:size-4" />,
error: <OctagonXIcon className="fui:size-4" />,
loading: <Loader2Icon className="fui:size-4 fui:animate-spin" />,
}}
style={
{
+1 -1
View File
@@ -7,7 +7,7 @@ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
data-slot="spinner"
role="status"
aria-label="Loading"
className={cn("lib:size-4 lib:animate-spin", className)}
className={cn("fui:size-4 fui:animate-spin", className)}
{...props}
/>
);
+2 -2
View File
@@ -13,14 +13,14 @@ function Switch({
data-slot="switch"
data-size={size}
className={cn(
"lib:peer lib:group/switch lib:relative lib:inline-flex lib:shrink-0 lib:items-center lib:rounded-full lib:border lib:border-transparent lib:transition-all lib:outline-none lib:group-has-[:focus-visible]/field-label:border-transparent lib:group-has-[:focus-visible]/field-label:ring-0 lib:after:absolute lib:after:-inset-x-3 lib:after:-inset-y-2 lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:aria-invalid:border-destructive lib:aria-invalid:ring-[3px] lib:aria-invalid:ring-destructive/20 lib:data-[size=default]:h-[18.4px] lib:data-[size=default]:w-[32px] lib:data-[size=sm]:h-[14px] lib:data-[size=sm]:w-[24px] lib:dark:aria-invalid:border-destructive/50 lib:dark:aria-invalid:ring-destructive/40 lib:data-checked:bg-primary lib:data-unchecked:bg-input lib:dark:data-unchecked:bg-input/80 lib:data-disabled:cursor-not-allowed lib:data-disabled:opacity-50",
"fui:peer fui:group/switch fui:relative fui:inline-flex fui:shrink-0 fui:items-center fui:rounded-full fui:border fui:border-transparent fui:transition-all fui:outline-none fui:group-has-[:focus-visible]/field-label:border-transparent fui:group-has-[:focus-visible]/field-label:ring-0 fui:after:absolute fui:after:-inset-x-3 fui:after:-inset-y-2 fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:aria-invalid:border-destructive fui:aria-invalid:ring-[3px] fui:aria-invalid:ring-destructive/20 fui:data-[size=default]:h-[18.4px] fui:data-[size=default]:w-[32px] fui:data-[size=sm]:h-[14px] fui:data-[size=sm]:w-[24px] fui:dark:aria-invalid:border-destructive/50 fui:dark:aria-invalid:ring-destructive/40 fui:data-checked:bg-primary fui:data-unchecked:bg-input fui:dark:data-unchecked:bg-input/80 fui:data-disabled:cursor-not-allowed fui:data-disabled:opacity-50",
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className="lib:pointer-events-none lib:block lib:rounded-full lib:bg-background lib:ring-0 lib:transition-transform lib:group-data-[size=default]/switch:size-4 lib:group-data-[size=sm]/switch:size-3 lib:group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] lib:group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] lib:dark:data-checked:bg-primary-foreground lib:group-data-[size=default]/switch:data-unchecked:translate-x-0 lib:group-data-[size=sm]/switch:data-unchecked:translate-x-0 lib:dark:data-unchecked:bg-foreground"
className="fui:pointer-events-none fui:block fui:rounded-full fui:bg-background fui:ring-0 fui:transition-transform fui:group-data-[size=default]/switch:size-4 fui:group-data-[size=sm]/switch:size-3 fui:group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] fui:group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] fui:dark:data-checked:bg-primary-foreground fui:group-data-[size=default]/switch:data-unchecked:translate-x-0 fui:group-data-[size=sm]/switch:data-unchecked:translate-x-0 fui:dark:data-unchecked:bg-foreground"
/>
</SwitchPrimitive.Root>
);
+152
View File
@@ -0,0 +1,152 @@
import { cn } from "cn";
import { CheckIcon, MinusIcon, XIcon } from "lucide-react";
import type * as React from "react";
import { Badge } from "@/components/badge";
/**
* Cell renderers for `DataTable` and `Table`.
*
* A listing is scanned far more often than it is read line by line, so the
* values that repeat down a column — flags, kinds, states — are better as a
* glyph or a tag than as a word. Anything carried by shape or colour alone
* also carries text for assistive tech.
*/
type BadgeVariant = React.ComponentProps<typeof Badge>["variant"];
/**
* A yes/no flag.
*
* A column of "Yes"/"No" reads as a wall of text and gives the eye nothing to
* lock onto; a tick against a muted cross does. `undefined` means the row has
* no value, which is not the same as a definite "no", so it gets a dash.
*
* `label` names what the flag means, because a tick on its own tells a screen
* reader nothing.
*/
function BooleanCell({
value,
label,
}: {
value: boolean | undefined | null;
label: string;
}) {
if (value === undefined || value === null) {
return (
<>
<MinusIcon
className="fui:size-4 fui:text-muted-foreground/60"
aria-hidden
/>
<span className="fui:sr-only">{label}: unknown</span>
</>
);
}
return (
<>
{value ? (
<CheckIcon className="fui:size-4 fui:text-foreground" aria-hidden />
) : (
<XIcon
className="fui:size-4 fui:text-muted-foreground/60"
aria-hidden
/>
)}
<span className="fui:sr-only">
{label}: {value ? "yes" : "no"}
</span>
</>
);
}
/**
* A single tag.
*
* `outline` by default: a listing can carry several tag columns, and filled
* badges in each turn the table into a colour chart. Spend colour only where
* it means something, by passing a `variant`.
*/
function TagCell({
children,
variant = "outline",
className,
}: {
children: React.ReactNode;
variant?: BadgeVariant;
className?: string;
}) {
if (children === null || children === undefined || children === "") {
return <EmptyCell />;
}
return (
<Badge variant={variant} className={cn("fui:font-normal", className)}>
{children}
</Badge>
);
}
/** Several tags in one cell, wrapping rather than widening the column. */
function TagListCell({
values,
variant = "outline",
empty,
}: {
values: ReadonlyArray<string>;
variant?: BadgeVariant;
empty?: React.ReactNode;
}) {
if (values.length === 0) {
return empty !== undefined ? <>{empty}</> : <EmptyCell />;
}
return (
<div className="fui:flex fui:flex-wrap fui:gap-1">
{values.map((value) => (
<TagCell key={value} variant={variant}>
{value}
</TagCell>
))}
</div>
);
}
/**
* An identifier: monospaced so digits line up down the column.
*
* Presentational only. A cell that navigates wraps this in the router's own
* link component — taking a route as a string here would mean casting away
* whatever type safety that router provides.
*/
function IdCell({
children,
className,
...props
}: React.ComponentProps<"span">) {
return (
<span className={cn("fui:font-mono fui:text-xs", className)} {...props}>
{children}
</span>
);
}
/**
* Affordance for a cell whose whole content navigates somewhere, to put on the
* router link that wraps it.
*/
const cellLinkClassName = "fui:text-link fui:hover:underline";
/** A value the row does not have, rendered so the column keeps its rhythm. */
function EmptyCell() {
return <span className="fui:text-muted-foreground">—</span>;
}
export {
BooleanCell,
cellLinkClassName,
EmptyCell,
IdCell,
TagCell,
TagListCell,
};
+9 -9
View File
@@ -7,11 +7,11 @@ function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
<div
data-slot="table-container"
className="lib:relative lib:w-full lib:overflow-x-auto"
className="fui:relative fui:w-full fui:overflow-x-auto"
>
<table
data-slot="table"
className={cn("lib:w-full lib:caption-bottom lib:text-sm", className)}
className={cn("fui:w-full fui:caption-bottom fui:text-sm", className)}
{...props}
/>
</div>
@@ -22,7 +22,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return (
<thead
data-slot="table-header"
className={cn("lib:[&_tr]:border-b", className)}
className={cn("fui:[&_tr]:border-b", className)}
{...props}
/>
);
@@ -32,7 +32,7 @@ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return (
<tbody
data-slot="table-body"
className={cn("lib:[&_tr:last-child]:border-0", className)}
className={cn("fui:[&_tr:last-child]:border-0", className)}
{...props}
/>
);
@@ -43,7 +43,7 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
<tfoot
data-slot="table-footer"
className={cn(
"lib:border-t lib:bg-muted/50 lib:font-medium lib:[&>tr]:last:border-b-0",
"fui:border-t fui:bg-muted/50 fui:font-medium fui:[&>tr]:last:border-b-0",
className,
)}
{...props}
@@ -56,7 +56,7 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
<tr
data-slot="table-row"
className={cn(
"lib:border-b lib:transition-colors lib:hover:bg-muted/50 lib:has-aria-expanded:bg-muted/50 lib:data-[state=selected]:bg-muted",
"fui:border-b fui:transition-colors fui:hover:bg-muted/50 fui:has-aria-expanded:bg-muted/50 fui:data-[state=selected]:bg-muted",
className,
)}
{...props}
@@ -69,7 +69,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
<th
data-slot="table-head"
className={cn(
"lib:h-12 lib:px-3 lib:text-left lib:align-middle lib:font-medium lib:whitespace-nowrap lib:text-foreground lib:[&:has([role=checkbox])]:pr-0",
"fui:h-12 fui:px-3 fui:text-left fui:align-middle fui:font-medium fui:whitespace-nowrap fui:text-foreground fui:[&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
@@ -82,7 +82,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
<td
data-slot="table-cell"
className={cn(
"lib:p-3 lib:align-middle lib:whitespace-nowrap lib:[&:has([role=checkbox])]:pr-0",
"fui:p-3 fui:align-middle fui:whitespace-nowrap fui:[&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
@@ -98,7 +98,7 @@ function TableCaption({
<caption
data-slot="table-caption"
className={cn(
"lib:mt-4 lib:text-sm lib:text-muted-foreground",
"fui:mt-4 fui:text-sm fui:text-muted-foreground",
className,
)}
{...props}
+9 -9
View File
@@ -12,7 +12,7 @@ function Tabs({
data-slot="tabs"
data-orientation={orientation}
className={cn(
"lib:group/tabs lib:flex lib:gap-2 lib:data-horizontal:flex-col",
"fui:group/tabs fui:flex fui:gap-2 fui:data-horizontal:flex-col",
className,
)}
{...props}
@@ -21,12 +21,12 @@ function Tabs({
}
const tabsListVariants = cva(
"lib:group/tabs-list lib:inline-flex lib:w-fit lib:items-center lib:justify-center lib:rounded-4xl lib:p-[3px] lib:text-muted-foreground lib:group-data-horizontal/tabs:h-9 lib:group-data-vertical/tabs:h-fit lib:group-data-vertical/tabs:flex-col lib:group-data-vertical/tabs:rounded-2xl lib:data-[variant=line]:rounded-none",
"fui:group/tabs-list fui:inline-flex fui:w-fit fui:items-center fui:justify-center fui:rounded-4xl fui:p-[3px] fui:text-muted-foreground fui:group-data-horizontal/tabs:h-9 fui:group-data-vertical/tabs:h-fit fui:group-data-vertical/tabs:flex-col fui:group-data-vertical/tabs:rounded-2xl fui:data-[variant=line]:rounded-none",
{
variants: {
variant: {
default: "lib:bg-muted",
line: "lib:gap-1 lib:bg-transparent",
default: "fui:bg-muted",
line: "fui:gap-1 fui:bg-transparent",
},
},
defaultVariants: {
@@ -55,10 +55,10 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
<TabsPrimitive.Tab
data-slot="tabs-trigger"
className={cn(
"lib:relative lib:inline-flex lib:h-[calc(100%-1px)] lib:flex-1 lib:items-center lib:justify-center lib:gap-1.5 lib:rounded-xl lib:border lib:border-transparent lib:px-2 lib:py-1 lib:text-sm lib:font-medium lib:whitespace-nowrap lib:text-foreground/60 lib:transition-all lib:group-data-vertical/tabs:w-full lib:group-data-vertical/tabs:justify-start lib:group-data-vertical/tabs:px-2.5 lib:group-data-vertical/tabs:py-1.5 lib:hover:text-foreground lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:focus-visible:outline-1 lib:focus-visible:outline-ring lib:disabled:pointer-events-none lib:disabled:opacity-50 lib:has-data-[icon=inline-end]:pr-1.5 lib:has-data-[icon=inline-start]:pl-1.5 lib:aria-disabled:pointer-events-none lib:aria-disabled:opacity-50 lib:dark:text-muted-foreground lib:dark:hover:text-foreground lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"lib:group-data-[variant=line]/tabs-list:bg-transparent lib:group-data-[variant=line]/tabs-list:data-active:bg-transparent lib:dark:group-data-[variant=line]/tabs-list:data-active:border-transparent lib:dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"lib:data-active:bg-background lib:data-active:text-foreground lib:dark:data-active:border-input lib:dark:data-active:bg-input/30 lib:dark:data-active:text-foreground",
"lib:after:absolute lib:after:bg-foreground lib:after:opacity-0 lib:after:transition-opacity lib:group-data-horizontal/tabs:after:inset-x-0 lib:group-data-horizontal/tabs:after:bottom-[-5px] lib:group-data-horizontal/tabs:after:h-0.5 lib:group-data-vertical/tabs:after:inset-y-0 lib:group-data-vertical/tabs:after:-right-1 lib:group-data-vertical/tabs:after:w-0.5 lib:group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
"fui:relative fui:inline-flex fui:h-[calc(100%-1px)] fui:flex-1 fui:items-center fui:justify-center fui:gap-1.5 fui:rounded-xl fui:border fui:border-transparent fui:px-2 fui:py-1 fui:text-sm fui:font-medium fui:whitespace-nowrap fui:text-foreground/60 fui:transition-all fui:group-data-vertical/tabs:w-full fui:group-data-vertical/tabs:justify-start fui:group-data-vertical/tabs:px-2.5 fui:group-data-vertical/tabs:py-1.5 fui:hover:text-foreground fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:focus-visible:outline-1 fui:focus-visible:outline-ring fui:disabled:pointer-events-none fui:disabled:opacity-50 fui:has-data-[icon=inline-end]:pr-1.5 fui:has-data-[icon=inline-start]:pl-1.5 fui:aria-disabled:pointer-events-none fui:aria-disabled:opacity-50 fui:dark:text-muted-foreground fui:dark:hover:text-foreground fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
"fui:group-data-[variant=line]/tabs-list:bg-transparent fui:group-data-[variant=line]/tabs-list:data-active:bg-transparent fui:dark:group-data-[variant=line]/tabs-list:data-active:border-transparent fui:dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"fui:data-active:bg-background fui:data-active:text-foreground fui:dark:data-active:border-input fui:dark:data-active:bg-input/30 fui:dark:data-active:text-foreground",
"fui:after:absolute fui:after:bg-foreground fui:after:opacity-0 fui:after:transition-opacity fui:group-data-horizontal/tabs:after:inset-x-0 fui:group-data-horizontal/tabs:after:bottom-[-5px] fui:group-data-horizontal/tabs:after:h-0.5 fui:group-data-vertical/tabs:after:inset-y-0 fui:group-data-vertical/tabs:after:-right-1 fui:group-data-vertical/tabs:after:w-0.5 fui:group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className,
)}
{...props}
@@ -70,7 +70,7 @@ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
return (
<TabsPrimitive.Panel
data-slot="tabs-content"
className={cn("lib:flex-1 lib:text-sm lib:outline-none", className)}
className={cn("fui:flex-1 fui:text-sm fui:outline-none", className)}
{...props}
/>
);
+1 -1
View File
@@ -6,7 +6,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea
data-slot="textarea"
className={cn(
"lib:flex lib:field-sizing-content lib:min-h-16 lib:w-full lib:resize-none lib:rounded-xl lib:border lib:border-input lib:bg-input/30 lib:px-3 lib:py-3 lib:text-base lib:transition-colors lib:outline-none lib:placeholder:text-muted-foreground lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:disabled:cursor-not-allowed lib:disabled:opacity-50 lib:aria-invalid:border-destructive lib:aria-invalid:ring-[3px] lib:aria-invalid:ring-destructive/20 lib:md:text-sm lib:dark:aria-invalid:border-destructive/50 lib:dark:aria-invalid:ring-destructive/40",
"fui:flex fui:field-sizing-content fui:min-h-16 fui:w-full fui:resize-none fui:rounded-xl fui:border fui:border-input fui:bg-input/30 fui:px-3 fui:py-3 fui:text-base fui:transition-colors fui:outline-none fui:placeholder:text-muted-foreground fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:disabled:cursor-not-allowed fui:disabled:opacity-50 fui:aria-invalid:border-destructive fui:aria-invalid:ring-[3px] fui:aria-invalid:ring-destructive/20 fui:md:text-sm fui:dark:aria-invalid:border-destructive/50 fui:dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
+2 -2
View File
@@ -40,7 +40,7 @@ function ToggleGroup({
data-orientation={orientation}
style={{ "--gap": spacing } as React.CSSProperties}
className={cn(
"lib:group/toggle-group lib:flex lib:w-fit lib:flex-row lib:items-center lib:gap-[--spacing(var(--gap))] lib:data-[spacing=0]:data-[variant=outline]:rounded-4xl lib:data-vertical:flex-col lib:data-vertical:items-stretch",
"fui:group/toggle-group fui:flex fui:w-fit fui:flex-row fui:items-center fui:gap-[--spacing(var(--gap))] fui:data-[spacing=0]:data-[variant=outline]:rounded-4xl fui:data-vertical:flex-col fui:data-vertical:items-stretch",
className,
)}
{...props}
@@ -70,7 +70,7 @@ function ToggleGroupItem({
data-size={context.size || size}
data-spacing={context.spacing}
className={cn(
"lib:shrink-0 lib:group-data-[spacing=0]/toggle-group:rounded-none lib:group-data-[spacing=0]/toggle-group:px-3 lib:group-data-[spacing=0]/toggle-group:shadow-none lib:focus:z-10 lib:focus-visible:z-10 lib:group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-2.5 lib:group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-2.5 lib:group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-3xl lib:group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-3xl lib:group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-3xl lib:group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-3xl lib:data-[state=on]:bg-muted lib:group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 lib:group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 lib:group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l lib:group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
"fui:shrink-0 fui:group-data-[spacing=0]/toggle-group:rounded-none fui:group-data-[spacing=0]/toggle-group:px-3 fui:group-data-[spacing=0]/toggle-group:shadow-none fui:focus:z-10 fui:focus-visible:z-10 fui:group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-2.5 fui:group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-2.5 fui:group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-3xl fui:group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-3xl fui:group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-3xl fui:group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-3xl fui:data-[state=on]:bg-muted fui:group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 fui:group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 fui:group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l fui:group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
+6 -6
View File
@@ -5,19 +5,19 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "cn";
const toggleVariants = cva(
"lib:group/toggle lib:inline-flex lib:items-center lib:justify-center lib:gap-1 lib:rounded-4xl lib:text-sm lib:font-medium lib:whitespace-nowrap lib:transition-colors lib:outline-none lib:hover:bg-muted lib:hover:text-foreground lib:focus-visible:border-ring lib:focus-visible:ring-[3px] lib:focus-visible:ring-ring/50 lib:disabled:pointer-events-none lib:disabled:opacity-50 lib:aria-invalid:border-destructive lib:aria-invalid:ring-destructive/20 lib:aria-pressed:bg-muted lib:dark:aria-invalid:ring-destructive/40 lib:[&_svg]:pointer-events-none lib:[&_svg]:shrink-0 lib:[&_svg:not([class*=size-])]:size-4",
"fui:group/toggle fui:inline-flex fui:items-center fui:justify-center fui:gap-1 fui:rounded-4xl fui:text-sm fui:font-medium fui:whitespace-nowrap fui:transition-colors fui:outline-none fui:hover:bg-muted fui:hover:text-foreground fui:focus-visible:border-ring fui:focus-visible:ring-[3px] fui:focus-visible:ring-ring/50 fui:disabled:pointer-events-none fui:disabled:opacity-50 fui:aria-invalid:border-destructive fui:aria-invalid:ring-destructive/20 fui:aria-pressed:bg-muted fui:dark:aria-invalid:ring-destructive/40 fui:[&_svg]:pointer-events-none fui:[&_svg]:shrink-0 fui:[&_svg:not([class*=size-])]:size-4",
{
variants: {
variant: {
default: "lib:bg-transparent",
default: "fui:bg-transparent",
outline:
"lib:border lib:border-input lib:bg-transparent lib:hover:bg-muted",
"fui:border fui:border-input fui:bg-transparent fui:hover:bg-muted",
},
size: {
default:
"lib:h-9 lib:min-w-9 lib:px-3 lib:has-data-[icon=inline-end]:pr-2.5 lib:has-data-[icon=inline-start]:pl-2.5",
sm: "lib:h-8 lib:min-w-8 lib:px-3 lib:has-data-[icon=inline-end]:pr-2 lib:has-data-[icon=inline-start]:pl-2",
lg: "lib:h-10 lib:min-w-10 lib:px-4 lib:has-data-[icon=inline-end]:pr-3 lib:has-data-[icon=inline-start]:pl-3",
"fui:h-9 fui:min-w-9 fui:px-3 fui:has-data-[icon=inline-end]:pr-2.5 fui:has-data-[icon=inline-start]:pl-2.5",
sm: "fui:h-8 fui:min-w-8 fui:px-3 fui:has-data-[icon=inline-end]:pr-2 fui:has-data-[icon=inline-start]:pl-2",
lg: "fui:h-10 fui:min-w-10 fui:px-4 fui:has-data-[icon=inline-end]:pr-3 fui:has-data-[icon=inline-start]:pl-3",
},
},
defaultVariants: {
+3 -3
View File
@@ -42,18 +42,18 @@ function TooltipContent({
alignOffset={alignOffset}
side={side}
sideOffset={sideOffset}
className="lib:isolate lib:z-50"
className="fui:isolate fui:z-50"
>
<TooltipPrimitive.Popup
data-slot="tooltip-content"
className={cn(
"lib:z-50 lib:inline-flex lib:w-fit lib:max-w-xs lib:origin-(--transform-origin) lib:items-center lib:gap-1.5 lib:rounded-2xl lib:bg-foreground lib:px-3 lib:py-1.5 lib:text-xs lib:text-background lib:has-data-[slot=kbd]:pr-1.5 lib:data-[side=bottom]:slide-in-from-top-2 lib:data-[side=inline-end]:slide-in-from-left-2 lib:data-[side=inline-start]:slide-in-from-right-2 lib:data-[side=left]:slide-in-from-right-2 lib:data-[side=right]:slide-in-from-left-2 lib:data-[side=top]:slide-in-from-bottom-2 lib:**:data-[slot=kbd]:relative lib:**:data-[slot=kbd]:isolate lib:**:data-[slot=kbd]:z-50 lib:**:data-[slot=kbd]:rounded-4xl lib:data-[state=delayed-open]:animate-in lib:data-[state=delayed-open]:fade-in-0 lib:data-[state=delayed-open]:zoom-in-95 lib:data-open:animate-in lib:data-open:fade-in-0 lib:data-open:zoom-in-95 lib:data-closed:animate-out lib:data-closed:fade-out-0 lib:data-closed:zoom-out-95",
"fui:z-50 fui:inline-flex fui:w-fit fui:max-w-xs fui:origin-(--transform-origin) fui:items-center fui:gap-1.5 fui:rounded-2xl fui:bg-foreground fui:px-3 fui:py-1.5 fui:text-xs fui:text-background fui:has-data-[slot=kbd]:pr-1.5 fui:data-[side=bottom]:slide-in-from-top-2 fui:data-[side=inline-end]:slide-in-from-left-2 fui:data-[side=inline-start]:slide-in-from-right-2 fui:data-[side=left]:slide-in-from-right-2 fui:data-[side=right]:slide-in-from-left-2 fui:data-[side=top]:slide-in-from-bottom-2 fui:**:data-[slot=kbd]:relative fui:**:data-[slot=kbd]:isolate fui:**:data-[slot=kbd]:z-50 fui:**:data-[slot=kbd]:rounded-4xl fui:data-[state=delayed-open]:animate-in fui:data-[state=delayed-open]:fade-in-0 fui:data-[state=delayed-open]:zoom-in-95 fui:data-open:animate-in fui:data-open:fade-in-0 fui:data-open:zoom-in-95 fui:data-closed:animate-out fui:data-closed:fade-out-0 fui:data-closed:zoom-out-95",
className,
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="lib:z-50 lib:size-2.5 lib:translate-y-[calc(-50%-2px)] lib:rotate-45 lib:rounded-[2px] lib:bg-foreground lib:fill-foreground lib:data-[side=bottom]:top-1 lib:data-[side=inline-end]:top-1/2! lib:data-[side=inline-end]:-left-1 lib:data-[side=inline-end]:translate-x-[1.5px] lib:data-[side=inline-end]:-translate-y-1/2 lib:data-[side=inline-start]:top-1/2! lib:data-[side=inline-start]:-right-1 lib:data-[side=inline-start]:translate-x-[-1.5px] lib:data-[side=inline-start]:-translate-y-1/2 lib:data-[side=left]:top-1/2! lib:data-[side=left]:-right-1 lib:data-[side=left]:translate-x-[-1.5px] lib:data-[side=left]:-translate-y-1/2 lib:data-[side=right]:top-1/2! lib:data-[side=right]:-left-1 lib:data-[side=right]:translate-x-[1.5px] lib:data-[side=right]:-translate-y-1/2 lib:data-[side=top]:-bottom-2.5" />
<TooltipPrimitive.Arrow className="fui:z-50 fui:size-2.5 fui:translate-y-[calc(-50%-2px)] fui:rotate-45 fui:rounded-[2px] fui:bg-foreground fui:fill-foreground fui:data-[side=bottom]:top-1 fui:data-[side=inline-end]:top-1/2! fui:data-[side=inline-end]:-left-1 fui:data-[side=inline-end]:translate-x-[1.5px] fui:data-[side=inline-end]:-translate-y-1/2 fui:data-[side=inline-start]:top-1/2! fui:data-[side=inline-start]:-right-1 fui:data-[side=inline-start]:translate-x-[-1.5px] fui:data-[side=inline-start]:-translate-y-1/2 fui:data-[side=left]:top-1/2! fui:data-[side=left]:-right-1 fui:data-[side=left]:translate-x-[-1.5px] fui:data-[side=left]:-translate-y-1/2 fui:data-[side=right]:top-1/2! fui:data-[side=right]:-left-1 fui:data-[side=right]:translate-x-[1.5px] fui:data-[side=right]:-translate-y-1/2 fui:data-[side=top]:-bottom-2.5" />
</TooltipPrimitive.Popup>
</TooltipPrimitive.Positioner>
</TooltipPrimitive.Portal>
+1 -1
View File
@@ -18,7 +18,7 @@ function Prose({
render,
props: mergeProps<"div">(
{
className: cn("lib:prose", className),
className: cn("fui:prose", className),
} as React.ComponentProps<"div">,
props,
),
+35
View File
@@ -0,0 +1,35 @@
import { useCallback, useEffect, useRef } from "react";
/**
* Defer `callback` until `delay` ms have passed without another call.
*
* Filters that apply as they are touched cost a request per keystroke without
* this. Selects and checkboxes change one value at a time and need no delay;
* text input does.
*
* The latest callback is held in a ref, so the returned function is stable and
* safe to hand to a memoised child without restarting the timer on every
* render.
*/
export function useDebouncedCallback<Args extends unknown[]>(
callback: (...args: Args) => void,
delay = 300,
): (...args: Args) => void {
const latest = useRef(callback);
const timer = useRef<ReturnType<typeof setTimeout>>(undefined);
useEffect(() => {
latest.current = callback;
}, [callback]);
// A pending call must not fire after the caller has gone.
useEffect(() => () => clearTimeout(timer.current), []);
return useCallback(
(...args: Args) => {
clearTimeout(timer.current);
timer.current = setTimeout(() => latest.current(...args), delay);
},
[delay],
);
}
+4 -3
View File
@@ -32,6 +32,7 @@
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-link: var(--link);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
@@ -64,12 +65,12 @@
@layer base {
* {
@apply lib:border-border lib:outline-ring/50;
@apply fui:border-border fui:outline-ring/50;
}
body {
@apply lib:bg-background lib:text-foreground;
@apply fui:bg-background fui:text-foreground;
}
html {
@apply lib:font-sans;
@apply fui:font-sans;
}
}
+109 -133
View File
@@ -195,10 +195,6 @@ import {
TooltipTrigger,
} from "../components/tooltip";
/* -------------------------------------------------------------------------- */
/* data */
/* -------------------------------------------------------------------------- */
const revenueByMonth = [
{ month: "Jan", revenue: 18600, expenses: 11400 },
{ month: "Feb", revenue: 30500, expenses: 14200 },
@@ -457,10 +453,6 @@ const stats = [
},
];
/* -------------------------------------------------------------------------- */
/* sidebar */
/* -------------------------------------------------------------------------- */
function AppSidebar() {
return (
<Sidebar collapsible="icon" variant="inset">
@@ -471,22 +463,22 @@ function AppSidebar() {
<DropdownMenuTrigger
render={
<SidebarMenuButton size="lg" tooltip="Switch workspace">
<div className="lib:flex lib:aspect-square lib:size-8 lib:items-center lib:justify-center lib:rounded-lg lib:bg-sidebar-primary lib:text-sidebar-primary-foreground">
<Building2 className="lib:size-4" />
<div className="fui:flex fui:aspect-square fui:size-8 fui:items-center fui:justify-center fui:rounded-lg fui:bg-sidebar-primary fui:text-sidebar-primary-foreground">
<Building2 className="fui:size-4" />
</div>
<div className="lib:grid lib:flex-1 lib:text-left lib:text-sm lib:leading-tight">
<span className="lib:truncate lib:font-medium">
<div className="fui:grid fui:flex-1 fui:text-left fui:text-sm fui:leading-tight">
<span className="fui:truncate fui:font-medium">
Foomo Inc.
</span>
<span className="lib:truncate lib:text-xs lib:text-sidebar-foreground/70">
<span className="fui:truncate fui:text-xs fui:text-sidebar-foreground/70">
Enterprise
</span>
</div>
<ChevronsUpDown className="lib:ml-auto lib:size-4" />
<ChevronsUpDown className="fui:ml-auto fui:size-4" />
</SidebarMenuButton>
}
/>
<DropdownMenuContent align="start" className="lib:w-56">
<DropdownMenuContent align="start" className="fui:w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>Workspaces</DropdownMenuLabel>
<DropdownMenuItem>
@@ -530,14 +522,14 @@ function AppSidebar() {
</SidebarMenuButton>
</SidebarMenuItem>
<Collapsible defaultOpen className="lib:group/collapsible">
<Collapsible defaultOpen className="fui:group/collapsible">
<SidebarMenuItem>
<CollapsibleTrigger
render={
<SidebarMenuButton tooltip="Projects">
<Package />
<span>Projects</span>
<ChevronRight className="lib:ml-auto lib:size-4 lib:transition-transform lib:duration-200 lib:group-data-open/collapsible:rotate-90" />
<ChevronRight className="fui:ml-auto fui:size-4 fui:transition-transform fui:duration-200 fui:group-data-open/collapsible:rotate-90" />
</SidebarMenuButton>
}
/>
@@ -620,27 +612,27 @@ function AppSidebar() {
<DropdownMenuTrigger
render={
<SidebarMenuButton size="lg" tooltip="Account">
<Avatar className="lib:size-8 lib:rounded-lg">
<AvatarFallback className="lib:rounded-lg">
<Avatar className="fui:size-8 fui:rounded-lg">
<AvatarFallback className="fui:rounded-lg">
LB
</AvatarFallback>
</Avatar>
<div className="lib:grid lib:flex-1 lib:text-left lib:text-sm lib:leading-tight">
<span className="lib:truncate lib:font-medium">
<div className="fui:grid fui:flex-1 fui:text-left fui:text-sm fui:leading-tight">
<span className="fui:truncate fui:font-medium">
Lena Brandt
</span>
<span className="lib:truncate lib:text-xs lib:text-sidebar-foreground/70">
<span className="fui:truncate fui:text-xs fui:text-sidebar-foreground/70">
lena@foomo.org
</span>
</div>
<ChevronsUpDown className="lib:ml-auto lib:size-4" />
<ChevronsUpDown className="fui:ml-auto fui:size-4" />
</SidebarMenuButton>
}
/>
<DropdownMenuContent
side="top"
align="start"
className="lib:w-56"
className="fui:w-56"
>
<DropdownMenuGroup>
<DropdownMenuLabel>My account</DropdownMenuLabel>
@@ -678,20 +670,16 @@ function AppSidebar() {
);
}
/* -------------------------------------------------------------------------- */
/* header */
/* -------------------------------------------------------------------------- */
function DashboardHeader({ onOpenCommand }: { onOpenCommand: () => void }) {
return (
<header className="lib:sticky lib:top-0 lib:z-10 lib:flex lib:h-16 lib:shrink-0 lib:items-center lib:gap-2 lib:border-b lib:bg-background/80 lib:px-4 lib:backdrop-blur">
<SidebarTrigger className="lib:-ml-1" />
<header className="fui:sticky fui:top-0 fui:z-10 fui:flex fui:h-16 fui:shrink-0 fui:items-center fui:gap-2 fui:border-b fui:bg-background/80 fui:px-4 fui:backdrop-blur">
<SidebarTrigger className="fui:-ml-1" />
<Separator
orientation="vertical"
className="lib:mr-2 lib:h-4 lib:self-center!"
className="fui:mr-2 fui:h-4 fui:self-center!"
/>
<Breadcrumb className="lib:hidden lib:md:block">
<Breadcrumb className="fui:hidden fui:md:block">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">Foomo Inc.</BreadcrumbLink>
@@ -703,15 +691,15 @@ function DashboardHeader({ onOpenCommand }: { onOpenCommand: () => void }) {
</BreadcrumbList>
</Breadcrumb>
<div className="lib:ml-auto lib:flex lib:items-center lib:gap-2">
<div className="fui:ml-auto fui:flex fui:items-center fui:gap-2">
<Button
variant="outline"
onClick={onOpenCommand}
className="lib:hidden lib:w-64 lib:justify-start lib:font-normal lib:text-muted-foreground lib:lg:inline-flex"
className="fui:hidden fui:w-64 fui:justify-start fui:font-normal fui:text-muted-foreground fui:lg:inline-flex"
>
<Search data-icon="inline-start" />
<span>Search everything…</span>
<KbdGroup className="lib:ml-auto">
<KbdGroup className="fui:ml-auto">
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
</KbdGroup>
@@ -724,10 +712,10 @@ function DashboardHeader({ onOpenCommand }: { onOpenCommand: () => void }) {
variant="ghost"
size="icon"
onClick={onOpenCommand}
className="lib:lg:hidden"
className="fui:lg:hidden"
>
<CommandIcon />
<span className="lib:sr-only">Open command palette</span>
<span className="fui:sr-only">Open command palette</span>
</Button>
}
/>
@@ -738,20 +726,20 @@ function DashboardHeader({ onOpenCommand }: { onOpenCommand: () => void }) {
<Separator
orientation="vertical"
className="lib:h-4 lib:self-center!"
className="fui:h-4 fui:self-center!"
/>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="lib:rounded-full">
<Avatar className="lib:size-7">
<Button variant="ghost" size="icon" className="fui:rounded-full">
<Avatar className="fui:size-7">
<AvatarFallback>LB</AvatarFallback>
</Avatar>
</Button>
}
/>
<DropdownMenuContent align="end" className="lib:w-56">
<DropdownMenuContent align="end" className="fui:w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>lena@foomo.org</DropdownMenuLabel>
</DropdownMenuGroup>
@@ -807,16 +795,16 @@ function NotificationsMenu() {
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="lib:relative">
<Button variant="ghost" size="icon" className="fui:relative">
<Bell />
<span className="lib:absolute lib:top-1.5 lib:right-1.5 lib:size-2 lib:rounded-full lib:bg-destructive" />
<span className="lib:sr-only">Notifications</span>
<span className="fui:absolute fui:top-1.5 fui:right-1.5 fui:size-2 fui:rounded-full fui:bg-destructive" />
<span className="fui:sr-only">Notifications</span>
</Button>
}
/>
<DropdownMenuContent align="end" className="lib:w-80 lib:p-0">
<div className="lib:flex lib:items-center lib:justify-between lib:px-3 lib:py-2">
<span className="lib:text-sm lib:font-medium">Notifications</span>
<DropdownMenuContent align="end" className="fui:w-80 fui:p-0">
<div className="fui:flex fui:items-center fui:justify-between fui:px-3 fui:py-2">
<span className="fui:text-sm fui:font-medium">Notifications</span>
<Badge variant="secondary">3 new</Badge>
</div>
<Separator />
@@ -831,7 +819,7 @@ function NotificationsMenu() {
<ItemDescription>{notification.body}</ItemDescription>
</ItemContent>
<ItemActions>
<span className="lib:text-xs lib:text-muted-foreground">
<span className="fui:text-xs fui:text-muted-foreground">
{notification.time}
</span>
</ItemActions>
@@ -843,24 +831,20 @@ function NotificationsMenu() {
);
}
/* -------------------------------------------------------------------------- */
/* sections */
/* -------------------------------------------------------------------------- */
function StatCards() {
return (
<div className="lib:grid lib:gap-4 lib:sm:grid-cols-2 lib:xl:grid-cols-4">
<div className="fui:grid fui:gap-4 fui:sm:grid-cols-2 fui:xl:grid-cols-4">
{stats.map((stat) => (
<Card key={stat.label} size="sm">
<CardHeader>
<CardDescription>{stat.label}</CardDescription>
<CardAction>
<stat.icon className="lib:size-4 lib:text-muted-foreground" />
<stat.icon className="fui:size-4 fui:text-muted-foreground" />
</CardAction>
</CardHeader>
<CardContent className="lib:flex lib:flex-col lib:gap-3">
<div className="lib:flex lib:items-center lib:gap-2">
<span className="lib:font-heading lib:text-2xl lib:font-medium lib:tabular-nums">
<CardContent className="fui:flex fui:flex-col fui:gap-3">
<div className="fui:flex fui:items-center fui:gap-2">
<span className="fui:font-heading fui:text-2xl fui:font-medium fui:tabular-nums">
{stat.value}
</span>
<Badge variant="secondary">
@@ -872,8 +856,8 @@ function StatCards() {
{stat.delta}
</Badge>
</div>
<Progress value={stat.progress} className="lib:h-1.5" />
<p className="lib:text-xs lib:text-muted-foreground">{stat.hint}</p>
<Progress value={stat.progress} className="fui:h-1.5" />
<p className="fui:text-xs fui:text-muted-foreground">{stat.hint}</p>
</CardContent>
</Card>
))}
@@ -883,13 +867,13 @@ function StatCards() {
function RevenueChartCard() {
return (
<Card className="lib:xl:col-span-4">
<Card className="fui:xl:col-span-4">
<CardHeader>
<CardTitle>Revenue vs. expenses</CardTitle>
<CardDescription>Rolling nine months, EUR</CardDescription>
<CardAction>
<Select defaultValue="9m">
<SelectTrigger className="lib:w-36" size="sm">
<SelectTrigger className="fui:w-36" size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -905,7 +889,7 @@ function RevenueChartCard() {
<CardContent>
<ChartContainer
config={revenueChartConfig}
className="lib:aspect-auto lib:h-[260px] lib:w-full"
className="fui:aspect-auto fui:h-[260px] fui:w-full"
>
<AreaChart data={revenueByMonth} margin={{ left: 4, right: 4 }}>
<defs>
@@ -975,20 +959,20 @@ function RevenueChartCard() {
function ActivityCard() {
return (
<Card className="lib:xl:col-span-3">
<Card className="fui:xl:col-span-3">
<CardHeader>
<CardTitle>Recent activity</CardTitle>
<CardDescription>Last 24 hours across all workspaces</CardDescription>
</CardHeader>
<CardContent className="lib:px-0">
<ScrollArea className="lib:h-[260px]">
<ItemGroup className="lib:px-4">
<CardContent className="fui:px-0">
<ScrollArea className="fui:h-[260px]">
<ItemGroup className="fui:px-4">
{recentActivity.map((entry, index) => (
<React.Fragment key={entry.name}>
{index > 0 && <ItemSeparator />}
<Item size="sm" className="lib:px-0">
<Item size="sm" className="fui:px-0">
<ItemMedia>
<Avatar className="lib:size-8">
<Avatar className="fui:size-8">
<AvatarFallback>{entry.initials}</AvatarFallback>
</Avatar>
</ItemMedia>
@@ -996,11 +980,11 @@ function ActivityCard() {
<ItemTitle>{entry.name}</ItemTitle>
<ItemDescription>{entry.action}</ItemDescription>
</ItemContent>
<ItemActions className="lib:flex-col lib:items-end lib:gap-0.5">
<span className="lib:text-sm lib:font-medium lib:tabular-nums">
<ItemActions className="fui:flex-col fui:items-end fui:gap-0.5">
<span className="fui:text-sm fui:font-medium fui:tabular-nums">
{entry.amount}
</span>
<span className="lib:text-xs lib:text-muted-foreground">
<span className="fui:text-xs fui:text-muted-foreground">
{entry.time}
</span>
</ItemActions>
@@ -1011,7 +995,7 @@ function ActivityCard() {
</ScrollArea>
</CardContent>
<CardFooter>
<Button variant="outline" size="sm" className="lib:w-full">
<Button variant="outline" size="sm" className="fui:w-full">
View all activity
</Button>
</CardFooter>
@@ -1027,7 +1011,7 @@ const orderColumns = [
header: ({ column: col }) => (
<DataTableColumnHeader column={col} title="Invoice" />
),
cell: (info) => <span className="lib:font-medium">{info.getValue()}</span>,
cell: (info) => <span className="fui:font-medium">{info.getValue()}</span>,
sortFn: "text",
}),
column.accessor("customer", {
@@ -1035,15 +1019,15 @@ const orderColumns = [
<DataTableColumnHeader column={col} title="Customer" />
),
cell: ({ row }) => (
<div className="lib:flex lib:items-center lib:gap-2">
<Avatar className="lib:size-6">
<AvatarFallback className="lib:text-[10px]">
<div className="fui:flex fui:items-center fui:gap-2">
<Avatar className="fui:size-6">
<AvatarFallback className="fui:text-[10px]">
{initials(row.original.customer)}
</AvatarFallback>
</Avatar>
<div className="lib:leading-tight">
<div className="fui:leading-tight">
<div>{row.original.customer}</div>
<div className="lib:text-xs lib:text-muted-foreground">
<div className="fui:text-xs fui:text-muted-foreground">
{row.original.email}
</div>
</div>
@@ -1065,7 +1049,7 @@ const orderColumns = [
cell: (info) => (
<Badge
variant={statusVariant[info.getValue()]}
className="lib:capitalize"
className="fui:capitalize"
>
{info.getValue()}
</Badge>
@@ -1080,7 +1064,7 @@ const orderColumns = [
<DataTableColumnHeader column={col} title="Date" />
),
cell: (info) => (
<span className="lib:whitespace-nowrap lib:text-muted-foreground">
<span className="fui:whitespace-nowrap fui:text-muted-foreground">
{shortDate.format(info.getValue())}
</span>
),
@@ -1091,7 +1075,7 @@ const orderColumns = [
<DataTableColumnHeader column={col} title="Amount" align="end" />
),
cell: (info) => (
<div className="lib:text-right lib:tabular-nums">
<div className="fui:text-right fui:tabular-nums">
{currency.format(info.getValue())}
</div>
),
@@ -1105,11 +1089,11 @@ const orderColumns = [
render={
<Button variant="ghost" size="icon-sm">
<Ellipsis />
<span className="lib:sr-only">Actions for {row.original.id}</span>
<span className="fui:sr-only">Actions for {row.original.id}</span>
</Button>
}
/>
<DropdownMenuContent align="end" className="lib:w-44">
<DropdownMenuContent align="end" className="fui:w-44">
<DropdownMenuGroup>
<DropdownMenuItem>
<Eye />
@@ -1147,7 +1131,7 @@ function OrdersTableCard() {
: "Sortable, filterable and paginated with TanStack Table"}
</CardDescription>
<CardAction>
<div className="lib:flex lib:items-center lib:gap-2">
<div className="fui:flex fui:items-center fui:gap-2">
<Button variant="outline" size="sm">
<Download data-icon="inline-start" />
Export
@@ -1220,7 +1204,7 @@ function NewProjectDialog() {
</Button>
}
/>
<DialogContent className="lib:sm:max-w-lg">
<DialogContent className="fui:sm:max-w-lg">
<DialogHeader>
<DialogTitle>Create project</DialogTitle>
<DialogDescription>
@@ -1287,8 +1271,8 @@ const goals = [
function AnalyticsTab() {
return (
<div className="lib:grid lib:gap-4 lib:xl:grid-cols-7">
<Card className="lib:xl:col-span-4">
<div className="fui:grid fui:gap-4 fui:xl:grid-cols-7">
<Card className="fui:xl:col-span-4">
<CardHeader>
<CardTitle>Traffic by source</CardTitle>
<CardDescription>Unique visitors, last 30 days</CardDescription>
@@ -1296,7 +1280,7 @@ function AnalyticsTab() {
<CardContent>
<ChartContainer
config={trafficChartConfig}
className="lib:aspect-auto lib:h-[240px] lib:w-full"
className="fui:aspect-auto fui:h-[240px] fui:w-full"
>
<BarChart data={trafficBySource} margin={{ left: 4, right: 4 }}>
<CartesianGrid vertical={false} />
@@ -1318,20 +1302,20 @@ function AnalyticsTab() {
</CardContent>
</Card>
<Card className="lib:xl:col-span-3">
<Card className="fui:xl:col-span-3">
<CardHeader>
<CardTitle>Quarterly goals</CardTitle>
<CardDescription>Progress toward Q3 targets</CardDescription>
</CardHeader>
<CardContent className="lib:flex lib:flex-col lib:gap-5">
<CardContent className="fui:flex fui:flex-col fui:gap-5">
{goals.map((goal) => (
<div
key={goal.label}
className="lib:flex lib:flex-col lib:gap-2 lib:text-sm"
className="fui:flex fui:flex-col fui:gap-2 fui:text-sm"
>
<div className="lib:flex lib:items-center lib:justify-between">
<div className="fui:flex fui:items-center fui:justify-between">
<span>{goal.label}</span>
<span className="lib:tabular-nums lib:text-muted-foreground">
<span className="fui:tabular-nums fui:text-muted-foreground">
{goal.value}%
</span>
</div>
@@ -1372,7 +1356,7 @@ function ReportsTab() {
</CardDescription>
</CardHeader>
<CardContent>
<ItemGroup className="lib:gap-2">
<ItemGroup className="fui:gap-2">
{reports.map((report) => (
<Item key={report.title} variant="outline">
<ItemMedia variant="icon">
@@ -1404,7 +1388,7 @@ function AuditTab() {
<CardDescription>No events recorded in this range.</CardDescription>
</CardHeader>
<CardContent>
<Empty className="lib:border">
<Empty className="fui:border">
<EmptyHeader>
<EmptyMedia variant="icon">
<Inbox />
@@ -1525,46 +1509,46 @@ function InviteDialog() {
function LoadingSkeleton() {
return (
<div className="lib:flex lib:flex-col lib:gap-4">
<div className="lib:grid lib:gap-4 lib:sm:grid-cols-2 lib:xl:grid-cols-4">
<div className="fui:flex fui:flex-col fui:gap-4">
<div className="fui:grid fui:gap-4 fui:sm:grid-cols-2 fui:xl:grid-cols-4">
{stats.map((stat) => (
<Card key={stat.label} size="sm">
<CardHeader>
<Skeleton className="lib:h-4 lib:w-24" />
<Skeleton className="fui:h-4 fui:w-24" />
</CardHeader>
<CardContent className="lib:flex lib:flex-col lib:gap-3">
<Skeleton className="lib:h-7 lib:w-32" />
<Skeleton className="lib:h-1.5 lib:w-full" />
<Skeleton className="lib:h-3 lib:w-40" />
<CardContent className="fui:flex fui:flex-col fui:gap-3">
<Skeleton className="fui:h-7 fui:w-32" />
<Skeleton className="fui:h-1.5 fui:w-full" />
<Skeleton className="fui:h-3 fui:w-40" />
</CardContent>
</Card>
))}
</div>
<div className="lib:grid lib:gap-4 lib:xl:grid-cols-7">
<Card className="lib:xl:col-span-4">
<div className="fui:grid fui:gap-4 fui:xl:grid-cols-7">
<Card className="fui:xl:col-span-4">
<CardHeader>
<Skeleton className="lib:h-5 lib:w-48" />
<Skeleton className="fui:h-5 fui:w-48" />
</CardHeader>
<CardContent>
<Skeleton className="lib:h-[260px] lib:w-full" />
<Skeleton className="fui:h-[260px] fui:w-full" />
</CardContent>
</Card>
<Card className="lib:xl:col-span-3">
<Card className="fui:xl:col-span-3">
<CardHeader>
<Skeleton className="lib:h-5 lib:w-36" />
<Skeleton className="fui:h-5 fui:w-36" />
</CardHeader>
<CardContent className="lib:flex lib:flex-col lib:gap-4">
<CardContent className="fui:flex fui:flex-col fui:gap-4">
{recentActivity.map((entry) => (
<div
key={entry.name}
className="lib:flex lib:items-center lib:gap-3"
className="fui:flex fui:items-center fui:gap-3"
>
<Skeleton className="lib:size-8 lib:rounded-full" />
<div className="lib:flex lib:flex-1 lib:flex-col lib:gap-1.5">
<Skeleton className="lib:h-3.5 lib:w-32" />
<Skeleton className="lib:h-3 lib:w-48" />
<Skeleton className="fui:size-8 fui:rounded-full" />
<div className="fui:flex fui:flex-1 fui:flex-col fui:gap-1.5">
<Skeleton className="fui:h-3.5 fui:w-32" />
<Skeleton className="fui:h-3 fui:w-48" />
</div>
<Skeleton className="lib:h-3.5 lib:w-16" />
<Skeleton className="fui:h-3.5 fui:w-16" />
</div>
))}
</CardContent>
@@ -1574,10 +1558,6 @@ function LoadingSkeleton() {
);
}
/* -------------------------------------------------------------------------- */
/* page */
/* -------------------------------------------------------------------------- */
type DashboardProps = {
/** Start with the sidebar collapsed to icons. */
sidebarOpen?: boolean;
@@ -1607,17 +1587,17 @@ function Dashboard({ sidebarOpen = true, loading = false }: DashboardProps) {
<SidebarInset>
<DashboardHeader onOpenCommand={() => setCommandOpen(true)} />
<div className="lib:flex lib:flex-1 lib:flex-col lib:gap-4 lib:p-4 lib:md:p-6">
<div className="lib:flex lib:flex-wrap lib:items-center lib:justify-between lib:gap-3">
<div className="lib:flex lib:flex-col lib:gap-1">
<h1 className="lib:font-heading lib:text-xl lib:font-medium">
<div className="fui:flex fui:flex-1 fui:flex-col fui:gap-4 fui:p-4 fui:md:p-6">
<div className="fui:flex fui:flex-wrap fui:items-center fui:justify-between fui:gap-3">
<div className="fui:flex fui:flex-col fui:gap-1">
<h1 className="fui:font-heading fui:text-xl fui:font-medium">
Good afternoon, Lena
</h1>
<p className="lib:text-sm lib:text-muted-foreground">
<p className="fui:text-sm fui:text-muted-foreground">
Here is what happened across Foomo Inc. today.
</p>
</div>
<div className="lib:flex lib:items-center lib:gap-2">
<div className="fui:flex fui:items-center fui:gap-2">
<Button
variant="outline"
size="sm"
@@ -1651,14 +1631,14 @@ function Dashboard({ sidebarOpen = true, loading = false }: DashboardProps) {
<StatCards />
<Tabs defaultValue="overview">
<div className="lib:flex lib:flex-wrap lib:items-center lib:justify-between lib:gap-2">
<div className="fui:flex fui:flex-wrap fui:items-center fui:justify-between fui:gap-2">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="audit">Audit</TabsTrigger>
</TabsList>
<span className="lib:hidden lib:items-center lib:gap-1.5 lib:text-xs lib:text-muted-foreground lib:sm:flex">
<span className="fui:hidden fui:items-center fui:gap-1.5 fui:text-xs fui:text-muted-foreground fui:sm:flex">
Press
<KbdGroup>
<Kbd>⌘</Kbd>
@@ -1670,9 +1650,9 @@ function Dashboard({ sidebarOpen = true, loading = false }: DashboardProps) {
<TabsContent
value="overview"
className="lib:flex lib:flex-col lib:gap-4"
className="fui:flex fui:flex-col fui:gap-4"
>
<div className="lib:grid lib:gap-4 lib:xl:grid-cols-7">
<div className="fui:grid fui:gap-4 fui:xl:grid-cols-7">
<RevenueChartCard />
<ActivityCard />
</div>
@@ -1703,10 +1683,6 @@ function Dashboard({ sidebarOpen = true, loading = false }: DashboardProps) {
);
}
/* -------------------------------------------------------------------------- */
/* stories */
/* -------------------------------------------------------------------------- */
const meta = {
title: "Showcase/Admin Dashboard",
component: Dashboard,
+7 -7
View File
@@ -107,9 +107,9 @@ const columns = column.columns([
<DataTableColumnHeader column={column} title="Project" />
),
cell: ({ row }) => (
<div className="lib:flex lib:flex-col">
<span className="lib:font-medium">{row.original.name}</span>
<span className="lib:text-xs lib:text-muted-foreground">
<div className="fui:flex fui:flex-col">
<span className="fui:font-medium">{row.original.name}</span>
<span className="fui:text-xs fui:text-muted-foreground">
{row.original.id}
</span>
</div>
@@ -129,7 +129,7 @@ const columns = column.columns([
cell: (info) => (
<Badge
variant={statusVariant[info.getValue()]}
className="lib:capitalize"
className="fui:capitalize"
>
{info.getValue()}
</Badge>
@@ -142,7 +142,7 @@ const columns = column.columns([
<DataTableColumnHeader column={column} title="Updated" />
),
cell: (info) => (
<span className="lib:whitespace-nowrap lib:text-muted-foreground">
<span className="fui:whitespace-nowrap fui:text-muted-foreground">
{dateTime.format(info.getValue())}
</span>
),
@@ -153,7 +153,7 @@ const columns = column.columns([
<DataTableColumnHeader column={column} title="Deployments" align="end" />
),
cell: (info) => (
<div className="lib:text-right lib:tabular-nums">{info.getValue()}</div>
<div className="fui:text-right fui:tabular-nums">{info.getValue()}</div>
),
}),
]);
@@ -171,7 +171,7 @@ const meta = {
tags: ["autodocs"],
decorators: [
(Story) => (
<div className="lib:mx-auto lib:w-full lib:max-w-5xl">
<div className="fui:mx-auto fui:w-full fui:max-w-5xl">
<Story />
</div>
),
+1 -1
View File
@@ -1,4 +1,4 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
prefix: "lib",
prefix: "fui",
};