@cortexasystem/ui 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/index.cjs +1326 -643
  2. package/dist/index.d.cts +171 -13
  3. package/dist/index.d.ts +171 -13
  4. package/dist/index.js +1307 -646
  5. package/package.json +3 -3
  6. package/src/assets/isotipo-cortexa-dark.png +0 -0
  7. package/src/assets/isotipo-cortexa-light.png +0 -0
  8. package/src/components/ai/ai-chat.tsx +597 -0
  9. package/src/components/branding/brand-logo.tsx +77 -0
  10. package/src/components/data-display/icons.tsx +81 -0
  11. package/src/components/data-display/profile-avatar.tsx +154 -0
  12. package/src/components/data-display/typography.tsx +46 -0
  13. package/src/components/feedback/empty-state.tsx +63 -0
  14. package/src/components/feedback/loading-state.tsx +93 -0
  15. package/src/components/feedback/module-skeleton.tsx +76 -0
  16. package/src/components/feedback/notification.tsx +111 -0
  17. package/src/components/feedback/skeleton.tsx +9 -0
  18. package/src/components/feedback/spinner.tsx +18 -0
  19. package/src/components/feedback/status-badge.tsx +44 -0
  20. package/src/components/feedback/sync-status-badge.tsx +54 -0
  21. package/src/components/feedback/sync-status-bar.tsx +92 -0
  22. package/src/components/feedback/toaster.tsx +36 -0
  23. package/src/components/forms/searchable-select.tsx +206 -0
  24. package/src/components/forms/select.tsx +142 -0
  25. package/src/components/layout/app-shell.tsx +44 -0
  26. package/src/components/layout/form-section.tsx +21 -0
  27. package/src/components/layout/page-header.tsx +21 -0
  28. package/src/components/layout/theme-toggle.tsx +33 -0
  29. package/src/components/navigation/breadcrumb.tsx +87 -0
  30. package/src/components/navigation/header-user-menu.tsx +108 -0
  31. package/src/components/navigation/navbar.tsx +30 -0
  32. package/src/components/navigation/page-breadcrumb.tsx +44 -0
  33. package/src/components/navigation/sidebar.tsx +104 -0
  34. package/src/components/navigation/steps.tsx +82 -0
  35. package/src/components/overlays/dialog.tsx +94 -0
  36. package/src/components/overlays/drawer.tsx +85 -0
  37. package/src/components/overlays/dropdown-menu.tsx +179 -0
  38. package/src/components/overlays/sheet.tsx +110 -0
  39. package/src/components/primitives/alert.tsx +43 -0
  40. package/src/components/primitives/avatar.tsx +41 -0
  41. package/src/components/primitives/badge.tsx +26 -0
  42. package/src/components/primitives/button.tsx +49 -0
  43. package/src/components/primitives/card.tsx +97 -0
  44. package/src/components/primitives/checkbox.tsx +52 -0
  45. package/src/components/primitives/input.tsx +23 -0
  46. package/src/components/primitives/label.tsx +18 -0
  47. package/src/components/primitives/radio-group.tsx +57 -0
  48. package/src/components/primitives/separator.tsx +23 -0
  49. package/src/components/primitives/switch.tsx +75 -0
  50. package/src/components/primitives/textarea.tsx +18 -0
  51. package/src/components/tables/data-table.tsx +214 -0
  52. package/src/components/tables/data-table.types.ts +9 -0
  53. package/src/components/tables/table-row-actions.tsx +61 -0
  54. package/src/components/tables/table.tsx +88 -0
  55. package/src/declarations.d.ts +14 -0
  56. package/src/index.ts +50 -0
  57. package/src/lib/cn.ts +6 -0
  58. package/src/providers/theme-provider.tsx +90 -0
  59. package/src/styles.css +1 -1
