@kreativa/ui 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1222 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Button: () => Button,
24
+ Card: () => Card,
25
+ DatePicker: () => DatePicker,
26
+ EmptyState: () => EmptyState,
27
+ FormButtonGroup: () => FormButtonGroup,
28
+ FormInput: () => FormInput,
29
+ FormTextarea: () => FormTextarea,
30
+ Input: () => Input,
31
+ Layout: () => Layout,
32
+ LoadingSpinner: () => LoadingSpinner,
33
+ Modal: () => Modal,
34
+ Select: () => Select,
35
+ ServiceSwitcher: () => ServiceSwitcher,
36
+ Sidebar: () => Sidebar,
37
+ Table: () => Table,
38
+ Tabs: () => Tabs,
39
+ Timer: () => Timer,
40
+ formatTime: () => formatTime
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/components/Layout.tsx
45
+ var import_jsx_runtime = require("react/jsx-runtime");
46
+ function Layout({ children, sidebar }) {
47
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-h-screen flex", children: [
48
+ sidebar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", { className: "w-64 bg-primary-600 text-white flex flex-col", children: sidebar }),
49
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", { className: "flex-1 overflow-auto bg-cream-50", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-6", children }) })
50
+ ] });
51
+ }
52
+
53
+ // src/components/Sidebar.tsx
54
+ var import_jsx_runtime2 = require("react/jsx-runtime");
55
+ function Sidebar({ header, navigation, footer }) {
56
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
57
+ header && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-4 border-b border-primary-500", children: header }),
58
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("nav", { className: "flex-1 p-4 overflow-y-auto", children: navigation }),
59
+ footer && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-4 border-t border-primary-500", children: footer })
60
+ ] });
61
+ }
62
+
63
+ // src/components/ServiceSwitcher.tsx
64
+ var import_jsx_runtime3 = require("react/jsx-runtime");
65
+ function ServiceSwitcher({ services, currentServiceId, onServiceChange }) {
66
+ if (services.length <= 1) {
67
+ return null;
68
+ }
69
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
70
+ "select",
71
+ {
72
+ value: currentServiceId,
73
+ onChange: (e) => onServiceChange(e.target.value),
74
+ className: "w-full px-3 py-2 rounded-lg text-sm appearance-none cursor-pointer bg-primary-500 text-white border border-primary-400 focus:outline-none focus:ring-2 focus:ring-primary-300",
75
+ children: services.map((service) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: service.id, children: service.name }, service.id))
76
+ }
77
+ ) });
78
+ }
79
+
80
+ // src/components/Button.tsx
81
+ var import_jsx_runtime4 = require("react/jsx-runtime");
82
+ var variantClasses = {
83
+ primary: "bg-pink-500 hover:bg-pink-600 text-white",
84
+ secondary: "bg-primary-500 hover:bg-primary-600 text-white",
85
+ danger: "bg-red-500 hover:bg-red-600 text-white",
86
+ ghost: "bg-transparent hover:bg-primary-100 text-primary-600"
87
+ };
88
+ var sizeClasses = {
89
+ sm: "px-3 py-1.5 text-sm",
90
+ md: "px-4 py-2 text-base",
91
+ lg: "px-6 py-3 text-lg"
92
+ };
93
+ function Button({
94
+ variant = "primary",
95
+ size = "md",
96
+ className = "",
97
+ children,
98
+ disabled,
99
+ ...props
100
+ }) {
101
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
102
+ "button",
103
+ {
104
+ className: `
105
+ inline-flex items-center justify-center rounded-lg font-medium
106
+ transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2
107
+ disabled:opacity-50 disabled:cursor-not-allowed
108
+ ${variantClasses[variant]}
109
+ ${sizeClasses[size]}
110
+ ${className}
111
+ `,
112
+ disabled,
113
+ ...props,
114
+ children
115
+ }
116
+ );
117
+ }
118
+
119
+ // src/components/Input.tsx
120
+ var import_react = require("react");
121
+ var import_jsx_runtime5 = require("react/jsx-runtime");
122
+ var Input = (0, import_react.forwardRef)(
123
+ ({ label, error, className = "", ...props }, ref) => {
124
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-full", children: [
125
+ label && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: label }),
126
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
127
+ "input",
128
+ {
129
+ ref,
130
+ className: `
131
+ w-full px-3 py-2 border rounded-lg
132
+ focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500
133
+ disabled:bg-gray-100 disabled:cursor-not-allowed
134
+ ${error ? "border-red-500" : "border-gray-300"}
135
+ ${className}
136
+ `,
137
+ ...props
138
+ }
139
+ ),
140
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-1 text-sm text-red-500", children: error })
141
+ ] });
142
+ }
143
+ );
144
+ Input.displayName = "Input";
145
+
146
+ // src/components/Card.tsx
147
+ var import_jsx_runtime6 = require("react/jsx-runtime");
148
+ var paddingClasses = {
149
+ none: "",
150
+ sm: "p-3",
151
+ md: "p-4",
152
+ lg: "p-6"
153
+ };
154
+ function Card({ children, className = "", padding = "md" }) {
155
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
156
+ "div",
157
+ {
158
+ className: `
159
+ bg-white rounded-lg shadow-sm border border-gray-200
160
+ ${paddingClasses[padding]}
161
+ ${className}
162
+ `,
163
+ children
164
+ }
165
+ );
166
+ }
167
+
168
+ // src/components/LoadingSpinner.tsx
169
+ var import_jsx_runtime7 = require("react/jsx-runtime");
170
+ var sizeClasses2 = {
171
+ sm: "w-4 h-4",
172
+ md: "w-8 h-8",
173
+ lg: "w-12 h-12"
174
+ };
175
+ function LoadingSpinner({ size = "md", fullScreen = false }) {
176
+ const spinner = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
177
+ "div",
178
+ {
179
+ className: `
180
+ animate-spin rounded-full border-2 border-primary-200 border-t-primary-600
181
+ ${sizeClasses2[size]}
182
+ `
183
+ }
184
+ );
185
+ if (fullScreen) {
186
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "min-h-screen flex items-center justify-center", children: spinner });
187
+ }
188
+ return spinner;
189
+ }
190
+
191
+ // src/components/EmptyState.tsx
192
+ var import_jsx_runtime8 = require("react/jsx-runtime");
193
+ function EmptyState({ icon, title, description, action }) {
194
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "text-center py-16", children: [
195
+ icon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-16 h-16 mx-auto mb-4 rounded-full bg-gray-100 flex items-center justify-center", children: icon }),
196
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-lg font-medium text-gray-800 mb-1", children: title }),
197
+ description && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-gray-500 mb-4", children: description }),
198
+ action
199
+ ] });
200
+ }
201
+
202
+ // src/components/Modal.tsx
203
+ var import_react2 = require("react");
204
+ var import_jsx_runtime9 = require("react/jsx-runtime");
205
+ var maxWidthClasses = {
206
+ sm: "max-w-sm",
207
+ md: "max-w-md",
208
+ lg: "max-w-lg",
209
+ xl: "max-w-xl",
210
+ "2xl": "max-w-2xl"
211
+ };
212
+ function Modal({ isOpen, onClose, title, children, maxWidth = "xl" }) {
213
+ (0, import_react2.useEffect)(() => {
214
+ const handleEscape = (e) => {
215
+ if (e.key === "Escape") {
216
+ onClose();
217
+ }
218
+ };
219
+ if (isOpen) {
220
+ document.addEventListener("keydown", handleEscape);
221
+ document.body.style.overflow = "hidden";
222
+ }
223
+ return () => {
224
+ document.removeEventListener("keydown", handleEscape);
225
+ document.body.style.overflow = "";
226
+ };
227
+ }, [isOpen, onClose]);
228
+ if (!isOpen) return null;
229
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
230
+ "div",
231
+ {
232
+ className: "fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4",
233
+ onClick: (e) => {
234
+ if (e.target === e.currentTarget) {
235
+ onClose();
236
+ }
237
+ },
238
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
239
+ "div",
240
+ {
241
+ className: `bg-white rounded-2xl shadow-2xl w-full ${maxWidthClasses[maxWidth]} p-6 max-h-[90vh] overflow-y-auto`,
242
+ children: [
243
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { className: "text-xl font-bold text-gray-800 mb-6", children: title }),
244
+ children
245
+ ]
246
+ }
247
+ )
248
+ }
249
+ );
250
+ }
251
+
252
+ // src/components/FormInput.tsx
253
+ var import_react3 = require("react");
254
+ var import_jsx_runtime10 = require("react/jsx-runtime");
255
+ var FormInput = (0, import_react3.forwardRef)(
256
+ ({ label, helpText, error, className = "", ...props }, ref) => {
257
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
258
+ label && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: label }),
259
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
260
+ "input",
261
+ {
262
+ ref,
263
+ className: `w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent ${error ? "border-red-300 focus:ring-red-500" : ""} ${className}`,
264
+ ...props
265
+ }
266
+ ),
267
+ helpText && !error && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mt-1 text-xs text-gray-500", children: helpText }),
268
+ error && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mt-1 text-xs text-red-600", children: error })
269
+ ] });
270
+ }
271
+ );
272
+ FormInput.displayName = "FormInput";
273
+
274
+ // src/components/FormTextarea.tsx
275
+ var import_react4 = require("react");
276
+ var import_jsx_runtime11 = require("react/jsx-runtime");
277
+ var FormTextarea = (0, import_react4.forwardRef)(
278
+ ({ label, helpText, error, className = "", ...props }, ref) => {
279
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
280
+ label && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: label }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
282
+ "textarea",
283
+ {
284
+ ref,
285
+ className: `w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent resize-y ${error ? "border-red-300 focus:ring-red-500" : ""} ${className}`,
286
+ ...props
287
+ }
288
+ ),
289
+ helpText && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "mt-1 text-xs text-gray-500", children: helpText }),
290
+ error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "mt-1 text-xs text-red-600", children: error })
291
+ ] });
292
+ }
293
+ );
294
+ FormTextarea.displayName = "FormTextarea";
295
+
296
+ // src/components/FormButtonGroup.tsx
297
+ var import_jsx_runtime12 = require("react/jsx-runtime");
298
+ function FormButtonGroup({
299
+ cancelText = "Cancel",
300
+ submitText = "Save",
301
+ loadingText = "Saving...",
302
+ isLoading = false,
303
+ isDisabled = false,
304
+ onCancel,
305
+ onSubmit,
306
+ submitContent
307
+ }) {
308
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-3 pt-2", children: [
309
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
310
+ "button",
311
+ {
312
+ type: "button",
313
+ onClick: onCancel,
314
+ className: "flex-1 py-3 px-4 bg-gray-100 text-gray-700 font-medium rounded-xl hover:bg-gray-200 transition-colors",
315
+ children: cancelText
316
+ }
317
+ ),
318
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
319
+ "button",
320
+ {
321
+ type: onSubmit ? "button" : "submit",
322
+ onClick: onSubmit,
323
+ disabled: isLoading || isDisabled,
324
+ className: "flex-1 py-3 px-4 bg-primary-600 text-white font-medium rounded-xl hover:bg-primary-700 transition-colors disabled:opacity-50",
325
+ children: submitContent ?? (isLoading ? loadingText : submitText)
326
+ }
327
+ )
328
+ ] });
329
+ }
330
+
331
+ // src/components/Table.tsx
332
+ var import_react5 = require("react");
333
+ var import_jsx_runtime13 = require("react/jsx-runtime");
334
+ function Table({
335
+ data,
336
+ columns,
337
+ getRowKey,
338
+ onRowClick,
339
+ loading = false,
340
+ emptyMessage = "No data available",
341
+ className = ""
342
+ }) {
343
+ const [sortKey, setSortKey] = (0, import_react5.useState)(null);
344
+ const [sortDirection, setSortDirection] = (0, import_react5.useState)(null);
345
+ const handleHeaderClick = (column) => {
346
+ if (!column.sortable) return;
347
+ if (sortKey === column.key) {
348
+ if (sortDirection === "asc") {
349
+ setSortDirection("desc");
350
+ } else if (sortDirection === "desc") {
351
+ setSortKey(null);
352
+ setSortDirection(null);
353
+ }
354
+ } else {
355
+ setSortKey(column.key);
356
+ setSortDirection("asc");
357
+ }
358
+ };
359
+ const sortedData = (0, import_react5.useMemo)(() => {
360
+ if (!sortKey || !sortDirection) return data;
361
+ const column = columns.find((c) => c.key === sortKey);
362
+ if (!column) return data;
363
+ const sorted = [...data].sort((a, b) => {
364
+ if (column.sortFn) {
365
+ return column.sortFn(a, b);
366
+ }
367
+ const aVal = String(column.render(a) ?? "");
368
+ const bVal = String(column.render(b) ?? "");
369
+ return aVal.localeCompare(bVal);
370
+ });
371
+ return sortDirection === "desc" ? sorted.reverse() : sorted;
372
+ }, [data, columns, sortKey, sortDirection]);
373
+ const getSortIcon = (column) => {
374
+ if (!column.sortable) return null;
375
+ if (sortKey !== column.key) {
376
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) });
377
+ }
378
+ if (sortDirection === "asc") {
379
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-primary-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 15l7-7 7 7" }) });
380
+ }
381
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-primary-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) });
382
+ };
383
+ const alignmentClasses = {
384
+ left: "text-left",
385
+ center: "text-center",
386
+ right: "text-right"
387
+ };
388
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: `overflow-x-auto ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { className: "w-full border-collapse", children: [
389
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { className: "bg-gray-50 border-b border-gray-200", children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
390
+ "th",
391
+ {
392
+ className: `
393
+ px-4 py-3 text-sm font-semibold text-gray-700
394
+ ${alignmentClasses[column.align ?? "left"]}
395
+ ${column.sortable ? "cursor-pointer select-none hover:bg-gray-100" : ""}
396
+ `,
397
+ style: { width: column.width },
398
+ onClick: () => handleHeaderClick(column),
399
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `flex items-center gap-1 ${column.align === "right" ? "justify-end" : column.align === "center" ? "justify-center" : ""}`, children: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: column.header }),
401
+ getSortIcon(column)
402
+ ] })
403
+ },
404
+ column.key
405
+ )) }) }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: loading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-center gap-2", children: [
407
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-5 h-5 border-2 border-primary-200 border-t-primary-600 rounded-full animate-spin" }),
408
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Loading..." })
409
+ ] }) }) }) : sortedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: emptyMessage }) }) : sortedData.map((item) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
410
+ "tr",
411
+ {
412
+ className: `
413
+ border-b border-gray-100 hover:bg-gray-50 transition-colors
414
+ ${onRowClick ? "cursor-pointer" : ""}
415
+ `,
416
+ onClick: () => onRowClick?.(item),
417
+ children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
418
+ "td",
419
+ {
420
+ className: `px-4 py-3 text-sm text-gray-800 ${alignmentClasses[column.align ?? "left"]}`,
421
+ style: { width: column.width },
422
+ children: column.render(item)
423
+ },
424
+ column.key
425
+ ))
426
+ },
427
+ getRowKey(item)
428
+ )) })
429
+ ] }) });
430
+ }
431
+
432
+ // src/components/Select.tsx
433
+ var import_react6 = require("react");
434
+ var import_jsx_runtime14 = require("react/jsx-runtime");
435
+ function Select({
436
+ options,
437
+ value,
438
+ onChange,
439
+ placeholder = "Select...",
440
+ label,
441
+ error,
442
+ searchable = true,
443
+ multiple = false,
444
+ disabled = false,
445
+ className = ""
446
+ }) {
447
+ const [isOpen, setIsOpen] = (0, import_react6.useState)(false);
448
+ const [searchTerm, setSearchTerm] = (0, import_react6.useState)("");
449
+ const [highlightedIndex, setHighlightedIndex] = (0, import_react6.useState)(0);
450
+ const containerRef = (0, import_react6.useRef)(null);
451
+ const inputRef = (0, import_react6.useRef)(null);
452
+ const filteredOptions = (0, import_react6.useMemo)(() => {
453
+ if (!searchTerm) return options;
454
+ const term = searchTerm.toLowerCase();
455
+ return options.filter((opt) => opt.label.toLowerCase().includes(term));
456
+ }, [options, searchTerm]);
457
+ const selectedOptions = (0, import_react6.useMemo)(() => {
458
+ if (value === null) return [];
459
+ const values = Array.isArray(value) ? value : [value];
460
+ return options.filter((opt) => values.includes(opt.value));
461
+ }, [options, value]);
462
+ const displayText = (0, import_react6.useMemo)(() => {
463
+ if (selectedOptions.length === 0) return "";
464
+ if (multiple) {
465
+ return selectedOptions.map((opt) => opt.label).join(", ");
466
+ }
467
+ return selectedOptions[0]?.label ?? "";
468
+ }, [selectedOptions, multiple]);
469
+ (0, import_react6.useEffect)(() => {
470
+ const handleClickOutside = (e) => {
471
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
472
+ setIsOpen(false);
473
+ setSearchTerm("");
474
+ }
475
+ };
476
+ document.addEventListener("mousedown", handleClickOutside);
477
+ return () => document.removeEventListener("mousedown", handleClickOutside);
478
+ }, []);
479
+ (0, import_react6.useEffect)(() => {
480
+ setHighlightedIndex(0);
481
+ }, [filteredOptions]);
482
+ const handleSelect = (option) => {
483
+ if (option.disabled) return;
484
+ if (multiple) {
485
+ const currentValues = Array.isArray(value) ? value : value ? [value] : [];
486
+ const isSelected2 = currentValues.includes(option.value);
487
+ if (isSelected2) {
488
+ const newValues = currentValues.filter((v) => v !== option.value);
489
+ onChange(newValues.length > 0 ? newValues : null);
490
+ } else {
491
+ onChange([...currentValues, option.value]);
492
+ }
493
+ } else {
494
+ onChange(option.value);
495
+ setIsOpen(false);
496
+ setSearchTerm("");
497
+ }
498
+ };
499
+ const handleKeyDown = (e) => {
500
+ if (disabled) return;
501
+ switch (e.key) {
502
+ case "ArrowDown":
503
+ e.preventDefault();
504
+ if (!isOpen) {
505
+ setIsOpen(true);
506
+ } else {
507
+ setHighlightedIndex((prev) => Math.min(prev + 1, filteredOptions.length - 1));
508
+ }
509
+ break;
510
+ case "ArrowUp":
511
+ e.preventDefault();
512
+ setHighlightedIndex((prev) => Math.max(prev - 1, 0));
513
+ break;
514
+ case "Enter":
515
+ e.preventDefault();
516
+ if (isOpen && filteredOptions[highlightedIndex]) {
517
+ handleSelect(filteredOptions[highlightedIndex]);
518
+ } else {
519
+ setIsOpen(true);
520
+ }
521
+ break;
522
+ case "Escape":
523
+ setIsOpen(false);
524
+ setSearchTerm("");
525
+ break;
526
+ }
527
+ };
528
+ const isSelected = (option) => {
529
+ if (value === null) return false;
530
+ const values = Array.isArray(value) ? value : [value];
531
+ return values.includes(option.value);
532
+ };
533
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { ref: containerRef, className: `relative ${className}`, children: [
534
+ label && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: label }),
535
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
536
+ "div",
537
+ {
538
+ className: `
539
+ relative w-full px-4 py-3 rounded-xl border bg-white
540
+ ${disabled ? "bg-gray-100 cursor-not-allowed" : "cursor-pointer"}
541
+ ${error ? "border-red-300" : isOpen ? "border-primary-500 ring-2 ring-primary-100" : "border-gray-200"}
542
+ transition-all
543
+ `,
544
+ onClick: () => {
545
+ if (!disabled) {
546
+ setIsOpen(!isOpen);
547
+ if (!isOpen && searchable) {
548
+ setTimeout(() => inputRef.current?.focus(), 0);
549
+ }
550
+ }
551
+ },
552
+ onKeyDown: handleKeyDown,
553
+ tabIndex: disabled ? -1 : 0,
554
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center justify-between", children: [
555
+ searchable && isOpen ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
556
+ "input",
557
+ {
558
+ ref: inputRef,
559
+ type: "text",
560
+ className: "w-full outline-none bg-transparent",
561
+ placeholder: displayText || placeholder,
562
+ value: searchTerm,
563
+ onChange: (e) => setSearchTerm(e.target.value),
564
+ onClick: (e) => e.stopPropagation()
565
+ }
566
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: displayText ? "text-gray-800" : "text-gray-400", children: displayText || placeholder }),
567
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
568
+ "svg",
569
+ {
570
+ className: `w-5 h-5 text-gray-400 transition-transform ${isOpen ? "rotate-180" : ""}`,
571
+ fill: "none",
572
+ stroke: "currentColor",
573
+ viewBox: "0 0 24 24",
574
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" })
575
+ }
576
+ )
577
+ ] })
578
+ }
579
+ ),
580
+ error && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mt-1 text-xs text-red-600", children: error }),
581
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-xl shadow-lg max-h-60 overflow-auto", children: filteredOptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "px-4 py-3 text-sm text-gray-500", children: "No options found" }) : filteredOptions.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
582
+ "div",
583
+ {
584
+ className: `
585
+ px-4 py-3 text-sm cursor-pointer flex items-center gap-2
586
+ ${option.disabled ? "text-gray-400 cursor-not-allowed" : "text-gray-800"}
587
+ ${highlightedIndex === index ? "bg-primary-50" : "hover:bg-gray-50"}
588
+ ${isSelected(option) ? "bg-primary-50 font-medium" : ""}
589
+ `,
590
+ onClick: (e) => {
591
+ e.stopPropagation();
592
+ handleSelect(option);
593
+ },
594
+ onMouseEnter: () => setHighlightedIndex(index),
595
+ children: [
596
+ multiple && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
597
+ "div",
598
+ {
599
+ className: `
600
+ w-4 h-4 border rounded flex items-center justify-center
601
+ ${isSelected(option) ? "bg-primary-600 border-primary-600" : "border-gray-300"}
602
+ `,
603
+ children: isSelected(option) && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("svg", { className: "w-3 h-3 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, d: "M5 13l4 4L19 7" }) })
604
+ }
605
+ ),
606
+ option.icon,
607
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: option.label })
608
+ ]
609
+ },
610
+ String(option.value)
611
+ )) })
612
+ ] });
613
+ }
614
+
615
+ // src/components/Tabs.tsx
616
+ var import_react7 = require("react");
617
+ var import_jsx_runtime15 = require("react/jsx-runtime");
618
+ function Tabs({
619
+ tabs,
620
+ activeTab: controlledActiveTab,
621
+ onTabChange,
622
+ defaultTab,
623
+ variant = "default",
624
+ renderContent = true,
625
+ className = ""
626
+ }) {
627
+ const isControlled = controlledActiveTab !== void 0;
628
+ const [internalActiveTab, setInternalActiveTab] = (0, import_react7.useState)(
629
+ defaultTab || tabs.find((t) => !t.disabled)?.id || tabs[0]?.id
630
+ );
631
+ const activeTab = isControlled ? controlledActiveTab : internalActiveTab;
632
+ const handleTabClick = (tab) => {
633
+ if (tab.disabled) return;
634
+ if (!isControlled) {
635
+ setInternalActiveTab(tab.id);
636
+ }
637
+ onTabChange?.(tab.id);
638
+ };
639
+ const handleKeyDown = (e, index) => {
640
+ const enabledTabs = tabs.filter((t) => !t.disabled);
641
+ const currentEnabledIndex = enabledTabs.findIndex((t) => t.id === tabs[index].id);
642
+ let newTab;
643
+ switch (e.key) {
644
+ case "ArrowLeft":
645
+ e.preventDefault();
646
+ newTab = enabledTabs[currentEnabledIndex - 1] || enabledTabs[enabledTabs.length - 1];
647
+ break;
648
+ case "ArrowRight":
649
+ e.preventDefault();
650
+ newTab = enabledTabs[currentEnabledIndex + 1] || enabledTabs[0];
651
+ break;
652
+ case "Home":
653
+ e.preventDefault();
654
+ newTab = enabledTabs[0];
655
+ break;
656
+ case "End":
657
+ e.preventDefault();
658
+ newTab = enabledTabs[enabledTabs.length - 1];
659
+ break;
660
+ }
661
+ if (newTab) {
662
+ handleTabClick(newTab);
663
+ const button = document.querySelector(`[data-tab-id="${newTab.id}"]`);
664
+ button?.focus();
665
+ }
666
+ };
667
+ const activeContent = tabs.find((t) => t.id === activeTab)?.content;
668
+ const variantStyles = {
669
+ default: {
670
+ container: "border-b border-gray-200",
671
+ tab: (isActive, isDisabled) => `
672
+ px-4 py-3 text-sm font-medium border-b-2 -mb-px transition-colors
673
+ ${isDisabled ? "text-gray-300 cursor-not-allowed" : "cursor-pointer"}
674
+ ${isActive ? "border-primary-500 text-primary-600" : isDisabled ? "border-transparent" : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"}
675
+ `
676
+ },
677
+ pills: {
678
+ container: "bg-gray-100 rounded-lg p-1 inline-flex",
679
+ tab: (isActive, isDisabled) => `
680
+ px-4 py-2 text-sm font-medium rounded-md transition-colors
681
+ ${isDisabled ? "text-gray-400 cursor-not-allowed" : "cursor-pointer"}
682
+ ${isActive ? "bg-white text-gray-900 shadow-sm" : isDisabled ? "" : "text-gray-500 hover:text-gray-700"}
683
+ `
684
+ },
685
+ underline: {
686
+ container: "",
687
+ tab: (isActive, isDisabled) => `
688
+ px-4 py-2 text-sm font-medium transition-colors relative
689
+ ${isDisabled ? "text-gray-300 cursor-not-allowed" : "cursor-pointer"}
690
+ ${isActive ? "text-primary-600" : isDisabled ? "" : "text-gray-500 hover:text-gray-700"}
691
+ ${isActive ? "after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-primary-500" : ""}
692
+ `
693
+ }
694
+ };
695
+ const styles = variantStyles[variant];
696
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className, children: [
697
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `flex ${styles.container}`, role: "tablist", children: tabs.map((tab, index) => {
698
+ const isActive = tab.id === activeTab;
699
+ const isDisabled = tab.disabled ?? false;
700
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
701
+ "button",
702
+ {
703
+ type: "button",
704
+ role: "tab",
705
+ "data-tab-id": tab.id,
706
+ "aria-selected": isActive,
707
+ "aria-disabled": isDisabled,
708
+ tabIndex: isActive ? 0 : -1,
709
+ className: styles.tab(isActive, isDisabled),
710
+ onClick: () => handleTabClick(tab),
711
+ onKeyDown: (e) => handleKeyDown(e, index),
712
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "flex items-center gap-2", children: [
713
+ tab.icon,
714
+ tab.label
715
+ ] })
716
+ },
717
+ tab.id
718
+ );
719
+ }) }),
720
+ renderContent && activeContent && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "pt-4", role: "tabpanel", children: activeContent })
721
+ ] });
722
+ }
723
+
724
+ // src/components/DatePicker.tsx
725
+ var import_react8 = require("react");
726
+ var import_jsx_runtime16 = require("react/jsx-runtime");
727
+ var WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
728
+ var MONTHS = [
729
+ "January",
730
+ "February",
731
+ "March",
732
+ "April",
733
+ "May",
734
+ "June",
735
+ "July",
736
+ "August",
737
+ "September",
738
+ "October",
739
+ "November",
740
+ "December"
741
+ ];
742
+ var PRESET_RANGES = [
743
+ {
744
+ label: "Today",
745
+ getValue: () => {
746
+ const today = /* @__PURE__ */ new Date();
747
+ today.setHours(0, 0, 0, 0);
748
+ return { start: today, end: today };
749
+ }
750
+ },
751
+ {
752
+ label: "Yesterday",
753
+ getValue: () => {
754
+ const yesterday = /* @__PURE__ */ new Date();
755
+ yesterday.setDate(yesterday.getDate() - 1);
756
+ yesterday.setHours(0, 0, 0, 0);
757
+ return { start: yesterday, end: yesterday };
758
+ }
759
+ },
760
+ {
761
+ label: "Last 7 days",
762
+ getValue: () => {
763
+ const end = /* @__PURE__ */ new Date();
764
+ end.setHours(0, 0, 0, 0);
765
+ const start = /* @__PURE__ */ new Date();
766
+ start.setDate(start.getDate() - 6);
767
+ start.setHours(0, 0, 0, 0);
768
+ return { start, end };
769
+ }
770
+ },
771
+ {
772
+ label: "Last 30 days",
773
+ getValue: () => {
774
+ const end = /* @__PURE__ */ new Date();
775
+ end.setHours(0, 0, 0, 0);
776
+ const start = /* @__PURE__ */ new Date();
777
+ start.setDate(start.getDate() - 29);
778
+ start.setHours(0, 0, 0, 0);
779
+ return { start, end };
780
+ }
781
+ },
782
+ {
783
+ label: "This month",
784
+ getValue: () => {
785
+ const start = /* @__PURE__ */ new Date();
786
+ start.setDate(1);
787
+ start.setHours(0, 0, 0, 0);
788
+ const end = /* @__PURE__ */ new Date();
789
+ end.setHours(0, 0, 0, 0);
790
+ return { start, end };
791
+ }
792
+ },
793
+ {
794
+ label: "Last month",
795
+ getValue: () => {
796
+ const start = /* @__PURE__ */ new Date();
797
+ start.setMonth(start.getMonth() - 1);
798
+ start.setDate(1);
799
+ start.setHours(0, 0, 0, 0);
800
+ const end = new Date(start.getFullYear(), start.getMonth() + 1, 0);
801
+ end.setHours(0, 0, 0, 0);
802
+ return { start, end };
803
+ }
804
+ }
805
+ ];
806
+ function formatDate(date) {
807
+ return date.toLocaleDateString("sv-SE");
808
+ }
809
+ function isSameDay(a, b) {
810
+ return a.getDate() === b.getDate() && a.getMonth() === b.getMonth() && a.getFullYear() === b.getFullYear();
811
+ }
812
+ function isInRange(date, range) {
813
+ if (!range.start || !range.end) return false;
814
+ const d = date.getTime();
815
+ return d >= range.start.getTime() && d <= range.end.getTime();
816
+ }
817
+ function DatePicker({
818
+ value,
819
+ onChange,
820
+ range = false,
821
+ label,
822
+ placeholder = "Select date",
823
+ error,
824
+ minDate,
825
+ maxDate,
826
+ disabled = false,
827
+ className = ""
828
+ }) {
829
+ const [isOpen, setIsOpen] = (0, import_react8.useState)(false);
830
+ const [viewDate, setViewDate] = (0, import_react8.useState)(/* @__PURE__ */ new Date());
831
+ const [hoverDate, setHoverDate] = (0, import_react8.useState)(null);
832
+ const containerRef = (0, import_react8.useRef)(null);
833
+ const [selectingEnd, setSelectingEnd] = (0, import_react8.useState)(false);
834
+ (0, import_react8.useEffect)(() => {
835
+ const handleClickOutside = (e) => {
836
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
837
+ setIsOpen(false);
838
+ setSelectingEnd(false);
839
+ }
840
+ };
841
+ document.addEventListener("mousedown", handleClickOutside);
842
+ return () => document.removeEventListener("mousedown", handleClickOutside);
843
+ }, []);
844
+ const displayText = (0, import_react8.useMemo)(() => {
845
+ if (!value) return "";
846
+ if (range) {
847
+ const rangeValue = value;
848
+ if (rangeValue.start && rangeValue.end) {
849
+ if (isSameDay(rangeValue.start, rangeValue.end)) {
850
+ return formatDate(rangeValue.start);
851
+ }
852
+ return `${formatDate(rangeValue.start)} - ${formatDate(rangeValue.end)}`;
853
+ }
854
+ if (rangeValue.start) {
855
+ return `${formatDate(rangeValue.start)} - ...`;
856
+ }
857
+ return "";
858
+ }
859
+ return formatDate(value);
860
+ }, [value, range]);
861
+ const calendarDays = (0, import_react8.useMemo)(() => {
862
+ const year = viewDate.getFullYear();
863
+ const month = viewDate.getMonth();
864
+ const firstDay = new Date(year, month, 1);
865
+ const lastDay = new Date(year, month + 1, 0);
866
+ let startDayOfWeek = firstDay.getDay() - 1;
867
+ if (startDayOfWeek < 0) startDayOfWeek = 6;
868
+ const days = [];
869
+ for (let i = 0; i < startDayOfWeek; i++) {
870
+ days.push(null);
871
+ }
872
+ for (let i = 1; i <= lastDay.getDate(); i++) {
873
+ days.push(new Date(year, month, i));
874
+ }
875
+ return days;
876
+ }, [viewDate]);
877
+ const handleDateClick = (date) => {
878
+ if (isDateDisabled(date)) return;
879
+ if (range) {
880
+ const rangeValue = value || { start: null, end: null };
881
+ if (!selectingEnd || !rangeValue.start) {
882
+ onChange({ start: date, end: null });
883
+ setSelectingEnd(true);
884
+ } else {
885
+ if (date < rangeValue.start) {
886
+ onChange({ start: date, end: rangeValue.start });
887
+ } else {
888
+ onChange({ start: rangeValue.start, end: date });
889
+ }
890
+ setSelectingEnd(false);
891
+ setIsOpen(false);
892
+ }
893
+ } else {
894
+ onChange(date);
895
+ setIsOpen(false);
896
+ }
897
+ };
898
+ const handlePresetClick = (preset) => {
899
+ const rangeVal = preset.getValue();
900
+ onChange(rangeVal);
901
+ setIsOpen(false);
902
+ setSelectingEnd(false);
903
+ };
904
+ const isDateDisabled = (date) => {
905
+ if (minDate && date < minDate) return true;
906
+ if (maxDate && date > maxDate) return true;
907
+ return false;
908
+ };
909
+ const isDateSelected = (date) => {
910
+ if (!value) return false;
911
+ if (range) {
912
+ const rangeValue = value;
913
+ if (rangeValue.start && isSameDay(date, rangeValue.start)) return true;
914
+ if (rangeValue.end && isSameDay(date, rangeValue.end)) return true;
915
+ return false;
916
+ }
917
+ return isSameDay(date, value);
918
+ };
919
+ const isDateInRange = (date) => {
920
+ if (!range || !value) return false;
921
+ const rangeValue = value;
922
+ if (selectingEnd && rangeValue.start && hoverDate) {
923
+ const previewRange = {
924
+ start: rangeValue.start,
925
+ end: hoverDate > rangeValue.start ? hoverDate : rangeValue.start
926
+ };
927
+ if (hoverDate < rangeValue.start) {
928
+ previewRange.start = hoverDate;
929
+ previewRange.end = rangeValue.start;
930
+ }
931
+ return isInRange(date, previewRange);
932
+ }
933
+ if (!rangeValue.start || !rangeValue.end) return false;
934
+ return isInRange(date, rangeValue);
935
+ };
936
+ const goToPrevMonth = () => {
937
+ setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() - 1, 1));
938
+ };
939
+ const goToNextMonth = () => {
940
+ setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() + 1, 1));
941
+ };
942
+ const today = /* @__PURE__ */ new Date();
943
+ today.setHours(0, 0, 0, 0);
944
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { ref: containerRef, className: `relative ${className}`, children: [
945
+ label && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: label }),
946
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
947
+ "div",
948
+ {
949
+ className: `
950
+ relative w-full px-4 py-3 rounded-xl border bg-white
951
+ ${disabled ? "bg-gray-100 cursor-not-allowed" : "cursor-pointer"}
952
+ ${error ? "border-red-300" : isOpen ? "border-primary-500 ring-2 ring-primary-100" : "border-gray-200"}
953
+ transition-all
954
+ `,
955
+ onClick: () => !disabled && setIsOpen(!isOpen),
956
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center justify-between", children: [
957
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: displayText ? "text-gray-800" : "text-gray-400", children: displayText || placeholder }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("svg", { className: "w-5 h-5 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" }) })
959
+ ] })
960
+ }
961
+ ),
962
+ error && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-1 text-xs text-red-600", children: error }),
963
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "absolute z-50 mt-1 bg-white border border-gray-200 rounded-xl shadow-lg p-4", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: range ? "flex gap-4" : "", children: [
964
+ range && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "border-r border-gray-100 pr-4 space-y-1", children: PRESET_RANGES.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
965
+ "button",
966
+ {
967
+ type: "button",
968
+ className: "block w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 rounded-lg",
969
+ onClick: () => handlePresetClick(preset),
970
+ children: preset.label
971
+ },
972
+ preset.label
973
+ )) }),
974
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
975
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [
976
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
977
+ "button",
978
+ {
979
+ type: "button",
980
+ className: "p-1 hover:bg-gray-100 rounded",
981
+ onClick: goToPrevMonth,
982
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) })
983
+ }
984
+ ),
985
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-sm font-semibold text-gray-800", children: [
986
+ MONTHS[viewDate.getMonth()],
987
+ " ",
988
+ viewDate.getFullYear()
989
+ ] }),
990
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
991
+ "button",
992
+ {
993
+ type: "button",
994
+ className: "p-1 hover:bg-gray-100 rounded",
995
+ onClick: goToNextMonth,
996
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })
997
+ }
998
+ )
999
+ ] }),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "grid grid-cols-7 gap-1 mb-2", children: WEEKDAYS.map((day) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-center text-xs font-medium text-gray-500 py-1", children: day }, day)) }),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "grid grid-cols-7 gap-1", children: calendarDays.map((date, index) => {
1002
+ if (!date) {
1003
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "w-9 h-9" }, `empty-${index}`);
1004
+ }
1005
+ const isDisabled = isDateDisabled(date);
1006
+ const isSelected = isDateSelected(date);
1007
+ const inRange = isDateInRange(date);
1008
+ const isToday = isSameDay(date, today);
1009
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1010
+ "button",
1011
+ {
1012
+ type: "button",
1013
+ className: `
1014
+ w-9 h-9 text-sm rounded-lg transition-colors
1015
+ ${isDisabled ? "text-gray-300 cursor-not-allowed" : "hover:bg-gray-100"}
1016
+ ${isSelected ? "bg-primary-600 text-white hover:bg-primary-700" : ""}
1017
+ ${inRange && !isSelected ? "bg-primary-100" : ""}
1018
+ ${isToday && !isSelected ? "ring-1 ring-primary-400" : ""}
1019
+ `,
1020
+ onClick: () => handleDateClick(date),
1021
+ onMouseEnter: () => setHoverDate(date),
1022
+ onMouseLeave: () => setHoverDate(null),
1023
+ disabled: isDisabled,
1024
+ children: date.getDate()
1025
+ },
1026
+ date.toISOString()
1027
+ );
1028
+ }) })
1029
+ ] })
1030
+ ] }) })
1031
+ ] });
1032
+ }
1033
+
1034
+ // src/components/Timer.tsx
1035
+ var import_react9 = require("react");
1036
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1037
+ function formatTime(totalSeconds) {
1038
+ const hours = Math.floor(totalSeconds / 3600);
1039
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
1040
+ const seconds = totalSeconds % 60;
1041
+ const pad = (n) => n.toString().padStart(2, "0");
1042
+ if (hours > 0) {
1043
+ return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
1044
+ }
1045
+ return `${pad(minutes)}:${pad(seconds)}`;
1046
+ }
1047
+ function Timer({
1048
+ initialSeconds = 0,
1049
+ onStart,
1050
+ onStop,
1051
+ onReset,
1052
+ onTick,
1053
+ showReset = true,
1054
+ size = "md",
1055
+ className = "",
1056
+ isRunning: controlledIsRunning,
1057
+ elapsedSeconds: controlledElapsedSeconds
1058
+ }) {
1059
+ const isControlled = controlledIsRunning !== void 0;
1060
+ const [internalIsRunning, setInternalIsRunning] = (0, import_react9.useState)(false);
1061
+ const [internalElapsedSeconds, setInternalElapsedSeconds] = (0, import_react9.useState)(initialSeconds);
1062
+ const isRunning = isControlled ? controlledIsRunning : internalIsRunning;
1063
+ const elapsedSeconds = isControlled ? controlledElapsedSeconds ?? 0 : internalElapsedSeconds;
1064
+ const intervalRef = (0, import_react9.useRef)(null);
1065
+ const startTimeRef = (0, import_react9.useRef)(null);
1066
+ (0, import_react9.useEffect)(() => {
1067
+ return () => {
1068
+ if (intervalRef.current) {
1069
+ clearInterval(intervalRef.current);
1070
+ }
1071
+ };
1072
+ }, []);
1073
+ (0, import_react9.useEffect)(() => {
1074
+ if (isControlled) return;
1075
+ if (isRunning) {
1076
+ intervalRef.current = setInterval(() => {
1077
+ setInternalElapsedSeconds((prev) => {
1078
+ const newValue = prev + 1;
1079
+ onTick?.(newValue);
1080
+ return newValue;
1081
+ });
1082
+ }, 1e3);
1083
+ } else {
1084
+ if (intervalRef.current) {
1085
+ clearInterval(intervalRef.current);
1086
+ intervalRef.current = null;
1087
+ }
1088
+ }
1089
+ return () => {
1090
+ if (intervalRef.current) {
1091
+ clearInterval(intervalRef.current);
1092
+ intervalRef.current = null;
1093
+ }
1094
+ };
1095
+ }, [isRunning, isControlled, onTick]);
1096
+ const handleStart = (0, import_react9.useCallback)(() => {
1097
+ const now = /* @__PURE__ */ new Date();
1098
+ startTimeRef.current = now;
1099
+ if (!isControlled) {
1100
+ setInternalIsRunning(true);
1101
+ }
1102
+ onStart?.(now);
1103
+ }, [isControlled, onStart]);
1104
+ const handleStop = (0, import_react9.useCallback)(() => {
1105
+ if (!isControlled) {
1106
+ setInternalIsRunning(false);
1107
+ }
1108
+ onStop?.(elapsedSeconds);
1109
+ }, [isControlled, elapsedSeconds, onStop]);
1110
+ const handleReset = (0, import_react9.useCallback)(() => {
1111
+ if (!isControlled) {
1112
+ setInternalIsRunning(false);
1113
+ setInternalElapsedSeconds(0);
1114
+ }
1115
+ startTimeRef.current = null;
1116
+ onReset?.();
1117
+ }, [isControlled, onReset]);
1118
+ const handleToggle = (0, import_react9.useCallback)(() => {
1119
+ if (isRunning) {
1120
+ handleStop();
1121
+ } else {
1122
+ handleStart();
1123
+ }
1124
+ }, [isRunning, handleStart, handleStop]);
1125
+ const sizeClasses3 = {
1126
+ sm: {
1127
+ container: "gap-2",
1128
+ time: "text-2xl",
1129
+ button: "w-10 h-10",
1130
+ icon: "w-4 h-4"
1131
+ },
1132
+ md: {
1133
+ container: "gap-3",
1134
+ time: "text-4xl",
1135
+ button: "w-12 h-12",
1136
+ icon: "w-5 h-5"
1137
+ },
1138
+ lg: {
1139
+ container: "gap-4",
1140
+ time: "text-5xl",
1141
+ button: "w-14 h-14",
1142
+ icon: "w-6 h-6"
1143
+ }
1144
+ };
1145
+ const styles = sizeClasses3[size];
1146
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `flex items-center ${styles.container} ${className}`, children: [
1147
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: `font-mono font-bold text-gray-800 ${styles.time} tabular-nums`, children: formatTime(elapsedSeconds) }),
1148
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
1149
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1150
+ "button",
1151
+ {
1152
+ type: "button",
1153
+ onClick: handleToggle,
1154
+ className: `
1155
+ ${styles.button} rounded-full flex items-center justify-center
1156
+ ${isRunning ? "bg-amber-500 hover:bg-amber-600 text-white" : "bg-green-500 hover:bg-green-600 text-white"}
1157
+ transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2
1158
+ ${isRunning ? "focus:ring-amber-500" : "focus:ring-green-500"}
1159
+ `,
1160
+ title: isRunning ? "Pause" : "Start",
1161
+ children: isRunning ? (
1162
+ // Pause icon
1163
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" }) })
1164
+ ) : (
1165
+ // Play icon
1166
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M8 5v14l11-7z" }) })
1167
+ )
1168
+ }
1169
+ ),
1170
+ elapsedSeconds > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1171
+ "button",
1172
+ {
1173
+ type: "button",
1174
+ onClick: handleStop,
1175
+ className: `
1176
+ ${styles.button} rounded-full flex items-center justify-center
1177
+ bg-red-500 hover:bg-red-600 text-white
1178
+ transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500
1179
+ `,
1180
+ title: "Stop",
1181
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M6 6h12v12H6z" }) })
1182
+ }
1183
+ ),
1184
+ showReset && elapsedSeconds > 0 && !isRunning && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1185
+ "button",
1186
+ {
1187
+ type: "button",
1188
+ onClick: handleReset,
1189
+ className: `
1190
+ ${styles.button} rounded-full flex items-center justify-center
1191
+ bg-gray-200 hover:bg-gray-300 text-gray-700
1192
+ transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-400
1193
+ `,
1194
+ title: "Reset",
1195
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { className: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) })
1196
+ }
1197
+ )
1198
+ ] })
1199
+ ] });
1200
+ }
1201
+ // Annotate the CommonJS export names for ESM import in node:
1202
+ 0 && (module.exports = {
1203
+ Button,
1204
+ Card,
1205
+ DatePicker,
1206
+ EmptyState,
1207
+ FormButtonGroup,
1208
+ FormInput,
1209
+ FormTextarea,
1210
+ Input,
1211
+ Layout,
1212
+ LoadingSpinner,
1213
+ Modal,
1214
+ Select,
1215
+ ServiceSwitcher,
1216
+ Sidebar,
1217
+ Table,
1218
+ Tabs,
1219
+ Timer,
1220
+ formatTime
1221
+ });
1222
+ //# sourceMappingURL=index.js.map