@vllnt/ui 0.1.11 → 0.2.0

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 (100) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +106 -1
  3. package/dist/components/activity-heatmap/activity-heatmap.js +168 -0
  4. package/dist/components/activity-heatmap/index.js +6 -0
  5. package/dist/components/activity-log/activity-log.js +256 -0
  6. package/dist/components/activity-log/index.js +6 -0
  7. package/dist/components/ai-chat-input/ai-chat-input.js +107 -0
  8. package/dist/components/ai-chat-input/index.js +4 -0
  9. package/dist/components/ai-message-bubble/ai-message-bubble.js +119 -0
  10. package/dist/components/ai-message-bubble/index.js +6 -0
  11. package/dist/components/ai-source-citation/ai-source-citation.js +39 -0
  12. package/dist/components/ai-source-citation/index.js +6 -0
  13. package/dist/components/ai-streaming-text/ai-streaming-text.js +41 -0
  14. package/dist/components/ai-streaming-text/index.js +6 -0
  15. package/dist/components/ai-tool-call-display/ai-tool-call-display.js +93 -0
  16. package/dist/components/ai-tool-call-display/index.js +6 -0
  17. package/dist/components/animated-text/animated-text.js +328 -0
  18. package/dist/components/animated-text/index.js +4 -0
  19. package/dist/components/annotation/annotation.js +49 -0
  20. package/dist/components/annotation/index.js +8 -0
  21. package/dist/components/avatar-group/avatar-group.js +82 -0
  22. package/dist/components/avatar-group/index.js +10 -0
  23. package/dist/components/border-beam/border-beam.js +51 -0
  24. package/dist/components/border-beam/index.js +4 -0
  25. package/dist/components/candlestick-chart/candlestick-chart.js +215 -0
  26. package/dist/components/candlestick-chart/index.js +6 -0
  27. package/dist/components/combobox/combobox.js +130 -0
  28. package/dist/components/combobox/index.js +4 -0
  29. package/dist/components/countdown-timer/countdown-timer.js +184 -0
  30. package/dist/components/countdown-timer/index.js +4 -0
  31. package/dist/components/credit-badge/credit-badge.js +59 -0
  32. package/dist/components/credit-badge/index.js +6 -0
  33. package/dist/components/data-list/data-list.js +99 -0
  34. package/dist/components/data-list/index.js +16 -0
  35. package/dist/components/data-table/data-table.js +242 -0
  36. package/dist/components/data-table/index.js +6 -0
  37. package/dist/components/date-picker/date-picker.js +74 -0
  38. package/dist/components/date-picker/index.js +4 -0
  39. package/dist/components/file-upload/file-upload.js +227 -0
  40. package/dist/components/file-upload/index.js +4 -0
  41. package/dist/components/flashcard/flashcard.js +66 -0
  42. package/dist/components/flashcard/index.js +4 -0
  43. package/dist/components/index.js +172 -1
  44. package/dist/components/live-feed/index.js +4 -0
  45. package/dist/components/live-feed/live-feed.js +168 -0
  46. package/dist/components/market-treemap/index.js +6 -0
  47. package/dist/components/market-treemap/market-treemap.js +100 -0
  48. package/dist/components/marquee/index.js +4 -0
  49. package/dist/components/marquee/marquee.js +98 -0
  50. package/dist/components/metric-gauge/index.js +6 -0
  51. package/dist/components/metric-gauge/metric-gauge.js +213 -0
  52. package/dist/components/model-selector/model-selector.js +11 -2
  53. package/dist/components/number-input/index.js +4 -0
  54. package/dist/components/number-input/number-input.js +167 -0
  55. package/dist/components/number-ticker/index.js +4 -0
  56. package/dist/components/number-ticker/number-ticker.js +63 -0
  57. package/dist/components/order-book/index.js +6 -0
  58. package/dist/components/order-book/order-book.js +128 -0
  59. package/dist/components/password-input/index.js +4 -0
  60. package/dist/components/password-input/password-input.js +45 -0
  61. package/dist/components/plan-badge/index.js +6 -0
  62. package/dist/components/plan-badge/plan-badge.js +67 -0
  63. package/dist/components/rating/index.js +4 -0
  64. package/dist/components/rating/rating.js +121 -0
  65. package/dist/components/role-badge/index.js +6 -0
  66. package/dist/components/role-badge/role-badge.js +50 -0
  67. package/dist/components/scope-selector/index.js +6 -0
  68. package/dist/components/scope-selector/scope-selector.js +336 -0
  69. package/dist/components/severity-badge/index.js +8 -0
  70. package/dist/components/severity-badge/severity-badge.js +163 -0
  71. package/dist/components/sparkline-grid/index.js +6 -0
  72. package/dist/components/sparkline-grid/sparkline-grid.js +92 -0
  73. package/dist/components/spinner/index.js +5 -1
  74. package/dist/components/spinner/unicode-spinner.js +708 -0
  75. package/dist/components/stat-card/index.js +5 -0
  76. package/dist/components/stat-card/stat-card.js +102 -0
  77. package/dist/components/status-board/index.js +6 -0
  78. package/dist/components/status-board/status-board.js +138 -0
  79. package/dist/components/status-indicator/index.js +10 -0
  80. package/dist/components/status-indicator/status-indicator.js +175 -0
  81. package/dist/components/stepper/index.js +4 -0
  82. package/dist/components/stepper/stepper.js +117 -0
  83. package/dist/components/subscription-card/index.js +6 -0
  84. package/dist/components/subscription-card/subscription-card.js +161 -0
  85. package/dist/components/ticker-tape/index.js +6 -0
  86. package/dist/components/ticker-tape/ticker-tape.js +106 -0
  87. package/dist/components/tour/index.js +4 -0
  88. package/dist/components/tour/tour.js +157 -0
  89. package/dist/components/usage-breakdown/index.js +6 -0
  90. package/dist/components/usage-breakdown/usage-breakdown.js +140 -0
  91. package/dist/components/wallet-card/index.js +4 -0
  92. package/dist/components/wallet-card/wallet-card.js +115 -0
  93. package/dist/components/watchlist/index.js +6 -0
  94. package/dist/components/watchlist/watchlist.js +110 -0
  95. package/dist/components/world-clock-bar/index.js +6 -0
  96. package/dist/components/world-clock-bar/world-clock-bar.js +101 -0
  97. package/dist/index.d.ts +1173 -7
  98. package/dist/test-setup.js +19 -0
  99. package/package.json +27 -6
  100. package/styles.css +55 -0
@@ -0,0 +1,99 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "../../lib/utils";
5
+ const dataListVariants = cva(
6
+ "grid rounded-xl border bg-card text-card-foreground",
7
+ {
8
+ defaultVariants: {
9
+ density: "default"
10
+ },
11
+ variants: {
12
+ density: {
13
+ compact: "divide-y",
14
+ default: "divide-y"
15
+ }
16
+ }
17
+ }
18
+ );
19
+ const dataListItemVariants = cva(
20
+ "grid gap-1 px-4 py-4 sm:grid-cols-[minmax(0,12rem)_1fr] sm:gap-4 sm:px-5",
21
+ {
22
+ defaultVariants: {
23
+ density: "default"
24
+ },
25
+ variants: {
26
+ density: {
27
+ compact: "py-3",
28
+ default: "py-4"
29
+ }
30
+ }
31
+ }
32
+ );
33
+ const DataListContext = React.createContext({
34
+ density: "default"
35
+ });
36
+ const DataList = React.forwardRef(
37
+ ({ className, density, ...props }, reference) => {
38
+ const resolvedDensity = density ?? "default";
39
+ const contextValue = React.useMemo(
40
+ () => ({ density: resolvedDensity }),
41
+ [resolvedDensity]
42
+ );
43
+ return /* @__PURE__ */ jsx(DataListContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
44
+ "dl",
45
+ {
46
+ className: cn(
47
+ dataListVariants({ density: resolvedDensity }),
48
+ className
49
+ ),
50
+ ref: reference,
51
+ ...props
52
+ }
53
+ ) });
54
+ }
55
+ );
56
+ DataList.displayName = "DataList";
57
+ const DataListItem = React.forwardRef(
58
+ ({ className, density, ...props }, reference) => {
59
+ const context = React.useContext(DataListContext);
60
+ return /* @__PURE__ */ jsx(
61
+ "div",
62
+ {
63
+ className: cn(
64
+ dataListItemVariants({ density: density ?? context.density }),
65
+ className
66
+ ),
67
+ ref: reference,
68
+ ...props
69
+ }
70
+ );
71
+ }
72
+ );
73
+ DataListItem.displayName = "DataListItem";
74
+ const DataListLabel = React.forwardRef(({ className, ...props }, reference) => /* @__PURE__ */ jsx(
75
+ "dt",
76
+ {
77
+ className: cn("text-sm font-medium text-muted-foreground", className),
78
+ ref: reference,
79
+ ...props
80
+ }
81
+ ));
82
+ DataListLabel.displayName = "DataListLabel";
83
+ const DataListValue = React.forwardRef(({ className, ...props }, reference) => /* @__PURE__ */ jsx(
84
+ "dd",
85
+ {
86
+ className: cn("m-0 text-sm leading-6 text-foreground", className),
87
+ ref: reference,
88
+ ...props
89
+ }
90
+ ));
91
+ DataListValue.displayName = "DataListValue";
92
+ export {
93
+ DataList,
94
+ DataListItem,
95
+ DataListLabel,
96
+ DataListValue,
97
+ dataListItemVariants,
98
+ dataListVariants
99
+ };
@@ -0,0 +1,16 @@
1
+ import {
2
+ DataList,
3
+ DataListItem,
4
+ dataListItemVariants,
5
+ DataListLabel,
6
+ DataListValue,
7
+ dataListVariants
8
+ } from "./data-list";
9
+ export {
10
+ DataList,
11
+ DataListItem,
12
+ DataListLabel,
13
+ DataListValue,
14
+ dataListItemVariants,
15
+ dataListVariants
16
+ };
@@ -0,0 +1,242 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import {
5
+ flexRender,
6
+ getCoreRowModel,
7
+ getFilteredRowModel,
8
+ getPaginationRowModel,
9
+ getSortedRowModel,
10
+ useReactTable
11
+ } from "@tanstack/react-table";
12
+ import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-react";
13
+ import { cn } from "../../lib/utils";
14
+ import { Button } from "../button";
15
+ import { Checkbox } from "../checkbox";
16
+ import { Input } from "../input";
17
+ import {
18
+ Select,
19
+ SelectContent,
20
+ SelectItem,
21
+ SelectTrigger,
22
+ SelectValue
23
+ } from "../select";
24
+ import {
25
+ Table,
26
+ TableBody,
27
+ TableCell,
28
+ TableHead,
29
+ TableHeader,
30
+ TableRow
31
+ } from "../table";
32
+ function SortIcon({ direction }) {
33
+ if (direction === "asc") {
34
+ return /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" });
35
+ }
36
+ if (direction === "desc") {
37
+ return /* @__PURE__ */ jsx(ArrowDown, { className: "h-4 w-4" });
38
+ }
39
+ return /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-4 w-4" });
40
+ }
41
+ function DataTableComponent({
42
+ caption,
43
+ className,
44
+ columns,
45
+ data,
46
+ emptyMessage = "No results found.",
47
+ enableFiltering = true,
48
+ enablePagination = true,
49
+ enableSelection = false,
50
+ filterableColumns = [],
51
+ getRowId,
52
+ pageSize = 10,
53
+ searchPlaceholder = "Search rows...",
54
+ ...props
55
+ }) {
56
+ const [sorting, setSorting] = React.useState([]);
57
+ const [globalFilter, setGlobalFilter] = React.useState("");
58
+ const [columnFilters, setColumnFilters] = React.useState(
59
+ []
60
+ );
61
+ const [rowSelection, setRowSelection] = React.useState({});
62
+ const selectionColumn = React.useMemo(
63
+ () => ({
64
+ cell: ({ row }) => /* @__PURE__ */ jsx(
65
+ Checkbox,
66
+ {
67
+ "aria-label": `Select row ${row.index + 1}`,
68
+ checked: row.getIsSelected(),
69
+ onCheckedChange: (checked) => {
70
+ row.toggleSelected(Boolean(checked));
71
+ }
72
+ }
73
+ ),
74
+ enableHiding: false,
75
+ enableSorting: false,
76
+ header: ({ table: table2 }) => /* @__PURE__ */ jsx(
77
+ Checkbox,
78
+ {
79
+ "aria-label": "Select all rows",
80
+ checked: table2.getIsAllPageRowsSelected() ? true : table2.getIsSomePageRowsSelected() ? "indeterminate" : false,
81
+ onCheckedChange: (checked) => {
82
+ table2.toggleAllPageRowsSelected(Boolean(checked));
83
+ }
84
+ }
85
+ ),
86
+ id: "select",
87
+ size: 40
88
+ }),
89
+ []
90
+ );
91
+ const resolvedColumns = React.useMemo(
92
+ () => enableSelection ? [selectionColumn, ...columns] : columns,
93
+ [columns, enableSelection, selectionColumn]
94
+ );
95
+ const table = useReactTable({
96
+ columns: resolvedColumns,
97
+ data,
98
+ enableRowSelection: enableSelection,
99
+ getCoreRowModel: getCoreRowModel(),
100
+ getFilteredRowModel: getFilteredRowModel(),
101
+ getPaginationRowModel: getPaginationRowModel(),
102
+ getRowId,
103
+ getSortedRowModel: getSortedRowModel(),
104
+ initialState: {
105
+ pagination: {
106
+ pageIndex: 0,
107
+ pageSize
108
+ }
109
+ },
110
+ onColumnFiltersChange: setColumnFilters,
111
+ onGlobalFilterChange: setGlobalFilter,
112
+ onRowSelectionChange: setRowSelection,
113
+ onSortingChange: setSorting,
114
+ state: {
115
+ columnFilters,
116
+ globalFilter,
117
+ rowSelection,
118
+ sorting
119
+ }
120
+ });
121
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), ...props, children: [
122
+ enableFiltering ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded-xl border bg-card p-4 sm:flex-row sm:items-center sm:justify-between", children: [
123
+ /* @__PURE__ */ jsx(
124
+ Input,
125
+ {
126
+ className: "w-full sm:max-w-sm",
127
+ onChange: (event) => {
128
+ setGlobalFilter(event.target.value);
129
+ },
130
+ placeholder: searchPlaceholder,
131
+ value: globalFilter
132
+ }
133
+ ),
134
+ filterableColumns.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: filterableColumns.map((filter) => {
135
+ const column = table.getColumn(filter.columnId);
136
+ const value = column?.getFilterValue();
137
+ const selectValue = typeof value === "string" && value ? value : "all";
138
+ return column ? /* @__PURE__ */ jsxs(
139
+ Select,
140
+ {
141
+ onValueChange: (nextValue) => {
142
+ column.setFilterValue(
143
+ nextValue === "all" ? void 0 : nextValue
144
+ );
145
+ },
146
+ value: selectValue,
147
+ children: [
148
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-[180px]", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: filter.label }) }),
149
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
150
+ /* @__PURE__ */ jsxs(SelectItem, { value: "all", children: [
151
+ "All ",
152
+ filter.label
153
+ ] }),
154
+ filter.options.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value))
155
+ ] })
156
+ ]
157
+ },
158
+ filter.columnId
159
+ ) : null;
160
+ }) }) : null
161
+ ] }) : null,
162
+ /* @__PURE__ */ jsx("div", { className: "rounded-xl border bg-card", children: /* @__PURE__ */ jsxs(Table, { children: [
163
+ caption ? /* @__PURE__ */ jsx("caption", { className: "sr-only", children: caption }) : null,
164
+ /* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(TableHead, { children: header.isPlaceholder ? null : header.column.getCanSort() ? /* @__PURE__ */ jsxs(
165
+ Button,
166
+ {
167
+ className: "-ml-3 h-8 px-3 text-xs font-medium",
168
+ onClick: header.column.getToggleSortingHandler(),
169
+ type: "button",
170
+ variant: "ghost",
171
+ children: [
172
+ flexRender(
173
+ header.column.columnDef.header,
174
+ header.getContext()
175
+ ),
176
+ /* @__PURE__ */ jsx(SortIcon, { direction: header.column.getIsSorted() })
177
+ ]
178
+ }
179
+ ) : flexRender(
180
+ header.column.columnDef.header,
181
+ header.getContext()
182
+ ) }, header.id)) }, headerGroup.id)) }),
183
+ /* @__PURE__ */ jsx(TableBody, { children: table.getRowModel().rows.length > 0 ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(
184
+ TableRow,
185
+ {
186
+ "data-state": row.getIsSelected() ? "selected" : void 0,
187
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, { children: flexRender(
188
+ cell.column.columnDef.cell,
189
+ cell.getContext()
190
+ ) }, cell.id))
191
+ },
192
+ row.id
193
+ )) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
194
+ TableCell,
195
+ {
196
+ className: "h-24 text-center text-muted-foreground",
197
+ colSpan: resolvedColumns.length,
198
+ children: emptyMessage
199
+ }
200
+ ) }) })
201
+ ] }) }),
202
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded-xl border bg-card p-4 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between", children: [
203
+ /* @__PURE__ */ jsx("div", { children: enableSelection ? `${table.getSelectedRowModel().rows.length} selected` : `${table.getFilteredRowModel().rows.length} rows` }),
204
+ enablePagination ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 self-end sm:self-auto", children: [
205
+ /* @__PURE__ */ jsx(
206
+ Button,
207
+ {
208
+ disabled: !table.getCanPreviousPage(),
209
+ onClick: () => {
210
+ table.previousPage();
211
+ },
212
+ type: "button",
213
+ variant: "outline",
214
+ children: "Previous"
215
+ }
216
+ ),
217
+ /* @__PURE__ */ jsxs("span", { className: "min-w-24 text-center text-xs uppercase tracking-wide text-muted-foreground", children: [
218
+ "Page ",
219
+ table.getState().pagination.pageIndex + 1,
220
+ " of",
221
+ " ",
222
+ table.getPageCount()
223
+ ] }),
224
+ /* @__PURE__ */ jsx(
225
+ Button,
226
+ {
227
+ disabled: !table.getCanNextPage(),
228
+ onClick: () => {
229
+ table.nextPage();
230
+ },
231
+ type: "button",
232
+ variant: "outline",
233
+ children: "Next"
234
+ }
235
+ )
236
+ ] }) : null
237
+ ] })
238
+ ] });
239
+ }
240
+ export {
241
+ DataTableComponent as DataTable
242
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ DataTable
3
+ } from "./data-table";
4
+ export {
5
+ DataTable
6
+ };
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { CalendarIcon } from "lucide-react";
5
+ import { cn } from "../../lib/utils";
6
+ import { Button } from "../button";
7
+ import { Calendar } from "../calendar";
8
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover";
9
+ const defaultDateFormatter = new Intl.DateTimeFormat("en-US", {
10
+ day: "numeric",
11
+ month: "long",
12
+ year: "numeric"
13
+ });
14
+ const DatePicker = React.forwardRef(
15
+ ({
16
+ buttonClassName,
17
+ calendarProps,
18
+ className,
19
+ onValueChange,
20
+ placeholder = "Pick a date",
21
+ value
22
+ }, reference) => {
23
+ const [open, setOpen] = React.useState(false);
24
+ const [internalValue, setInternalValue] = React.useState(
25
+ value
26
+ );
27
+ const selectedDate = value ?? internalValue;
28
+ React.useEffect(() => {
29
+ if (value !== void 0) {
30
+ setInternalValue(value);
31
+ }
32
+ }, [value]);
33
+ const handleSelect = (nextDate) => {
34
+ if (value === void 0) {
35
+ setInternalValue(nextDate);
36
+ }
37
+ onValueChange?.(nextDate);
38
+ if (nextDate) {
39
+ setOpen(false);
40
+ }
41
+ };
42
+ return /* @__PURE__ */ jsxs(Popover, { onOpenChange: setOpen, open, children: [
43
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
44
+ Button,
45
+ {
46
+ className: cn(
47
+ "w-full justify-start text-left font-normal",
48
+ !selectedDate && "text-muted-foreground",
49
+ buttonClassName
50
+ ),
51
+ ref: reference,
52
+ variant: "outline",
53
+ children: [
54
+ /* @__PURE__ */ jsx(CalendarIcon, { className: "mr-2 h-4 w-4" }),
55
+ selectedDate ? defaultDateFormatter.format(selectedDate) : placeholder
56
+ ]
57
+ }
58
+ ) }),
59
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: cn("w-auto p-0", className), children: /* @__PURE__ */ jsx(
60
+ Calendar,
61
+ {
62
+ mode: "single",
63
+ onSelect: handleSelect,
64
+ selected: selectedDate,
65
+ ...calendarProps
66
+ }
67
+ ) })
68
+ ] });
69
+ }
70
+ );
71
+ DatePicker.displayName = "DatePicker";
72
+ export {
73
+ DatePicker
74
+ };
@@ -0,0 +1,4 @@
1
+ import { DatePicker } from "./date-picker";
2
+ export {
3
+ DatePicker
4
+ };
@@ -0,0 +1,227 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { FileUp, UploadCloud, X } from "lucide-react";
5
+ import { cn } from "../../lib/utils";
6
+ import { Button } from "../button";
7
+ function useFileUploadState(controlledFiles, multiple, onFilesChange) {
8
+ const [internalFiles, setInternalFiles] = React.useState(
9
+ controlledFiles ?? []
10
+ );
11
+ React.useEffect(() => {
12
+ if (controlledFiles !== void 0) {
13
+ setInternalFiles(controlledFiles);
14
+ }
15
+ }, [controlledFiles]);
16
+ const resolvedFiles = controlledFiles ?? internalFiles;
17
+ const updateFiles = React.useCallback(
18
+ (nextFiles) => {
19
+ if (controlledFiles === void 0) {
20
+ setInternalFiles(nextFiles);
21
+ }
22
+ onFilesChange?.(nextFiles);
23
+ },
24
+ [controlledFiles, onFilesChange]
25
+ );
26
+ const addFiles = React.useCallback(
27
+ (incomingFiles) => {
28
+ const nextFiles = [...incomingFiles];
29
+ updateFiles(
30
+ multiple ? [...resolvedFiles, ...nextFiles] : nextFiles.slice(0, 1)
31
+ );
32
+ },
33
+ [multiple, resolvedFiles, updateFiles]
34
+ );
35
+ const removeFile = React.useCallback(
36
+ (fileToRemove) => {
37
+ updateFiles(
38
+ resolvedFiles.filter(
39
+ (file) => !(file.name === fileToRemove.name && file.size === fileToRemove.size && file.lastModified === fileToRemove.lastModified)
40
+ )
41
+ );
42
+ },
43
+ [resolvedFiles, updateFiles]
44
+ );
45
+ return { addFiles, removeFile, resolvedFiles };
46
+ }
47
+ function assignInputReference(reference, node) {
48
+ if (typeof reference === "function") {
49
+ reference(node);
50
+ return;
51
+ }
52
+ if (reference) {
53
+ reference.current = node;
54
+ }
55
+ }
56
+ function FileListItem({
57
+ file,
58
+ onRemove
59
+ }) {
60
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between rounded-md border bg-muted/30 px-3 py-2 text-sm", children: [
61
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
62
+ /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: file.name }),
63
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
64
+ (file.size / 1024).toFixed(1),
65
+ " KB"
66
+ ] })
67
+ ] }),
68
+ /* @__PURE__ */ jsx(
69
+ Button,
70
+ {
71
+ "aria-label": `Remove ${file.name}`,
72
+ onClick: onRemove,
73
+ size: "icon",
74
+ type: "button",
75
+ variant: "ghost",
76
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
77
+ }
78
+ )
79
+ ] });
80
+ }
81
+ function FileUploadDropzone({
82
+ browseLabel,
83
+ children,
84
+ disabled,
85
+ dropzoneText,
86
+ helperText,
87
+ isDragging,
88
+ onActivate,
89
+ onDragStateChange,
90
+ onFilesDrop
91
+ }) {
92
+ return /* @__PURE__ */ jsxs(
93
+ "div",
94
+ {
95
+ className: cn(
96
+ "flex min-h-40 cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-input bg-background px-6 py-8 text-center transition-colors",
97
+ isDragging && "border-primary bg-accent/40",
98
+ disabled && "cursor-not-allowed opacity-50"
99
+ ),
100
+ onClick: onActivate,
101
+ onDragEnter: (event) => {
102
+ event.preventDefault();
103
+ if (!disabled) {
104
+ onDragStateChange(true);
105
+ }
106
+ },
107
+ onDragLeave: (event) => {
108
+ event.preventDefault();
109
+ onDragStateChange(false);
110
+ },
111
+ onDragOver: (event) => {
112
+ event.preventDefault();
113
+ },
114
+ onDrop: (event) => {
115
+ event.preventDefault();
116
+ onDragStateChange(false);
117
+ if (!disabled && event.dataTransfer.files.length > 0) {
118
+ onFilesDrop(event.dataTransfer.files);
119
+ }
120
+ },
121
+ onKeyDown: (event) => {
122
+ if ((event.key === "Enter" || event.key === " ") && !disabled) {
123
+ event.preventDefault();
124
+ onActivate();
125
+ }
126
+ },
127
+ role: "button",
128
+ tabIndex: disabled ? -1 : 0,
129
+ children: [
130
+ /* @__PURE__ */ jsx(UploadCloud, { className: "mb-3 h-10 w-10 text-muted-foreground" }),
131
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
132
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: dropzoneText }),
133
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: helperText })
134
+ ] }),
135
+ /* @__PURE__ */ jsxs("span", { className: "mt-4 inline-flex h-10 items-center justify-center rounded-md border border-input bg-secondary px-4 py-2 text-sm font-medium text-secondary-foreground shadow-sm", children: [
136
+ /* @__PURE__ */ jsx(FileUp, { className: "mr-2 h-4 w-4" }),
137
+ browseLabel
138
+ ] }),
139
+ children
140
+ ]
141
+ }
142
+ );
143
+ }
144
+ function FileUploadList({
145
+ files,
146
+ onRemove
147
+ }) {
148
+ if (files.length === 0) {
149
+ return null;
150
+ }
151
+ return /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: files.map((file) => /* @__PURE__ */ jsx(
152
+ FileListItem,
153
+ {
154
+ file,
155
+ onRemove: () => {
156
+ onRemove(file);
157
+ }
158
+ },
159
+ `${file.name}-${file.lastModified}-${file.size}`
160
+ )) });
161
+ }
162
+ function FileUploadComponent({
163
+ accept,
164
+ browseLabel = "Choose files",
165
+ className,
166
+ disabled,
167
+ dropzoneText = "Drag and drop files here, or click to browse.",
168
+ files,
169
+ helperText = "Supports one or more files.",
170
+ multiple = true,
171
+ onFilesChange,
172
+ ...props
173
+ }, reference) {
174
+ const inputReference = React.useRef(null);
175
+ const [isDragging, setIsDragging] = React.useState(false);
176
+ const { addFiles, removeFile, resolvedFiles } = useFileUploadState(
177
+ files,
178
+ multiple,
179
+ onFilesChange
180
+ );
181
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
182
+ /* @__PURE__ */ jsx(
183
+ FileUploadDropzone,
184
+ {
185
+ browseLabel,
186
+ disabled,
187
+ dropzoneText,
188
+ helperText,
189
+ isDragging,
190
+ onActivate: () => {
191
+ if (!disabled) {
192
+ inputReference.current?.click();
193
+ }
194
+ },
195
+ onDragStateChange: setIsDragging,
196
+ onFilesDrop: addFiles,
197
+ children: /* @__PURE__ */ jsx(
198
+ "input",
199
+ {
200
+ ...props,
201
+ accept,
202
+ "aria-label": browseLabel,
203
+ className: "sr-only",
204
+ disabled,
205
+ multiple,
206
+ onChange: (event) => {
207
+ if (event.target.files) {
208
+ addFiles(event.target.files);
209
+ }
210
+ },
211
+ ref: (node) => {
212
+ inputReference.current = node;
213
+ assignInputReference(reference, node);
214
+ },
215
+ type: "file"
216
+ }
217
+ )
218
+ }
219
+ ),
220
+ /* @__PURE__ */ jsx(FileUploadList, { files: resolvedFiles, onRemove: removeFile })
221
+ ] });
222
+ }
223
+ const FileUpload = React.forwardRef(FileUploadComponent);
224
+ FileUpload.displayName = "FileUpload";
225
+ export {
226
+ FileUpload
227
+ };
@@ -0,0 +1,4 @@
1
+ import { FileUpload } from "./file-upload";
2
+ export {
3
+ FileUpload
4
+ };