@@ -0,0 +1,57 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/cn";
4
+
5
+ const RadioGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
6
+ <div ref={ref} role="radiogroup" className={cn("grid gap-3", className)} {...props} />
7
+ ));
8
+
9
+ RadioGroup.displayName = "RadioGroup";
10
+
11
+ type RadioGroupItemProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "type">;
12
+
13
+ const RadioGroupItem = React.forwardRef<HTMLInputElement, RadioGroupItemProps>(({ className, ...props }, ref) => (
14
+ <span className="relative inline-flex h-4 w-4 shrink-0">
15
+ <input
16
+ ref={ref}
17
+ type="radio"
18
+ className={cn(
19
+ "peer absolute inset-0 h-4 w-4 cursor-pointer appearance-none rounded-full border border-input bg-background shadow-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 checked:border-primary",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ <span
25
+ aria-hidden="true"
26
+ className="pointer-events-none absolute left-1/2 top-1/2 h-2 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary opacity-0 transition-opacity peer-checked:opacity-100"
27
+ />
28
+ </span>
29
+ ));
30
+
31
+ RadioGroupItem.displayName = "RadioGroupItem";
32
+
33
+ interface RadioFieldProps extends RadioGroupItemProps {
34
+ label: React.ReactNode;
35
+ description?: React.ReactNode;
36
+ containerClassName?: string;
37
+ }
38
+
39
+ function RadioField({ id, label, description, containerClassName, className, ...props }: RadioFieldProps) {
40
+ const generatedId = React.useId();
41
+ const inputId = id ?? generatedId;
42
+
43
+ return (
44
+ <label
45
+ htmlFor={inputId}
46
+ className={cn("flex items-center gap-3 rounded-lg border border-border bg-card p-3 transition-colors hover:bg-accent/30", containerClassName)}
47
+ >
48
+ <RadioGroupItem id={inputId} className={className} {...props} />
49
+ <span className="grid gap-1">
50
+ <span className="text-sm font-medium text-foreground">{label}</span>
51
+ {description ? <span className="text-xs text-muted-foreground">{description}</span> : null}
52
+ </span>
53
+ </label>
54
+ );
55
+ }
56
+
57
+ export { RadioGroup, RadioGroupItem, RadioField };
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
5
+
6
+ import { cn } from "../../lib/cn";
7
+
8
+ const Separator = React.forwardRef<
9
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
11
+ >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
12
+ <SeparatorPrimitive.Root
13
+ ref={ref}
14
+ decorative={decorative}
15
+ orientation={orientation}
16
+ className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className)}
17
+ {...props}
18
+ />
19
+ ));
20
+
21
+ Separator.displayName = SeparatorPrimitive.Root.displayName;
22
+
23
+ export { Separator };
@@ -0,0 +1,75 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/cn";
4
+
5
+ type SwitchProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size"> & {
6
+ size?: "default" | "sm";
7
+ };
8
+
9
+ const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(({ className, size = "default", ...props }, ref) => {
10
+ const isSm = size === "sm";
11
+
12
+ return (
13
+ <span
14
+ className={cn(
15
+ "relative inline-flex shrink-0",
16
+ isSm ? "h-4 w-7" : "h-6 w-10"
17
+ )}
18
+ >
19
+ <input
20
+ ref={ref}
21
+ type="checkbox"
22
+ role="switch"
23
+ className={cn(
24
+ "peer absolute inset-0 cursor-pointer appearance-none rounded-full border border-input bg-input",
25
+ "transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
26
+ "disabled:cursor-not-allowed disabled:opacity-50",
27
+ "checked:border-primary checked:bg-primary",
28
+ className
29
+ )}
30
+ {...props}
31
+ />
32
+ <span
33
+ aria-hidden="true"
34
+ className={cn(
35
+ "pointer-events-none absolute rounded-full bg-background shadow-sm transition-transform",
36
+ isSm
37
+ ? "top-0.5 left-0.5 h-3 w-3 peer-checked:translate-x-3"
38
+ : "top-1 left-1 h-4 w-4 peer-checked:translate-x-4"
39
+ )}
40
+ />
41
+ </span>
42
+ );
43
+ });
44
+
45
+ Switch.displayName = "Switch";
46
+
47
+ interface SwitchFieldProps extends SwitchProps {
48
+ label: React.ReactNode;
49
+ description?: React.ReactNode;
50
+ containerClassName?: string;
51
+ }
52
+
53
+ function SwitchField({ id, label, description, containerClassName, size, className, ...props }: SwitchFieldProps) {
54
+ const generatedId = React.useId();
55
+ const inputId = id ?? generatedId;
56
+
57
+ return (
58
+ <label
59
+ htmlFor={inputId}
60
+ className={cn(
61
+ "flex cursor-pointer items-center justify-between gap-4 rounded-lg border border-border bg-card p-3 transition-colors hover:bg-accent/30",
62
+ props.disabled && "cursor-not-allowed opacity-50 hover:bg-card",
63
+ containerClassName
64
+ )}
65
+ >
66
+ <span className="grid gap-0.5">
67
+ <span className="text-sm font-medium text-foreground">{label}</span>
68
+ {description ? <span className="text-xs text-muted-foreground">{description}</span> : null}
69
+ </span>
70
+ <Switch id={inputId} size={size} className={className} {...props} />
71
+ </label>
72
+ );
73
+ }
74
+
75
+ export { Switch, SwitchField };
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/cn";
4
+
5
+ const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(({ className, ...props }, ref) => (
6
+ <textarea
7
+ ref={ref}
8
+ className={cn(
9
+ "flex min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ ));
15
+
16
+ Textarea.displayName = "Textarea";
17
+
18
+ export { Textarea };
@@ -0,0 +1,214 @@
1
+ import { useMemo, useState, type ReactNode } from "react";
2
+ import { ChevronLeft, ChevronRight, Search } from "lucide-react";
3
+
4
+ import { cn } from "../../lib/cn";
5
+ import { Skeleton } from "../feedback/skeleton";
6
+ import { Button } from "../primitives/button";
7
+ import { Card, CardContent } from "../primitives/card";
8
+ import { Input } from "../primitives/input";
9
+ import {
10
+ Table,
11
+ TableBody,
12
+ TableCell,
13
+ TableHead,
14
+ TableHeader,
15
+ TableRow
16
+ } from "./table";
17
+ import type { ColumnDef } from "./data-table.types";
18
+
19
+ interface DataTableProps<T> {
20
+ data: T[];
21
+ columns: ColumnDef<T>[];
22
+ isLoading?: boolean;
23
+ actions?: (row: T) => ReactNode;
24
+ emptyMessage?: string;
25
+ pageSize?: number;
26
+ }
27
+
28
+ function getCellContent<T>(row: T, column: ColumnDef<T>): ReactNode {
29
+ if (column.render) {
30
+ return column.render(row);
31
+ }
32
+
33
+ const value = (row as Record<string, unknown>)[column.key as string];
34
+
35
+ if (value === null || value === undefined) {
36
+ return <span className="text-muted-foreground/40">-</span>;
37
+ }
38
+
39
+ if (typeof value === "boolean") {
40
+ return value ? "Yes" : "No";
41
+ }
42
+
43
+ return String(value);
44
+ }
45
+
46
+ const SKELETON_ROWS = 5;
47
+
48
+ export function DataTable<T>({
49
+ data,
50
+ columns,
51
+ isLoading = false,
52
+ actions,
53
+ emptyMessage = "No records found",
54
+ pageSize = 10
55
+ }: DataTableProps<T>) {
56
+ const [search, setSearch] = useState("");
57
+ const [page, setPage] = useState(0);
58
+
59
+ const filtered = useMemo(() => {
60
+ const query = search.toLowerCase().trim();
61
+
62
+ if (!query) {
63
+ return data;
64
+ }
65
+
66
+ return data.filter((row) =>
67
+ Object.values(row as Record<string, unknown>).some(
68
+ (value) => value != null && String(value).toLowerCase().includes(query)
69
+ )
70
+ );
71
+ }, [data, search]);
72
+
73
+ const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
74
+ const currentPage = Math.min(page, totalPages - 1);
75
+ const paginated = filtered.slice(currentPage * pageSize, (currentPage + 1) * pageSize);
76
+ const mobileColumns = columns.filter((column) => !column.mobileHide);
77
+
78
+ function handleSearch(value: string) {
79
+ setSearch(value);
80
+ setPage(0);
81
+ }
82
+
83
+ return (
84
+ <div className="space-y-4">
85
+ <div className="relative">
86
+ <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
87
+ <Input
88
+ placeholder="Search..."
89
+ value={search}
90
+ onChange={(event) => handleSearch(event.target.value)}
91
+ className="pl-9"
92
+ />
93
+ </div>
94
+
95
+ <div className="hidden rounded-lg border md:block">
96
+ <Table>
97
+ <TableHeader>
98
+ <TableRow>
99
+ {columns.map((column) => (
100
+ <TableHead key={String(column.key)}>{column.label}</TableHead>
101
+ ))}
102
+ {actions ? <TableHead className="w-[100px] text-right">Actions</TableHead> : null}
103
+ </TableRow>
104
+ </TableHeader>
105
+ <TableBody>
106
+ {isLoading ? (
107
+ Array.from({ length: SKELETON_ROWS }).map((_, index) => (
108
+ <TableRow key={index}>
109
+ {columns.map((column) => (
110
+ <TableCell key={String(column.key)}>
111
+ <Skeleton className="h-4 w-full" />
112
+ </TableCell>
113
+ ))}
114
+ {actions ? (
115
+ <TableCell>
116
+ <Skeleton className="ml-auto h-8 w-20" />
117
+ </TableCell>
118
+ ) : null}
119
+ </TableRow>
120
+ ))
121
+ ) : paginated.length === 0 ? (
122
+ <TableRow>
123
+ <TableCell colSpan={columns.length + (actions ? 1 : 0)} className="py-12 text-center text-muted-foreground">
124
+ {emptyMessage}
125
+ </TableCell>
126
+ </TableRow>
127
+ ) : (
128
+ paginated.map((row, index) => (
129
+ <TableRow key={index}>
130
+ {columns.map((column) => (
131
+ <TableCell key={String(column.key)}>{getCellContent(row, column)}</TableCell>
132
+ ))}
133
+ {actions ? (
134
+ <TableCell>
135
+ <div className="flex justify-end gap-1.5">{actions(row)}</div>
136
+ </TableCell>
137
+ ) : null}
138
+ </TableRow>
139
+ ))
140
+ )}
141
+ </TableBody>
142
+ </Table>
143
+ </div>
144
+
145
+ <div className="space-y-3 md:hidden">
146
+ {isLoading ? (
147
+ Array.from({ length: SKELETON_ROWS }).map((_, index) => (
148
+ <Card key={index}>
149
+ <CardContent className="space-y-2 p-4">
150
+ <Skeleton className="h-5 w-1/2" />
151
+ <Skeleton className="h-4 w-2/3" />
152
+ <Skeleton className="h-4 w-1/3" />
153
+ </CardContent>
154
+ </Card>
155
+ ))
156
+ ) : paginated.length === 0 ? (
157
+ <p className="py-10 text-center text-muted-foreground">{emptyMessage}</p>
158
+ ) : (
159
+ paginated.map((row, index) => (
160
+ <Card key={index}>
161
+ <CardContent className="space-y-3 p-4">
162
+ <div className="space-y-1.5">
163
+ {mobileColumns.map((column, mobileIndex) => (
164
+ <div
165
+ key={String(column.key)}
166
+ className={cn(
167
+ "flex items-center gap-1.5",
168
+ mobileIndex === 0 ? "text-sm font-medium text-foreground" : "text-xs text-muted-foreground"
169
+ )}
170
+ >
171
+ {mobileIndex > 0 ? (
172
+ <span className="shrink-0 font-medium text-foreground/50">{column.label}:</span>
173
+ ) : null}
174
+ <span className="truncate">{getCellContent(row, column)}</span>
175
+ </div>
176
+ ))}
177
+ </div>
178
+ {actions ? <div className="flex gap-2 border-t border-border pt-2">{actions(row)}</div> : null}
179
+ </CardContent>
180
+ </Card>
181
+ ))
182
+ )}
183
+ </div>
184
+
185
+ {!isLoading && filtered.length > pageSize ? (
186
+ <div className="flex items-center justify-between">
187
+ <span className="text-sm text-muted-foreground">
188
+ {filtered.length} records · Page {currentPage + 1} of {totalPages}
189
+ </span>
190
+ <div className="flex gap-1">
191
+ <Button
192
+ variant="outline"
193
+ size="icon"
194
+ className="h-8 w-8"
195
+ onClick={() => setPage((previous) => Math.max(0, previous - 1))}
196
+ disabled={currentPage === 0}
197
+ >
198
+ <ChevronLeft className="h-4 w-4" />
199
+ </Button>
200
+ <Button
201
+ variant="outline"
202
+ size="icon"
203
+ className="h-8 w-8"
204
+ onClick={() => setPage((previous) => Math.min(totalPages - 1, previous + 1))}
205
+ disabled={currentPage >= totalPages - 1}
206
+ >
207
+ <ChevronRight className="h-4 w-4" />
208
+ </Button>
209
+ </div>
210
+ </div>
211
+ ) : null}
212
+ </div>
213
+ );
214
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export interface ColumnDef<T> {
4
+ key: keyof T | string;
5
+ label: string;
6
+ render?: (row: T) => ReactNode;
7
+ mobileHide?: boolean;
8
+ sortable?: boolean;
9
+ }
@@ -0,0 +1,61 @@
1
+ import { MoreHorizontal } from "lucide-react";
2
+ import { type ReactNode } from "react";
3
+
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuLabel,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuShortcut,
11
+ DropdownMenuTrigger
12
+ } from "../overlays/dropdown-menu";
13
+ import { Button } from "../primitives/button";
14
+ import { cn } from "../../lib/cn";
15
+
16
+ interface TableActionItem {
17
+ label: string;
18
+ icon?: ReactNode;
19
+ shortcut?: string;
20
+ disabled?: boolean;
21
+ destructive?: boolean;
22
+ onSelect?: () => void;
23
+ }
24
+
25
+ interface TableRowActionsProps {
26
+ className?: string;
27
+ label?: string;
28
+ menuLabel?: string;
29
+ items: TableActionItem[];
30
+ }
31
+
32
+ function TableRowActions({ className, label = "Abrir acciones", menuLabel = "Acciones", items }: TableRowActionsProps) {
33
+ return (
34
+ <DropdownMenu>
35
+ <DropdownMenuTrigger asChild>
36
+ <Button variant="ghost" size="icon" className={cn("h-8 w-8 shadow-none", className)} aria-label={label}>
37
+ <MoreHorizontal className="h-4 w-4" />
38
+ </Button>
39
+ </DropdownMenuTrigger>
40
+ <DropdownMenuContent align="end" className="w-48">
41
+ <DropdownMenuLabel>{menuLabel}</DropdownMenuLabel>
42
+ <DropdownMenuSeparator />
43
+ {items.map((item) => (
44
+ <DropdownMenuItem
45
+ key={item.label}
46
+ disabled={item.disabled}
47
+ onSelect={item.onSelect}
48
+ className={cn(item.destructive && "text-destructive focus:bg-destructive/10 focus:text-destructive")}
49
+ >
50
+ {item.icon}
51
+ {item.label}
52
+ {item.shortcut ? <DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut> : null}
53
+ </DropdownMenuItem>
54
+ ))}
55
+ </DropdownMenuContent>
56
+ </DropdownMenu>
57
+ );
58
+ }
59
+
60
+ export { TableRowActions };
61
+ export type { TableActionItem };
@@ -0,0 +1,88 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/cn";
4
+
5
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(({ className, ...props }, ref) => (
6
+ <div className="relative w-full overflow-auto">
7
+ <table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
8
+ </div>
9
+ ));
10
+
11
+ Table.displayName = "Table";
12
+
13
+ const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
14
+ ({ className, ...props }, ref) => <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
15
+ );
16
+
17
+ TableHeader.displayName = "TableHeader";
18
+
19
+ const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
20
+ ({ className, ...props }, ref) => (
21
+ <tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
22
+ )
23
+ );
24
+
25
+ TableBody.displayName = "TableBody";
26
+
27
+ const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
28
+ ({ className, ...props }, ref) => (
29
+ <tfoot
30
+ ref={ref}
31
+ className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
32
+ {...props}
33
+ />
34
+ )
35
+ );
36
+
37
+ TableFooter.displayName = "TableFooter";
38
+
39
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
40
+ ({ className, ...props }, ref) => (
41
+ <tr
42
+ ref={ref}
43
+ className={cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className)}
44
+ {...props}
45
+ />
46
+ )
47
+ );
48
+
49
+ TableRow.displayName = "TableRow";
50
+
51
+ const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
52
+ ({ className, ...props }, ref) => (
53
+ <th
54
+ ref={ref}
55
+ className={cn("h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", className)}
56
+ {...props}
57
+ />
58
+ )
59
+ );
60
+
61
+ TableHead.displayName = "TableHead";
62
+
63
+ const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
64
+ ({ className, ...props }, ref) => (
65
+ <td ref={ref} className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)} {...props} />
66
+ )
67
+ );
68
+
69
+ TableCell.displayName = "TableCell";
70
+
71
+ const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
72
+ ({ className, ...props }, ref) => (
73
+ <caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
74
+ )
75
+ );
76
+
77
+ TableCaption.displayName = "TableCaption";
78
+
79
+ export {
80
+ Table,
81
+ TableHeader,
82
+ TableBody,
83
+ TableFooter,
84
+ TableHead,
85
+ TableRow,
86
+ TableCell,
87
+ TableCaption
88
+ };
@@ -0,0 +1,14 @@
1
+ declare module "*.png" {
2
+ const src: string;
3
+ export default src;
4
+ }
5
+
6
+ declare module "*.jpg" {
7
+ const src: string;
8
+ export default src;
9
+ }
10
+
11
+ declare module "*.svg" {
12
+ const src: string;
13
+ export default src;
14
+ }
package/src/index.ts ADDED
@@ -0,0 +1,50 @@
1
+ export * from "./providers/theme-provider";
2
+ export * from "./lib/cn";
3
+
4
+ export * from "./components/ai/ai-chat";
5
+ export * from "./components/branding/brand-logo";
6
+ export * from "./components/data-display/icons";
7
+ export * from "./components/data-display/profile-avatar";
8
+ export * from "./components/data-display/typography";
9
+ export * from "./components/feedback/empty-state";
10
+ export * from "./components/feedback/loading-state";
11
+ export * from "./components/feedback/module-skeleton";
12
+ export * from "./components/feedback/notification";
13
+ export * from "./components/feedback/skeleton";
14
+ export * from "./components/feedback/spinner";
15
+ export * from "./components/feedback/status-badge";
16
+ export * from "./components/feedback/sync-status-badge";
17
+ export * from "./components/feedback/sync-status-bar";
18
+ export * from "./components/feedback/toaster";
19
+ export * from "./components/forms/searchable-select";
20
+ export * from "./components/forms/select";
21
+ export * from "./components/layout/app-shell";
22
+ export * from "./components/layout/form-section";
23
+ export * from "./components/layout/page-header";
24
+ export * from "./components/layout/theme-toggle";
25
+ export * from "./components/navigation/breadcrumb";
26
+ export * from "./components/navigation/header-user-menu";
27
+ export * from "./components/navigation/navbar";
28
+ export * from "./components/navigation/page-breadcrumb";
29
+ export * from "./components/navigation/sidebar";
30
+ export * from "./components/navigation/steps";
31
+ export * from "./components/overlays/dialog";
32
+ export * from "./components/overlays/drawer";
33
+ export * from "./components/overlays/dropdown-menu";
34
+ export * from "./components/overlays/sheet";
35
+ export * from "./components/primitives/alert";
36
+ export * from "./components/primitives/avatar";
37
+ export * from "./components/primitives/badge";
38
+ export * from "./components/primitives/button";
39
+ export * from "./components/primitives/card";
40
+ export * from "./components/primitives/checkbox";
41
+ export * from "./components/primitives/input";
42
+ export * from "./components/primitives/label";
43
+ export * from "./components/primitives/radio-group";
44
+ export * from "./components/primitives/separator";
45
+ export * from "./components/primitives/switch";
46
+ export * from "./components/primitives/textarea";
47
+ export * from "./components/tables/data-table";
48
+ export * from "./components/tables/data-table.types";
49
+ export * from "./components/tables/table";
50
+ export * from "./components/tables/table-row-actions";
package/src/lib/cn.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }