From 7ea68d241aa5c8c24895c54d62e2d8bf2e3abeab Mon Sep 17 00:00:00 2001 From: Aleksandr Minkin Date: Thu, 27 Aug 2026 11:38:47 +0300 Subject: [PATCH 1/5] feat(ui): add shadcn component foundation --- apps/web/components.json | 9 + apps/web/package.json | 1 + apps/web/src/app/globals.css | 184 ++--- apps/web/src/shared/ui/skeleton-board.tsx | 2 +- apps/web/src/shared/ui/tracker-icons.tsx | 330 +-------- apps/web/tailwind.config.ts | 33 +- packages/ui/package.json | 9 +- packages/ui/src/index.ts | 10 +- packages/ui/src/lib/badge.tsx | 45 +- packages/ui/src/lib/button.tsx | 63 +- packages/ui/src/lib/card.tsx | 43 +- packages/ui/src/lib/dialog.tsx | 44 ++ packages/ui/src/lib/dropdown-menu.tsx | 29 + packages/ui/src/lib/input.tsx | 20 +- packages/ui/src/lib/modal.tsx | 34 +- packages/ui/src/lib/select.tsx | 20 +- packages/ui/src/lib/separator.tsx | 8 + packages/ui/src/lib/skeleton.tsx | 6 + packages/ui/src/lib/textarea.tsx | 20 +- packages/ui/src/lib/tooltip.tsx | 13 + packages/ui/src/lib/utils.ts | 6 + pnpm-lock.yaml | 809 ++++++++++++++++++++++ 22 files changed, 1156 insertions(+), 582 deletions(-) create mode 100644 apps/web/components.json create mode 100644 packages/ui/src/lib/dialog.tsx create mode 100644 packages/ui/src/lib/dropdown-menu.tsx create mode 100644 packages/ui/src/lib/separator.tsx create mode 100644 packages/ui/src/lib/skeleton.tsx create mode 100644 packages/ui/src/lib/tooltip.tsx create mode 100644 packages/ui/src/lib/utils.ts diff --git a/apps/web/components.json b/apps/web/components.json new file mode 100644 index 0000000..68e2181 --- /dev/null +++ b/apps/web/components.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { "config": "tailwind.config.ts", "css": "src/app/globals.css", "baseColor": "slate", "cssVariables": true, "prefix": "" }, + "aliases": { "components": "@tracker/ui", "utils": "@tracker/ui", "ui": "@tracker/ui", "lib": "@/lib", "hooks": "@/hooks" }, + "iconLibrary": "lucide" +} diff --git a/apps/web/package.json b/apps/web/package.json index 48d3393..dc8eefe 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,6 +16,7 @@ "@tracker/types": "workspace:*", "@tracker/ui": "workspace:*", "clsx": "^2.1.1", + "lucide-react": "^1.34.0", "next": "^15.5.23", "next-themes": "^0.4.6", "react": "^19.2.8", diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index b037afd..8836404 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -2,158 +2,60 @@ @tailwind components; @tailwind utilities; -:root { - color-scheme: light; - --surface: #f7f8fa; - --card: #ffffff; - --border: #dfe3e8; - --muted: #f0f2f5; - --text: #171a1f; - --accent: #5e6ad2; - --accent-soft: rgba(94, 106, 210, 0.12); - --success: #14805e; - --warning: #b86614; - --danger: #c73d52; - --focus-ring: rgba(94, 106, 210, 0.26); - --panel-shadow: 0 1px 2px rgb(16 24 40 / 0.04), 0 1px 5px rgb(16 24 40 / 0.03); -} - -html, -body { - min-height: 100%; - padding: 0; - margin: 0; -} - -html { - scroll-behavior: smooth; -} - -body { - min-height: 100vh; - color: var(--text); - background: var(--surface); - font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; - overflow-x: hidden; -} - -* { - box-sizing: border-box; -} - -::selection { - background: rgba(37, 111, 104, 0.24); -} - -a { - color: inherit; - text-decoration: none; -} - -button, -input, -select, -textarea { - font: inherit; -} - -button { - -webkit-tap-highlight-color: transparent; -} - -button:not(:disabled) { - cursor: pointer; -} - -input, -select, -textarea { - color: var(--text); -} - -input::placeholder, -textarea::placeholder { - color: rgba(17, 24, 39, 0.38); -} - -select option { - color: #172033; - background: #ffffff; -} - -.text-muted { - color: color-mix(in srgb, var(--text) 58%, transparent); -} - -.font-mono { - font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; -} - -@keyframes tracker-rise { - from { - opacity: 0; - transform: translateY(14px); +@layer base { + :root { + --background: 220 20% 97%; --foreground: 222 47% 11%; + --card: 0 0% 100%; --card-foreground: 222 47% 11%; + --popover: 0 0% 100%; --popover-foreground: 222 47% 11%; + --primary: 239 84% 67%; --primary-foreground: 0 0% 100%; + --secondary: 220 17% 94%; --secondary-foreground: 222 47% 11%; + --muted: 220 17% 94%; --muted-foreground: 215 16% 47%; + --accent: 239 75% 96%; --accent-foreground: 239 70% 48%; + --destructive: 0 72% 51%; --destructive-foreground: 0 0% 98%; + --border: 220 16% 88%; --input: 220 16% 85%; --ring: 239 84% 67%; + --success: 158 64% 32%; --warning: 32 95% 44%; --radius: 0.75rem; } - to { - opacity: 1; - transform: translateY(0); - } -} - -main { - animation: tracker-rise 420ms ease-out both; -} - -@layer components { - .tracker-panel { - border: 1px solid var(--border); - background: var(--card); - box-shadow: var(--panel-shadow); + .dark { + --background: 222 47% 7%; --foreground: 210 40% 98%; + --card: 222 39% 10%; --card-foreground: 210 40% 98%; + --popover: 222 39% 10%; --popover-foreground: 210 40% 98%; + --primary: 239 84% 72%; --primary-foreground: 222 47% 8%; + --secondary: 217 25% 16%; --secondary-foreground: 210 40% 98%; + --muted: 217 25% 16%; --muted-foreground: 215 20% 65%; + --accent: 239 35% 20%; --accent-foreground: 239 90% 82%; + --destructive: 0 63% 45%; --destructive-foreground: 0 0% 98%; + --border: 217 25% 19%; --input: 217 25% 22%; --ring: 239 84% 72%; + --success: 158 64% 46%; --warning: 38 92% 52%; } - .tracker-subtle-panel { - border: 1px solid var(--border); - background: var(--muted); + * { @apply border-border; } + html { scroll-behavior: smooth; } + body { + @apply min-h-screen bg-background text-foreground antialiased; + font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + text-rendering: optimizeLegibility; } + ::selection { @apply bg-primary/20; } + :focus-visible { outline: none; } } -:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -::-webkit-scrollbar { - width: 12px; - height: 12px; -} - -::-webkit-scrollbar-track { - background: transparent; +@layer components { + .tracker-panel { @apply border bg-card text-card-foreground shadow-sm; } + .tracker-subtle-panel { @apply border bg-muted/50; } + .text-muted { @apply text-muted-foreground; } + .font-mono { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; } } -::-webkit-scrollbar-thumb { - background: rgba(23, 32, 51, 0.16); - border: 3px solid transparent; - border-radius: 999px; - background-clip: content-box; +@keyframes tracker-rise { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } } -::-webkit-scrollbar-thumb:hover { - background: rgba(23, 32, 51, 0.28); - border: 3px solid transparent; - background-clip: content-box; -} +main { animation: tracker-rise 300ms ease-out both; } +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-thumb { background: hsl(var(--muted-foreground) / 0.25); border: 3px solid transparent; border-radius: 999px; background-clip: content-box; } @media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - scroll-behavior: auto !important; - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } + *, *::before, *::after { scroll-behavior: auto !important; animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } diff --git a/apps/web/src/shared/ui/skeleton-board.tsx b/apps/web/src/shared/ui/skeleton-board.tsx index 91b57e6..46e2727 100644 --- a/apps/web/src/shared/ui/skeleton-board.tsx +++ b/apps/web/src/shared/ui/skeleton-board.tsx @@ -2,7 +2,7 @@ export function SkeletonBoard() { return (
{Array.from({ length: 4 }).map((_, colIdx) => ( -
+
{Array.from({ length: 3 }).map((__, taskIdx) => ( diff --git a/apps/web/src/shared/ui/tracker-icons.tsx b/apps/web/src/shared/ui/tracker-icons.tsx index 020cb9a..b1147a3 100644 --- a/apps/web/src/shared/ui/tracker-icons.tsx +++ b/apps/web/src/shared/ui/tracker-icons.tsx @@ -1,300 +1,34 @@ "use client"; -import type { SVGProps } from "react"; -import clsx from "clsx"; - -export type IconProps = SVGProps & { - size?: number; - strokeWidth?: number; -}; - -function BaseIcon({ - children, - className, - size = 20, - strokeWidth = 1.8, - viewBox = "0 0 24 24", - ...props -}: IconProps) { - return ( - - {children} - - ); -} - -export function SearchIcon(props: IconProps) { - return ( - - - - - ); -} - -export function SplitViewIcon(props: IconProps) { - return ( - - - - - ); -} - -export function PlusIcon(props: IconProps) { - return ( - - - - - ); -} - -export function ListIcon(props: IconProps) { - return ( - - - - - - - - - ); -} - -export function ProjectsIcon(props: IconProps) { - return ( - - - - - ); -} - -export function GoalIcon(props: IconProps) { - return ( - - - - - - - - ); -} - -export function QueueIcon(props: IconProps) { - return ( - - - - - - - - ); -} - -export function BoardIcon(props: IconProps) { - return ( - - - - - - ); -} - -export function DashboardIcon(props: IconProps) { - return ( - - - - - - - ); -} - -export function HistoryIcon(props: IconProps) { - return ( - - - - - - ); -} - -export function BellIcon(props: IconProps) { - return ( - - - - - ); -} - -export function HelpIcon(props: IconProps) { - return ( - - - - - - ); -} - -export function SettingsIcon(props: IconProps) { - return ( - - - - - - - - - - - - ); -} - -export function ChevronDownIcon(props: IconProps) { - return ( - - - - ); -} - -export function StarIcon(props: IconProps) { - return ( - - - - ); -} - -export function LinkIcon(props: IconProps) { - return ( - - - - - - ); -} - -export function CopyIcon(props: IconProps) { - return ( - - - - - ); -} - -export function FilterIcon(props: IconProps) { - return ( - - - - - - ); -} - -export function MoreIcon(props: IconProps) { - return ( - - - - - - ); -} - -export function UserIcon(props: IconProps) { - return ( - - - - - ); -} - -export function CalendarIcon(props: IconProps) { - return ( - - - - - - - ); -} - -export function CommentIcon(props: IconProps) { - return ( - - - - ); -} - -export function ActivityIcon(props: IconProps) { - return ( - - - - ); -} - -export function AttachmentIcon(props: IconProps) { - return ( - - - - ); -} - -export function CheckCircleIcon(props: IconProps) { - return ( - - - - - ); -} - -export function PauseCircleIcon(props: IconProps) { - return ( - - - - - - ); -} - -export function SparkIcon(props: IconProps) { - return ( - - - - ); -} +import type { LucideProps } from "lucide-react"; + +export type IconProps = LucideProps; +export { + Activity as ActivityIcon, + Bell as BellIcon, + CalendarDays as CalendarIcon, + CheckCircle2 as CheckCircleIcon, + ChevronDown as ChevronDownIcon, + CircleHelp as HelpIcon, + CirclePause as PauseCircleIcon, + Columns3 as BoardIcon, + Copy as CopyIcon, + Ellipsis as MoreIcon, + Filter as FilterIcon, + FolderKanban as ProjectsIcon, + History as HistoryIcon, + LayoutDashboard as DashboardIcon, + Link as LinkIcon, + List as ListIcon, + ListTodo as QueueIcon, + MessageSquare as CommentIcon, + PanelLeft as SplitViewIcon, + Paperclip as AttachmentIcon, + Plus as PlusIcon, + Search as SearchIcon, + Settings as SettingsIcon, + Sparkles as SparkIcon, + Star as StarIcon, + Target as GoalIcon, + User as UserIcon, +} from "lucide-react"; diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts index ff9a828..263d532 100644 --- a/apps/web/tailwind.config.ts +++ b/apps/web/tailwind.config.ts @@ -6,19 +6,30 @@ const config: Config = { theme: { extend: { colors: { - surface: "var(--surface)", - card: "var(--card)", - border: "var(--border)", - muted: "var(--muted)", - text: "var(--text)", - accent: "var(--accent)", - success: "var(--success)", - warning: "var(--warning)", - danger: "var(--danger)", + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" }, + secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))" }, + destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))" }, + muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))" }, + accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))" }, + popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))" }, + card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))" }, + surface: "hsl(var(--background))", + text: "hsl(var(--foreground))", + success: "hsl(var(--success))", + warning: "hsl(var(--warning))", + danger: "hsl(var(--destructive))", }, - boxShadow: { - soft: "0 10px 30px rgba(0, 0, 0, 0.08)", + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", }, + boxShadow: { soft: "0 16px 40px hsl(222 47% 11% / 0.08)" }, }, }, plugins: [], diff --git a/packages/ui/package.json b/packages/ui/package.json index f7bb363..3cf4544 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -13,7 +13,14 @@ "react-dom": "^19.0.0" }, "dependencies": { - "clsx": "^2.1.1" + "@radix-ui/react-dialog": "^1.1.23", + "@radix-ui/react-dropdown-menu": "^2.1.24", + "@radix-ui/react-separator": "^1.1.15", + "@radix-ui/react-slot": "^1.3.3", + "@radix-ui/react-tooltip": "^1.2.16", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@types/react": "^19.2.18", diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index e1a229e..7147a76 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,7 +1,13 @@ +export * from "./lib/badge"; export * from "./lib/button"; export * from "./lib/card"; +export * from "./lib/dialog"; +export * from "./lib/dropdown-menu"; export * from "./lib/input"; +export * from "./lib/modal"; export * from "./lib/select"; +export * from "./lib/separator"; +export * from "./lib/skeleton"; export * from "./lib/textarea"; -export * from "./lib/badge"; -export * from "./lib/modal"; +export * from "./lib/tooltip"; +export * from "./lib/utils"; diff --git a/packages/ui/src/lib/badge.tsx b/packages/ui/src/lib/badge.tsx index b3ef6ed..d686c82 100644 --- a/packages/ui/src/lib/badge.tsx +++ b/packages/ui/src/lib/badge.tsx @@ -1,21 +1,30 @@ -import type { PropsWithChildren } from "react"; -import clsx from "clsx"; +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "./utils"; -interface BadgeProps extends PropsWithChildren { - tone?: "neutral" | "success" | "warning" | "danger"; -} +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: "border-transparent bg-primary text-primary-foreground", + secondary: "border-transparent bg-secondary text-secondary-foreground", + destructive: "border-transparent bg-destructive text-destructive-foreground", + outline: "text-foreground", + }, + tone: { + neutral: "border-transparent bg-secondary text-secondary-foreground", + success: "border-transparent bg-emerald-500/12 text-emerald-700 dark:text-emerald-400", + warning: "border-transparent bg-amber-500/14 text-amber-700 dark:text-amber-400", + danger: "border-transparent bg-destructive/12 text-destructive", + }, + }, + defaultVariants: { variant: "secondary", tone: "neutral" }, + }, +); + +export interface BadgeProps extends React.HTMLAttributes, VariantProps {} -export function Badge({ children, tone = "neutral" }: BadgeProps) { - return ( - - {children} - - ); +export function Badge({ className, variant, tone, ...props }: BadgeProps) { + return ; } diff --git a/packages/ui/src/lib/button.tsx b/packages/ui/src/lib/button.tsx index d434e9f..5b835a5 100644 --- a/packages/ui/src/lib/button.tsx +++ b/packages/ui/src/lib/button.tsx @@ -1,30 +1,41 @@ -import type { ButtonHTMLAttributes, PropsWithChildren } from "react"; -import clsx from "clsx"; +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "./utils"; -type ButtonVariant = "primary" | "secondary" | "ghost" | "danger"; +export const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ring-offset-background disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", + primary: "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + danger: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { variant: "default", size: "default" }, + }, +); -interface ButtonProps extends PropsWithChildren, ButtonHTMLAttributes { - variant?: ButtonVariant; +export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { + asChild?: boolean; } -export function Button({ children, className, variant = "secondary", ...props }: ButtonProps) { - return ( - - ); -} +export const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ; + }, +); +Button.displayName = "Button"; diff --git a/packages/ui/src/lib/card.tsx b/packages/ui/src/lib/card.tsx index a4ea3eb..3e0ffd1 100644 --- a/packages/ui/src/lib/card.tsx +++ b/packages/ui/src/lib/card.tsx @@ -1,18 +1,27 @@ -import type { HTMLAttributes, PropsWithChildren } from "react"; -import clsx from "clsx"; +import * as React from "react"; +import { cn } from "./utils"; -interface CardProps extends PropsWithChildren, HTMLAttributes {} - -export function Card({ children, className, ...props }: CardProps) { - return ( -
- {children} -
- ); -} +export const Card = React.forwardRef>( + ({ className, ...props }, ref) =>
, +); +Card.displayName = "Card"; +export const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) =>
, +); +CardHeader.displayName = "CardHeader"; +export const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) =>

