SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
This commit is contained in:
commit
fa9c73363d
1367 files changed
+355735
No files matched your search
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "@snowluma/ui",
|
||||
"author": {
|
||||
"name": "MotricSeven",
|
||||
"email": "motricseven@gmail.com"
|
||||
},
|
||||
"version": "2.4.2",
|
||||
"description": "Shared SnowLuma UI primitives — Tailwind 4 theme tokens + shadcn-style atomic components",
|
||||
"keywords": [
|
||||
"snowluma",
|
||||
"ui",
|
||||
"shadcn",
|
||||
"tailwind",
|
||||
"react"
|
||||
],
|
||||
"homepage": "https://github.com/SnowLuma/SnowLuma#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/SnowLuma/SnowLuma/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/SnowLuma/SnowLuma.git",
|
||||
"directory": "packages/ui"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./styles/theme.css": "./src/styles/theme.css",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src/styles"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "node -e \"const fs = require('node:fs'); fs.rmSync('dist', { recursive: true, force: true })\"",
|
||||
"prebuild": "pnpm run clean",
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"prepack": "pnpm run build",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^24.22.1 || >=26.12.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-alert-dialog": "^1.1.7",
|
||||
"@radix-ui/react-checkbox": "^1.1.5",
|
||||
"@radix-ui/react-dialog": "^1.1.7",
|
||||
"@radix-ui/react-label": "^2.1.3",
|
||||
"@radix-ui/react-popover": "^1.1.7",
|
||||
"@radix-ui/react-scroll-area": "^1.2.4",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.3",
|
||||
"@radix-ui/react-slot": "^1.1.3",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.1.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.469.0",
|
||||
"sonner": "^2.0.6",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import * as React from 'react';
|
||||
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
||||
import { cn } from '../lib/cn';
|
||||
import { buttonVariants } from './button';
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root;
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
||||
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
||||
|
||||
const AlertDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-background/60 backdrop-blur-sm',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDialogOverlay.displayName = 'AlertDialogOverlay';
|
||||
|
||||
const AlertDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-lg grid-cols-[minmax(0,1fr)] -translate-x-1/2 -translate-y-1/2 gap-5 rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-xl ring-1 ring-foreground/5',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
));
|
||||
AlertDialogContent.displayName = 'AlertDialogContent';
|
||||
|
||||
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn('flex flex-col gap-1.5 text-left', className)} {...props} />
|
||||
);
|
||||
|
||||
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const AlertDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('text-base font-semibold leading-tight tracking-tight', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDialogTitle.displayName = 'AlertDialogTitle';
|
||||
|
||||
const AlertDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('text-sm leading-relaxed text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDialogDescription.displayName = 'AlertDialogDescription';
|
||||
|
||||
const AlertDialogAction = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
|
||||
));
|
||||
AlertDialogAction.displayName = 'AlertDialogAction';
|
||||
|
||||
const AlertDialogCancel = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(buttonVariants({ variant: 'outline' }), 'mt-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDialogCancel.displayName = 'AlertDialogCancel';
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogPortal,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
|
||||
({ className, size = 36, style, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative inline-flex shrink-0 overflow-hidden rounded-full bg-muted',
|
||||
className,
|
||||
)}
|
||||
style={{ width: size, height: size, ...style }}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Avatar.displayName = 'Avatar';
|
||||
|
||||
const AvatarImage = React.forwardRef<HTMLImageElement, React.ImgHTMLAttributes<HTMLImageElement>>(
|
||||
({ className, alt = '', ...props }, ref) => (
|
||||
<img
|
||||
ref={ref}
|
||||
alt={alt}
|
||||
className={cn('aspect-square size-full object-cover', className)}
|
||||
referrerPolicy="no-referrer"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
AvatarImage.displayName = 'AvatarImage';
|
||||
|
||||
const AvatarFallback = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<span
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex size-full items-center justify-center bg-muted text-xs font-medium text-muted-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
AvatarFallback.displayName = 'AvatarFallback';
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback };
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium leading-none transition-colors',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border-transparent bg-primary/15 text-primary',
|
||||
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
||||
destructive: 'border-transparent bg-destructive/15 text-destructive',
|
||||
success: 'border-transparent bg-success/15 text-success',
|
||||
warning: 'border-transparent bg-warning/15 text-warning',
|
||||
info: 'border-transparent bg-info/15 text-info',
|
||||
outline: 'border-border bg-card text-foreground',
|
||||
soft: 'border-transparent bg-muted text-muted-foreground',
|
||||
},
|
||||
size: {
|
||||
default: 'px-2 py-0.5 text-[11px]',
|
||||
sm: 'px-1.5 py-px text-[10px]',
|
||||
lg: 'px-2.5 py-1 text-xs',
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: 'default', size: 'default' },
|
||||
},
|
||||
);
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof badgeVariants> {}
|
||||
|
||||
export function Badge({ className, variant, size, ...props }: BadgeProps) {
|
||||
return <span className={cn(badgeVariants({ variant, size }), className)} {...props} />;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import * as React from 'react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
[
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium',
|
||||
'transition-[color,background-color,border-color,box-shadow,transform] duration-150 ease-out',
|
||||
'disabled:pointer-events-none disabled:opacity-50',
|
||||
"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
|
||||
'outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:border-ring',
|
||||
'active:translate-y-px cursor-pointer',
|
||||
].join(' '),
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 active:bg-primary/95',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 active:bg-destructive/95',
|
||||
outline:
|
||||
'border border-border bg-card text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground hover:border-border/80',
|
||||
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
||||
ghost: 'text-foreground hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline shadow-none',
|
||||
success: 'bg-success text-success-foreground shadow-sm hover:bg-success/90',
|
||||
warning: 'bg-warning text-warning-foreground shadow-sm hover:bg-warning/90',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-lg px-6',
|
||||
icon: 'size-9 rounded-lg',
|
||||
'icon-sm': 'size-8 rounded-md',
|
||||
'icon-xs': 'size-7 rounded-md',
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: 'default', size: 'default' },
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size }), className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = 'Button';
|
||||
@@ -0,0 +1,61 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-xl border border-border bg-card text-card-foreground shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Card.displayName = 'Card';
|
||||
|
||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('flex flex-col gap-1.5 p-5', className)} {...props} />
|
||||
),
|
||||
);
|
||||
CardHeader.displayName = 'CardHeader';
|
||||
|
||||
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('text-base font-semibold leading-tight tracking-tight', className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
CardTitle.displayName = 'CardTitle';
|
||||
|
||||
const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('text-sm leading-relaxed text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
CardDescription.displayName = 'CardDescription';
|
||||
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('p-5 pt-0', className)} {...props} />
|
||||
),
|
||||
);
|
||||
CardContent.displayName = 'CardContent';
|
||||
|
||||
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('flex items-center gap-2 p-5 pt-0', className)} {...props} />
|
||||
),
|
||||
);
|
||||
CardFooter.displayName = 'CardFooter';
|
||||
|
||||
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||
import { Check, Minus } from 'lucide-react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'peer size-4 shrink-0 rounded-[5px] border border-border bg-card shadow-xs transition-colors',
|
||||
'hover:border-border/80',
|
||||
'focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary',
|
||||
'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground data-[state=indeterminate]:border-primary',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator className="flex items-center justify-center text-current">
|
||||
{props.checked === 'indeterminate' ? (
|
||||
<Minus className="size-3" />
|
||||
) : (
|
||||
<Check className="size-3" />
|
||||
)}
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
Checkbox.displayName = 'Checkbox';
|
||||
|
||||
export { Checkbox };
|
||||
@@ -0,0 +1,105 @@
|
||||
import * as React from 'react';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { X } from 'lucide-react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-background/60 backdrop-blur-sm',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = 'DialogOverlay';
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { showClose?: boolean }
|
||||
>(({ className, children, showClose = true, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
// grid-cols-[minmax(0,1fr)] keeps wide children (long select text,
|
||||
// file paths, etc.) from blowing past the dialog's max-w-lg by
|
||||
// forcing the implicit grid track's `min-width: auto` to 0.
|
||||
'fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-2rem)] max-w-lg grid-cols-[minmax(0,1fr)] -translate-x-1/2 -translate-y-1/2 gap-5 rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-xl ring-1 ring-foreground/5',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||
'data-[state=open]:slide-in-from-top-2',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showClose && (
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 inline-flex size-7 items-center justify-center rounded-md text-muted-foreground opacity-80 transition hover:bg-muted hover:opacity-100 focus:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40">
|
||||
<X className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = 'DialogContent';
|
||||
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn('flex flex-col gap-1.5 text-left', className)} {...props} />
|
||||
);
|
||||
|
||||
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('text-base font-semibold leading-tight tracking-tight', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = 'DialogTitle';
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('text-sm leading-relaxed text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = 'DialogDescription';
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
|
||||
icon?: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
action?: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic "nothing here yet" placeholder. Used by Bot list, search-with-no-
|
||||
* results, log filters that filtered everything out, etc.
|
||||
*/
|
||||
export function EmptyState({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
className,
|
||||
...props
|
||||
}: EmptyStateProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border bg-card/40 px-6 py-12 text-center',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{icon && (
|
||||
<div className="flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-medium text-foreground">{title}</h3>
|
||||
{description && (
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
{action && <div className="pt-1">{action}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
|
||||
({ className, type, ...props }, ref) => (
|
||||
<input
|
||||
type={type}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-9 w-full min-w-0 rounded-lg border border-border bg-card px-3 py-1 text-sm text-foreground shadow-xs transition-[color,box-shadow,border-color,background-color] outline-none',
|
||||
'file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium',
|
||||
'placeholder:text-muted-foreground',
|
||||
'hover:border-border/80',
|
||||
'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/40',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'aria-invalid:border-destructive aria-invalid:ring-destructive/20',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Input.displayName = 'Input';
|
||||
|
||||
export { Input };
|
||||
@@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
/** Small inline keyboard-key label, e.g. `<Kbd>⌘K</Kbd>`. */
|
||||
export const Kbd = React.forwardRef<HTMLElement, React.HTMLAttributes<HTMLElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<kbd
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex h-5 min-w-[1.25rem] items-center justify-center rounded border border-border bg-muted px-1 font-mono text-[10px] font-medium text-muted-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Kbd.displayName = 'Kbd';
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import * as LabelPrimitive from '@radix-ui/react-label';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-xs font-medium leading-none text-muted-foreground select-none',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = 'Label';
|
||||
|
||||
export { Label };
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Popover = PopoverPrimitive.Root;
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
const PopoverAnchor = PopoverPrimitive.Anchor;
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = 'center', sideOffset = 6, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 w-72 rounded-xl border border-border bg-popover p-4 text-popover-foreground shadow-xl ring-1 ring-foreground/5 outline-none',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
));
|
||||
PopoverContent.displayName = 'PopoverContent';
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent };
|
||||
@@ -0,0 +1,54 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
value?: number;
|
||||
indicatorClassName?: string;
|
||||
/**
|
||||
* When true, the bar shows an animated marquee instead of a fixed
|
||||
* width — useful when we know a job is running but don't yet know
|
||||
* the total size (e.g. a download whose Content-Length is unknown).
|
||||
*/
|
||||
indeterminate?: boolean;
|
||||
}
|
||||
|
||||
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
||||
({ className, value = 0, indicatorClassName, indeterminate = false, ...props }, ref) => {
|
||||
const clamped = Math.max(0, Math.min(100, value));
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="progressbar"
|
||||
aria-valuenow={indeterminate ? undefined : clamped}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
className={cn(
|
||||
'relative h-2 w-full overflow-hidden rounded-full bg-muted',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{indeterminate ? (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute inset-y-0 left-0 w-1/3 rounded-full bg-primary',
|
||||
'animate-[progress-marquee_1.4s_ease-in-out_infinite]',
|
||||
indicatorClassName,
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
'h-full rounded-full bg-primary transition-[width] duration-200 ease-out',
|
||||
indicatorClassName,
|
||||
)}
|
||||
style={{ width: `${clamped}%` }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
Progress.displayName = 'Progress';
|
||||
|
||||
export { Progress };
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as React from 'react';
|
||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
interface ScrollAreaProps extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {
|
||||
viewportClassName?: string;
|
||||
}
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
ScrollAreaProps
|
||||
>(({ className, children, viewportClassName, type = 'hover', ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
type={type}
|
||||
className={cn('relative overflow-hidden', className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
className={cn('size-full rounded-[inherit] [&>div]:!block', viewportClassName)}
|
||||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollBar orientation="horizontal" />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
));
|
||||
ScrollArea.displayName = 'ScrollArea';
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = 'vertical', ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'flex touch-none select-none p-0.5 transition-colors',
|
||||
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent',
|
||||
orientation === 'horizontal' && 'h-2.5 w-full flex-col border-t border-t-transparent',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border transition-colors hover:bg-muted-foreground/50" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
));
|
||||
ScrollBar.displayName = 'ScrollBar';
|
||||
|
||||
export { ScrollArea, ScrollBar };
|
||||
@@ -0,0 +1,171 @@
|
||||
import * as React from 'react';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'group flex h-9 w-full min-w-0 items-center justify-between gap-2 rounded-lg border border-border bg-card px-3 py-1 text-sm shadow-xs outline-none transition-[color,box-shadow,background-color,border-color]',
|
||||
'hover:border-border/80 hover:bg-card/70',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/40',
|
||||
'data-[state=open]:border-ring data-[state=open]:ring-[3px] data-[state=open]:ring-ring/40',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'data-[placeholder]:text-muted-foreground',
|
||||
// Selected text mustn't push the chevron out of the trigger.
|
||||
'[&>span:not([data-radix-select-icon])]:flex-1 [&>span:not([data-radix-select-icon])]:min-w-0 [&>span:not([data-radix-select-icon])]:truncate [&>span:not([data-radix-select-icon])]:text-left',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild data-radix-select-icon>
|
||||
<ChevronDown className="size-4 shrink-0 opacity-60 transition-transform group-data-[state=open]:rotate-180" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
SelectTrigger.displayName = 'SelectTrigger';
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1 text-muted-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
));
|
||||
SelectScrollUpButton.displayName = 'SelectScrollUpButton';
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1 text-muted-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
));
|
||||
SelectScrollDownButton.displayName = 'SelectScrollDownButton';
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = 'popper', ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
position={position}
|
||||
sideOffset={4}
|
||||
className={cn(
|
||||
// Solid popover surface — explicit bg + ring for visibility against any background.
|
||||
'relative z-50 max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width) overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-xl ring-1 ring-foreground/5',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
'p-1',
|
||||
position === 'popper' &&
|
||||
'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
SelectContent.displayName = 'SelectContent';
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'px-2 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectLabel.displayName = 'SelectLabel';
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex w-full cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-2 pr-8 text-sm outline-none transition-colors',
|
||||
'focus:bg-accent focus:text-accent-foreground',
|
||||
'data-[state=checked]:font-medium',
|
||||
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex size-3.5 items-center justify-center text-primary">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = 'SelectItem';
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn('-mx-1 my-1 h-px bg-border', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectSeparator.displayName = 'SelectSeparator';
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'shrink-0 bg-border',
|
||||
orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Separator.displayName = 'Separator';
|
||||
|
||||
export { Separator };
|
||||
@@ -0,0 +1,72 @@
|
||||
import * as React from 'react';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { X } from 'lucide-react';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Sheet = DialogPrimitive.Root;
|
||||
const SheetTrigger = DialogPrimitive.Trigger;
|
||||
const SheetClose = DialogPrimitive.Close;
|
||||
const SheetPortal = DialogPrimitive.Portal;
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SheetOverlay.displayName = 'SheetOverlay';
|
||||
|
||||
const sheetVariants = cva(
|
||||
'fixed z-50 gap-4 bg-card p-0 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 outline-none',
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||
bottom: 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||
left: 'inset-y-0 left-0 h-full w-3/4 max-w-xs border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
|
||||
right: 'inset-y-0 right-0 h-full w-3/4 max-w-xs border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
|
||||
},
|
||||
},
|
||||
defaultVariants: { side: 'right' },
|
||||
},
|
||||
);
|
||||
|
||||
interface SheetContentProps
|
||||
extends
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {
|
||||
showClose?: boolean;
|
||||
}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
SheetContentProps
|
||||
>(({ side = 'right', className, children, showClose = true, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showClose && (
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 inline-flex size-7 items-center justify-center rounded-md opacity-70 transition-opacity hover:opacity-100 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40 cursor-pointer">
|
||||
<X className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</SheetPortal>
|
||||
));
|
||||
SheetContent.displayName = 'SheetContent';
|
||||
|
||||
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetPortal, SheetOverlay };
|
||||
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
/**
|
||||
* A semantic colored status dot — drop-in replacement for ad-hoc emoji
|
||||
* (🟢 / 🟡 / 🔴) so we keep visual rhythm with the rest of the Lucide
|
||||
* iconography. `pulse` adds a soft halo for the "active" state.
|
||||
*/
|
||||
const dotVariants = cva('inline-block shrink-0 rounded-full', {
|
||||
variants: {
|
||||
tone: {
|
||||
success: 'bg-success',
|
||||
warning: 'bg-warning',
|
||||
destructive: 'bg-destructive',
|
||||
info: 'bg-info',
|
||||
muted: 'bg-muted-foreground/50',
|
||||
primary: 'bg-primary',
|
||||
},
|
||||
size: {
|
||||
xs: 'size-1.5',
|
||||
sm: 'size-2',
|
||||
md: 'size-2.5',
|
||||
lg: 'size-3',
|
||||
},
|
||||
},
|
||||
defaultVariants: { tone: 'muted', size: 'sm' },
|
||||
});
|
||||
|
||||
export interface StatusDotProps
|
||||
extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof dotVariants> {
|
||||
pulse?: boolean;
|
||||
}
|
||||
|
||||
export function StatusDot({ className, tone, size, pulse, ...props }: StatusDotProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'relative inline-flex items-center justify-center',
|
||||
pulse &&
|
||||
'before:absolute before:inset-0 before:rounded-full before:bg-current before:opacity-30 before:animate-ping',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className={cn(dotVariants({ tone, size }), 'relative')} aria-hidden />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import * as React from 'react';
|
||||
import * as SwitchPrimitive from '@radix-ui/react-switch';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-border shadow-inner transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
// Checked: filled with primary
|
||||
'data-[state=checked]:bg-primary data-[state=checked]:border-primary',
|
||||
// Unchecked: visible track that contrasts with both surface modes
|
||||
'data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-muted-foreground/25',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
className={cn(
|
||||
'pointer-events-none block size-4 rounded-full shadow-md ring-0 transition-transform',
|
||||
// Thumb always uses card surface so it pops against the track in both states.
|
||||
'bg-card dark:bg-foreground/95',
|
||||
'data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
));
|
||||
Switch.displayName = 'Switch';
|
||||
|
||||
export { Switch };
|
||||
@@ -0,0 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex h-9 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsList.displayName = 'TabsList';
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1 text-sm font-medium transition-all',
|
||||
'focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40',
|
||||
'disabled:pointer-events-none disabled:opacity-50',
|
||||
'data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsTrigger.displayName = 'TabsTrigger';
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content ref={ref} className={cn('mt-2 outline-none', className)} {...props} />
|
||||
));
|
||||
TabsContent.displayName = 'TabsContent';
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Toaster as Sonner, toast, type ToasterProps } from 'sonner';
|
||||
|
||||
/**
|
||||
* SnowLuma-themed toaster. Uses the Tailwind `--*-foreground / --background`
|
||||
* tokens defined in `@snowluma/ui/styles/theme.css` so toasts match the
|
||||
* surrounding chrome in both light and dark mode.
|
||||
*/
|
||||
export function Toaster(props: ToasterProps) {
|
||||
return (
|
||||
<Sonner
|
||||
position="bottom-right"
|
||||
richColors
|
||||
closeButton
|
||||
theme="system"
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast: 'group toast group-[.toaster]:bg-card group-[.toaster]:text-card-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||
description: 'group-[.toast]:text-muted-foreground',
|
||||
actionButton:
|
||||
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { toast };
|
||||
export type { ToasterProps };
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
const Tooltip = TooltipPrimitive.Root;
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 6, ...props }, ref) => (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 max-w-xs overflow-hidden rounded-md border border-border bg-popover px-2.5 py-1.5 text-xs text-popover-foreground shadow-lg ring-1 ring-foreground/5',
|
||||
'data-[state=delayed-open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=delayed-open]:fade-in-0',
|
||||
'data-[state=delayed-open]:zoom-in-95 data-[state=closed]:zoom-out-95',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</TooltipPrimitive.Portal>
|
||||
));
|
||||
TooltipContent.displayName = 'TooltipContent';
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
@@ -0,0 +1,72 @@
|
||||
export { cn } from './lib/cn';
|
||||
export { Button, buttonVariants, type ButtonProps } from './components/button';
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogPortal,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
} from './components/alert-dialog';
|
||||
export { Avatar, AvatarImage, AvatarFallback } from './components/avatar';
|
||||
export { Badge, badgeVariants, type BadgeProps } from './components/badge';
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
} from './components/card';
|
||||
export { Checkbox } from './components/checkbox';
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from './components/dialog';
|
||||
export { Input } from './components/input';
|
||||
export { Label } from './components/label';
|
||||
export { Progress } from './components/progress';
|
||||
export { ScrollArea, ScrollBar } from './components/scroll-area';
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
} from './components/select';
|
||||
export { Separator } from './components/separator';
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetPortal,
|
||||
SheetOverlay,
|
||||
} from './components/sheet';
|
||||
export { Skeleton } from './components/skeleton';
|
||||
export { Switch } from './components/switch';
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent } from './components/tabs';
|
||||
export { Toaster, toast, type ToasterProps } from './components/toaster';
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './components/tooltip';
|
||||
export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent } from './components/popover';
|
||||
export { Kbd } from './components/kbd';
|
||||
export { StatusDot, type StatusDotProps } from './components/status-dot';
|
||||
export { EmptyState } from './components/empty-state';
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* @snowluma/ui — Tailwind 4 theme tokens.
|
||||
*
|
||||
* Consumer setup:
|
||||
* @import "tailwindcss";
|
||||
* @import "tw-animate-css";
|
||||
* @import "@snowluma/ui/styles/theme.css";
|
||||
* @source "../node_modules/@snowluma/ui/dist/**\/*.js";
|
||||
*/
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
:root {
|
||||
/* Surface ramp — light mode: near-white surfaces over a faint blue base. */
|
||||
--radius: 0.625rem;
|
||||
|
||||
--background: oklch(99% 0.004 240);
|
||||
--foreground: oklch(20% 0.03 250);
|
||||
|
||||
--card: oklch(100% 0 0);
|
||||
--card-foreground: oklch(20% 0.03 250);
|
||||
|
||||
--popover: oklch(100% 0 0);
|
||||
--popover-foreground: oklch(20% 0.03 250);
|
||||
|
||||
/* sky accent ramp */
|
||||
--primary: oklch(64% 0.16 230);
|
||||
--primary-foreground: oklch(99% 0.005 240);
|
||||
|
||||
--secondary: oklch(96.5% 0.012 240);
|
||||
--secondary-foreground: oklch(22% 0.03 250);
|
||||
|
||||
--muted: oklch(96.5% 0.012 240);
|
||||
--muted-foreground: oklch(48% 0.02 250);
|
||||
|
||||
--accent: oklch(94% 0.04 230);
|
||||
--accent-foreground: oklch(28% 0.08 230);
|
||||
|
||||
--destructive: oklch(58% 0.22 27);
|
||||
--destructive-foreground: oklch(99% 0.005 240);
|
||||
|
||||
--success: oklch(64% 0.16 155);
|
||||
--success-foreground: oklch(99% 0.005 240);
|
||||
|
||||
--warning: oklch(76% 0.16 75);
|
||||
--warning-foreground: oklch(20% 0.05 75);
|
||||
|
||||
--info: oklch(64% 0.16 230);
|
||||
--info-foreground: oklch(99% 0.005 240);
|
||||
|
||||
--border: oklch(91% 0.012 240);
|
||||
--input: oklch(94% 0.012 240);
|
||||
--ring: oklch(64% 0.16 230);
|
||||
|
||||
--sidebar: oklch(98% 0.006 240);
|
||||
--sidebar-foreground: oklch(25% 0.03 250);
|
||||
--sidebar-primary: oklch(64% 0.16 230);
|
||||
--sidebar-primary-foreground: oklch(99% 0.005 240);
|
||||
--sidebar-accent: oklch(94% 0.03 230);
|
||||
--sidebar-accent-foreground: oklch(28% 0.08 230);
|
||||
--sidebar-border: oklch(91% 0.012 240);
|
||||
--sidebar-ring: oklch(64% 0.16 230);
|
||||
|
||||
--chrome-height: 3.25rem; /* 52px — uniform top-bar height */
|
||||
--titlebar-height: 2rem; /* 32px — Windows native titlebar height */
|
||||
|
||||
--shadow-color: 240 6% 10%;
|
||||
--shadow-xs: 0 1px 2px 0 hsl(var(--shadow-color) / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0 hsl(var(--shadow-color) / 0.08), 0 1px 2px -1px hsl(var(--shadow-color) / 0.06);
|
||||
--shadow-md: 0 4px 6px -1px hsl(var(--shadow-color) / 0.08), 0 2px 4px -2px hsl(var(--shadow-color) / 0.04);
|
||||
--shadow-lg: 0 10px 16px -3px hsl(var(--shadow-color) / 0.10), 0 4px 6px -4px hsl(var(--shadow-color) / 0.05);
|
||||
--shadow-xl: 0 20px 28px -8px hsl(var(--shadow-color) / 0.14);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(13% 0.02 250);
|
||||
--foreground: oklch(96% 0.012 240);
|
||||
|
||||
--card: oklch(17% 0.022 250);
|
||||
--card-foreground: oklch(96% 0.012 240);
|
||||
|
||||
--popover: oklch(20% 0.025 250);
|
||||
--popover-foreground: oklch(96% 0.012 240);
|
||||
|
||||
--primary: oklch(72% 0.16 230);
|
||||
--primary-foreground: oklch(12% 0.025 250);
|
||||
|
||||
--secondary: oklch(22% 0.025 250);
|
||||
--secondary-foreground: oklch(94% 0.012 240);
|
||||
|
||||
--muted: oklch(20% 0.022 250);
|
||||
--muted-foreground: oklch(72% 0.02 240);
|
||||
|
||||
--accent: oklch(30% 0.06 230);
|
||||
--accent-foreground: oklch(88% 0.12 230);
|
||||
|
||||
--destructive: oklch(66% 0.22 27);
|
||||
--destructive-foreground: oklch(98% 0.005 240);
|
||||
|
||||
--success: oklch(70% 0.16 155);
|
||||
--success-foreground: oklch(12% 0.025 250);
|
||||
|
||||
--warning: oklch(80% 0.16 75);
|
||||
--warning-foreground: oklch(20% 0.05 75);
|
||||
|
||||
--info: oklch(72% 0.16 230);
|
||||
--info-foreground: oklch(12% 0.025 250);
|
||||
|
||||
--border: oklch(28% 0.025 250);
|
||||
--input: oklch(26% 0.025 250);
|
||||
--ring: oklch(72% 0.16 230);
|
||||
|
||||
--sidebar: oklch(15% 0.022 250);
|
||||
--sidebar-foreground: oklch(92% 0.012 240);
|
||||
--sidebar-primary: oklch(72% 0.16 230);
|
||||
--sidebar-primary-foreground: oklch(12% 0.025 250);
|
||||
--sidebar-accent: oklch(28% 0.06 230);
|
||||
--sidebar-accent-foreground: oklch(88% 0.12 230);
|
||||
--sidebar-border: oklch(24% 0.025 250);
|
||||
--sidebar-ring: oklch(72% 0.16 230);
|
||||
|
||||
--shadow-color: 240 14% 4%;
|
||||
--shadow-xs: 0 1px 2px 0 hsl(var(--shadow-color) / 0.30);
|
||||
--shadow-sm: 0 1px 3px 0 hsl(var(--shadow-color) / 0.36), 0 1px 2px -1px hsl(var(--shadow-color) / 0.28);
|
||||
--shadow-md: 0 4px 6px -1px hsl(var(--shadow-color) / 0.40), 0 2px 4px -2px hsl(var(--shadow-color) / 0.28);
|
||||
--shadow-lg: 0 10px 16px -3px hsl(var(--shadow-color) / 0.50), 0 4px 6px -4px hsl(var(--shadow-color) / 0.32);
|
||||
--shadow-xl: 0 20px 28px -8px hsl(var(--shadow-color) / 0.55);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-success: var(--success);
|
||||
--color-success-foreground: var(--success-foreground);
|
||||
--color-warning: var(--warning);
|
||||
--color-warning-foreground: var(--warning-foreground);
|
||||
--color-info: var(--info);
|
||||
--color-info-foreground: var(--info-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
|
||||
--radius-xs: calc(var(--radius) - 6px);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
--radius-3xl: calc(var(--radius) + 16px);
|
||||
|
||||
--shadow-xs: var(--shadow-xs);
|
||||
--shadow-sm: var(--shadow-sm);
|
||||
--shadow-md: var(--shadow-md);
|
||||
--shadow-lg: var(--shadow-lg);
|
||||
--shadow-xl: var(--shadow-xl);
|
||||
|
||||
--font-sans: 'Inter', 'Noto Sans SC', system-ui, -apple-system, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
border-color: var(--border);
|
||||
}
|
||||
html, body, #root {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
line-height: 1.5;
|
||||
overflow: hidden; /* desktop chrome: no body scroll, panels manage their own */
|
||||
}
|
||||
::selection {
|
||||
background-color: color-mix(in oklab, var(--primary) 28%, transparent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
*::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in oklab, var(--muted-foreground) 35%, transparent);
|
||||
border-radius: 9999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background-color: color-mix(in oklab, var(--muted-foreground) 55%, transparent);
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
html[data-density="compact"] body {
|
||||
font-size: 13.5px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.hover-lift {
|
||||
transition: transform 180ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.hover-lift:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.surface-gloss {
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in oklab, var(--foreground) 3%, transparent) 0%,
|
||||
transparent 80%
|
||||
);
|
||||
}
|
||||
.surface-glass {
|
||||
background-color: color-mix(in oklab, var(--popover) 92%, transparent);
|
||||
backdrop-filter: saturate(180%) blur(12px);
|
||||
-webkit-backdrop-filter: saturate(180%) blur(12px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes progress-marquee {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(400%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"types": ["react", "react-dom"],
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
external: ['react', 'react-dom'],
|
||||
target: 'es2022',
|
||||
treeshake: true,
|
||||
});
|
||||
Reference in new issue
Block a user