, +); +CardTitle.displayName = "CardTitle"; +export const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) =>

, +); +CardDescription.displayName = "CardDescription"; +export const CardContent = React.forwardRef>( + ({ className, ...props }, ref) =>

, +); +CardContent.displayName = "CardContent"; +export const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) =>
, +); +CardFooter.displayName = "CardFooter"; diff --git a/packages/ui/src/lib/dialog.tsx b/packages/ui/src/lib/dialog.tsx new file mode 100644 index 0000000..4383c56 --- /dev/null +++ b/packages/ui/src/lib/dialog.tsx @@ -0,0 +1,44 @@ +"use client"; + +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { cn } from "./utils"; + +export const Dialog = DialogPrimitive.Root; +export const DialogTrigger = DialogPrimitive.Trigger; +export const DialogClose = DialogPrimitive.Close; + +export const DialogOverlay = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => , +); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +export const DialogContent = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, children, ...props }, ref) => ( + + + + {children} + + ×Закрыть + + + + ), +); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +export function DialogHeader({ className, ...props }: React.HTMLAttributes) { + return
; +} +export function DialogFooter({ className, ...props }: React.HTMLAttributes) { + return
; +} +export const DialogTitle = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => , +); +DialogTitle.displayName = DialogPrimitive.Title.displayName; +export const DialogDescription = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => , +); +DialogDescription.displayName = DialogPrimitive.Description.displayName; diff --git a/packages/ui/src/lib/dropdown-menu.tsx b/packages/ui/src/lib/dropdown-menu.tsx new file mode 100644 index 0000000..64d2a27 --- /dev/null +++ b/packages/ui/src/lib/dropdown-menu.tsx @@ -0,0 +1,29 @@ +"use client"; + +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { cn } from "./utils"; + +export const DropdownMenu = DropdownMenuPrimitive.Root; +export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +export const DropdownMenuGroup = DropdownMenuPrimitive.Group; +export const DropdownMenuPortal = DropdownMenuPrimitive.Portal; +export const DropdownMenuSub = DropdownMenuPrimitive.Sub; +export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +export const DropdownMenuContent = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, sideOffset = 4, ...props }, ref) => , +); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; +export const DropdownMenuItem = React.forwardRef, React.ComponentPropsWithoutRef & { inset?: boolean }>( + ({ className, inset, ...props }, ref) => , +); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; +export const DropdownMenuLabel = React.forwardRef, React.ComponentPropsWithoutRef & { inset?: boolean }>( + ({ className, inset, ...props }, ref) => , +); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; +export const DropdownMenuSeparator = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, ...props }, ref) => , +); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; diff --git a/packages/ui/src/lib/input.tsx b/packages/ui/src/lib/input.tsx index 2daefc0..b0e3244 100644 --- a/packages/ui/src/lib/input.tsx +++ b/packages/ui/src/lib/input.tsx @@ -1,15 +1,7 @@ -import type { ComponentPropsWithRef } from "react"; -import clsx from "clsx"; +import * as React from "react"; +import { cn } from "./utils"; -export function Input({ className, ...props }: ComponentPropsWithRef<"input">) { - return ( - - ); -} +export const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => , +); +Input.displayName = "Input"; diff --git a/packages/ui/src/lib/modal.tsx b/packages/ui/src/lib/modal.tsx index cab7a62..5b7b6b9 100644 --- a/packages/ui/src/lib/modal.tsx +++ b/packages/ui/src/lib/modal.tsx @@ -1,4 +1,5 @@ import type { PropsWithChildren, ReactNode } from "react"; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./dialog"; interface ModalProps extends PropsWithChildren { title: ReactNode; @@ -8,31 +9,14 @@ interface ModalProps extends PropsWithChildren { export function Modal({ children, title, subtitle, onClose }: ModalProps) { return ( -
-
event.stopPropagation()} - > -
-
-

{title}

- {subtitle ?

{subtitle}

: null} -
- -
+ { if (!open) onClose(); }}> + + + {title} + {subtitle ? {subtitle} : null} + {children} -
-
+ + ); } diff --git a/packages/ui/src/lib/select.tsx b/packages/ui/src/lib/select.tsx index 38e0313..e169517 100644 --- a/packages/ui/src/lib/select.tsx +++ b/packages/ui/src/lib/select.tsx @@ -1,15 +1,7 @@ -import type { SelectHTMLAttributes } from "react"; -import clsx from "clsx"; +import * as React from "react"; +import { cn } from "./utils"; -export function Select({ className, ...props }: SelectHTMLAttributes) { - return ( - , +); +Select.displayName = "Select"; diff --git a/packages/ui/src/lib/separator.tsx b/packages/ui/src/lib/separator.tsx new file mode 100644 index 0000000..0376615 --- /dev/null +++ b/packages/ui/src/lib/separator.tsx @@ -0,0 +1,8 @@ +import * as React from "react"; +import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import { cn } from "./utils"; + +export const Separator = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ className, orientation = "horizontal", decorative = true, ...props }, ref) => , +); +Separator.displayName = SeparatorPrimitive.Root.displayName; diff --git a/packages/ui/src/lib/skeleton.tsx b/packages/ui/src/lib/skeleton.tsx new file mode 100644 index 0000000..2579d7d --- /dev/null +++ b/packages/ui/src/lib/skeleton.tsx @@ -0,0 +1,6 @@ +import type { HTMLAttributes } from "react"; +import { cn } from "./utils"; + +export function Skeleton({ className, ...props }: HTMLAttributes) { + return
; +} diff --git a/packages/ui/src/lib/textarea.tsx b/packages/ui/src/lib/textarea.tsx index 02740a1..d3f0366 100644 --- a/packages/ui/src/lib/textarea.tsx +++ b/packages/ui/src/lib/textarea.tsx @@ -1,15 +1,7 @@ -import type { TextareaHTMLAttributes } from "react"; -import clsx from "clsx"; +import * as React from "react"; +import { cn } from "./utils"; -export function Textarea({ className, ...props }: TextareaHTMLAttributes) { - return ( -