@kreativa/ui 0.1.0 → 0.1.2

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.mjs CHANGED
@@ -286,7 +286,7 @@ function FormButtonGroup({
286
286
  }
287
287
 
288
288
  // src/components/Table.tsx
289
- import { useState, useMemo } from "react";
289
+ import { useState, useMemo, useRef, useCallback } from "react";
290
290
  import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
291
291
  function Table({
292
292
  data,
@@ -295,10 +295,15 @@ function Table({
295
295
  onRowClick,
296
296
  loading = false,
297
297
  emptyMessage = "No data available",
298
- className = ""
298
+ className = "",
299
+ resizable = false,
300
+ fixedLayout = false
299
301
  }) {
300
302
  const [sortKey, setSortKey] = useState(null);
301
303
  const [sortDirection, setSortDirection] = useState(null);
304
+ const [columnWidths, setColumnWidths] = useState({});
305
+ const tableRef = useRef(null);
306
+ const resizingRef = useRef(null);
302
307
  const handleHeaderClick = (column) => {
303
308
  if (!column.sortable) return;
304
309
  if (sortKey === column.key) {
@@ -327,67 +332,140 @@ function Table({
327
332
  });
328
333
  return sortDirection === "desc" ? sorted.reverse() : sorted;
329
334
  }, [data, columns, sortKey, sortDirection]);
335
+ const handleResizeStart = useCallback(
336
+ (e, columnKey) => {
337
+ e.preventDefault();
338
+ e.stopPropagation();
339
+ const headerCell = e.currentTarget.parentElement;
340
+ if (!headerCell) return;
341
+ const startWidth = headerCell.getBoundingClientRect().width;
342
+ resizingRef.current = {
343
+ columnKey,
344
+ startX: e.clientX,
345
+ startWidth
346
+ };
347
+ document.addEventListener("mousemove", handleResizeMove);
348
+ document.addEventListener("mouseup", handleResizeEnd);
349
+ document.body.style.cursor = "col-resize";
350
+ document.body.style.userSelect = "none";
351
+ },
352
+ []
353
+ );
354
+ const handleResizeMove = useCallback((e) => {
355
+ if (!resizingRef.current) return;
356
+ const { columnKey, startX, startWidth } = resizingRef.current;
357
+ const column = columns.find((c) => c.key === columnKey);
358
+ const minWidth = column?.minWidth ?? 50;
359
+ const newWidth = Math.max(minWidth, startWidth + (e.clientX - startX));
360
+ setColumnWidths((prev) => ({
361
+ ...prev,
362
+ [columnKey]: newWidth
363
+ }));
364
+ }, [columns]);
365
+ const handleResizeEnd = useCallback(() => {
366
+ resizingRef.current = null;
367
+ document.removeEventListener("mousemove", handleResizeMove);
368
+ document.removeEventListener("mouseup", handleResizeEnd);
369
+ document.body.style.cursor = "";
370
+ document.body.style.userSelect = "";
371
+ }, [handleResizeMove]);
330
372
  const getSortIcon = (column) => {
331
373
  if (!column.sortable) return null;
332
374
  if (sortKey !== column.key) {
333
- return /* @__PURE__ */ jsx13("svg", { className: "w-4 h-4 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx13("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) });
375
+ return /* @__PURE__ */ jsx13("svg", { className: "w-4 h-4 text-gray-400 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx13("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) });
334
376
  }
335
377
  if (sortDirection === "asc") {
336
- return /* @__PURE__ */ jsx13("svg", { className: "w-4 h-4 text-primary-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx13("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 15l7-7 7 7" }) });
378
+ return /* @__PURE__ */ jsx13("svg", { className: "w-4 h-4 text-primary-600 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx13("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 15l7-7 7 7" }) });
337
379
  }
338
- return /* @__PURE__ */ jsx13("svg", { className: "w-4 h-4 text-primary-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx13("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) });
380
+ return /* @__PURE__ */ jsx13("svg", { className: "w-4 h-4 text-primary-600 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx13("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) });
339
381
  };
340
382
  const alignmentClasses = {
341
383
  left: "text-left",
342
384
  center: "text-center",
343
385
  right: "text-right"
344
386
  };
345
- return /* @__PURE__ */ jsx13("div", { className: `overflow-x-auto ${className}`, children: /* @__PURE__ */ jsxs9("table", { className: "w-full border-collapse", children: [
346
- /* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsx13("tr", { className: "bg-gray-50 border-b border-gray-200", children: columns.map((column) => /* @__PURE__ */ jsx13(
347
- "th",
348
- {
349
- className: `
350
- px-4 py-3 text-sm font-semibold text-gray-700
387
+ const getColumnStyle = (column) => {
388
+ if (columnWidths[column.key]) {
389
+ return { width: columnWidths[column.key], minWidth: columnWidths[column.key] };
390
+ }
391
+ if (column.width) {
392
+ return { width: column.width, minWidth: column.minWidth ?? 50 };
393
+ }
394
+ return { minWidth: column.minWidth ?? 50 };
395
+ };
396
+ return /* @__PURE__ */ jsx13("div", { className: `overflow-x-auto ${className}`, children: /* @__PURE__ */ jsxs9(
397
+ "table",
398
+ {
399
+ ref: tableRef,
400
+ className: "w-full border-collapse",
401
+ style: { tableLayout: fixedLayout || resizable ? "fixed" : "auto" },
402
+ children: [
403
+ /* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsx13("tr", { className: "bg-gray-50 border-b border-gray-200", children: columns.map((column, index) => /* @__PURE__ */ jsxs9(
404
+ "th",
405
+ {
406
+ className: `
407
+ px-4 py-3 text-sm font-semibold text-gray-700 relative
351
408
  ${alignmentClasses[column.align ?? "left"]}
352
409
  ${column.sortable ? "cursor-pointer select-none hover:bg-gray-100" : ""}
353
410
  `,
354
- style: { width: column.width },
355
- onClick: () => handleHeaderClick(column),
356
- children: /* @__PURE__ */ jsxs9("div", { className: `flex items-center gap-1 ${column.align === "right" ? "justify-end" : column.align === "center" ? "justify-center" : ""}`, children: [
357
- /* @__PURE__ */ jsx13("span", { children: column.header }),
358
- getSortIcon(column)
359
- ] })
360
- },
361
- column.key
362
- )) }) }),
363
- /* @__PURE__ */ jsx13("tbody", { children: loading ? /* @__PURE__ */ jsx13("tr", { children: /* @__PURE__ */ jsx13("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-center gap-2", children: [
364
- /* @__PURE__ */ jsx13("div", { className: "w-5 h-5 border-2 border-primary-200 border-t-primary-600 rounded-full animate-spin" }),
365
- /* @__PURE__ */ jsx13("span", { children: "Loading..." })
366
- ] }) }) }) : sortedData.length === 0 ? /* @__PURE__ */ jsx13("tr", { children: /* @__PURE__ */ jsx13("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: emptyMessage }) }) : sortedData.map((item) => /* @__PURE__ */ jsx13(
367
- "tr",
368
- {
369
- className: `
411
+ style: getColumnStyle(column),
412
+ onClick: () => handleHeaderClick(column),
413
+ children: [
414
+ /* @__PURE__ */ jsxs9(
415
+ "div",
416
+ {
417
+ className: `flex items-center gap-1 overflow-hidden ${column.align === "right" ? "justify-end" : column.align === "center" ? "justify-center" : ""}`,
418
+ children: [
419
+ /* @__PURE__ */ jsx13("span", { className: "truncate", children: column.header }),
420
+ getSortIcon(column)
421
+ ]
422
+ }
423
+ ),
424
+ resizable && index < columns.length - 1 && /* @__PURE__ */ jsx13(
425
+ "div",
426
+ {
427
+ className: "absolute top-0 right-0 w-1 h-full cursor-col-resize bg-transparent hover:bg-primary-300 transition-colors",
428
+ onMouseDown: (e) => handleResizeStart(e, column.key),
429
+ onClick: (e) => e.stopPropagation()
430
+ }
431
+ )
432
+ ]
433
+ },
434
+ column.key
435
+ )) }) }),
436
+ /* @__PURE__ */ jsx13("tbody", { children: loading ? /* @__PURE__ */ jsx13("tr", { children: /* @__PURE__ */ jsx13("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-center gap-2", children: [
437
+ /* @__PURE__ */ jsx13("div", { className: "w-5 h-5 border-2 border-primary-200 border-t-primary-600 rounded-full animate-spin" }),
438
+ /* @__PURE__ */ jsx13("span", { children: "Loading..." })
439
+ ] }) }) }) : sortedData.length === 0 ? /* @__PURE__ */ jsx13("tr", { children: /* @__PURE__ */ jsx13("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: emptyMessage }) }) : sortedData.map((item) => /* @__PURE__ */ jsx13(
440
+ "tr",
441
+ {
442
+ className: `
370
443
  border-b border-gray-100 hover:bg-gray-50 transition-colors
371
444
  ${onRowClick ? "cursor-pointer" : ""}
372
445
  `,
373
- onClick: () => onRowClick?.(item),
374
- children: columns.map((column) => /* @__PURE__ */ jsx13(
375
- "td",
376
- {
377
- className: `px-4 py-3 text-sm text-gray-800 ${alignmentClasses[column.align ?? "left"]}`,
378
- style: { width: column.width },
379
- children: column.render(item)
446
+ onClick: () => onRowClick?.(item),
447
+ children: columns.map((column) => /* @__PURE__ */ jsx13(
448
+ "td",
449
+ {
450
+ className: `px-4 py-3 text-sm text-gray-800 overflow-hidden ${alignmentClasses[column.align ?? "left"]}`,
451
+ style: getColumnStyle(column),
452
+ children: /* @__PURE__ */ jsx13("div", { className: "truncate", children: column.render(item) })
453
+ },
454
+ column.key
455
+ ))
380
456
  },
381
- column.key
382
- ))
383
- },
384
- getRowKey(item)
385
- )) })
386
- ] }) });
457
+ getRowKey(item)
458
+ )) })
459
+ ]
460
+ }
461
+ ) });
387
462
  }
388
463
 
464
+ // src/components/DataTable.tsx
465
+ import { useState as useState3, useMemo as useMemo3, useCallback as useCallback2 } from "react";
466
+
389
467
  // src/components/Select.tsx
390
- import { useState as useState2, useRef, useEffect as useEffect2, useMemo as useMemo2 } from "react";
468
+ import { useState as useState2, useRef as useRef2, useEffect as useEffect2, useMemo as useMemo2 } from "react";
391
469
  import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
392
470
  function Select({
393
471
  options,
@@ -404,8 +482,8 @@ function Select({
404
482
  const [isOpen, setIsOpen] = useState2(false);
405
483
  const [searchTerm, setSearchTerm] = useState2("");
406
484
  const [highlightedIndex, setHighlightedIndex] = useState2(0);
407
- const containerRef = useRef(null);
408
- const inputRef = useRef(null);
485
+ const containerRef = useRef2(null);
486
+ const inputRef = useRef2(null);
409
487
  const filteredOptions = useMemo2(() => {
410
488
  if (!searchTerm) return options;
411
489
  const term = searchTerm.toLowerCase();
@@ -569,9 +647,245 @@ function Select({
569
647
  ] });
570
648
  }
571
649
 
572
- // src/components/Tabs.tsx
573
- import { useState as useState3 } from "react";
650
+ // src/components/DataTable.tsx
574
651
  import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
652
+ function ColumnFilter({ column, value, onChange }) {
653
+ const filterType = column.filterType ?? "text";
654
+ if (filterType === "none") {
655
+ return /* @__PURE__ */ jsx15("div", { className: "h-9" });
656
+ }
657
+ if (filterType === "select" && column.filterOptions) {
658
+ return /* @__PURE__ */ jsx15(
659
+ Select,
660
+ {
661
+ options: [{ value: "", label: "Alla" }, ...column.filterOptions],
662
+ value: value || "",
663
+ onChange: (val) => onChange(val),
664
+ placeholder: column.filterPlaceholder || "Alla",
665
+ className: "w-full text-sm"
666
+ }
667
+ );
668
+ }
669
+ if (filterType === "multiselect" && column.filterOptions) {
670
+ return /* @__PURE__ */ jsx15(
671
+ Select,
672
+ {
673
+ options: column.filterOptions,
674
+ value: value || [],
675
+ onChange: (val) => onChange(val),
676
+ placeholder: column.filterPlaceholder || "Alla",
677
+ multiple: true,
678
+ className: "w-full text-sm"
679
+ }
680
+ );
681
+ }
682
+ if (filterType === "boolean") {
683
+ return /* @__PURE__ */ jsx15(
684
+ Select,
685
+ {
686
+ options: [
687
+ { value: "", label: "Alla" },
688
+ { value: "true", label: "Ja" },
689
+ { value: "false", label: "Nej" }
690
+ ],
691
+ value: value || "",
692
+ onChange: (val) => onChange(val),
693
+ className: "w-full text-sm"
694
+ }
695
+ );
696
+ }
697
+ return /* @__PURE__ */ jsx15(
698
+ Input,
699
+ {
700
+ type: "text",
701
+ value: value || "",
702
+ onChange: (e) => onChange(e.target.value),
703
+ placeholder: column.filterPlaceholder || `Filtrera ${column.header.toLowerCase()}...`,
704
+ className: "w-full text-sm h-9"
705
+ }
706
+ );
707
+ }
708
+ function DataTable({
709
+ data,
710
+ columns,
711
+ getRowKey,
712
+ onRowClick,
713
+ loading,
714
+ emptyMessage = "Ingen data att visa.",
715
+ className,
716
+ showFilters = true,
717
+ initialSortKey,
718
+ initialSortDirection = "asc",
719
+ onFilteredDataChange
720
+ }) {
721
+ const [filters, setFilters] = useState3({});
722
+ const [sortKey, setSortKey] = useState3(initialSortKey || null);
723
+ const [sortDirection, setSortDirection] = useState3(initialSortDirection);
724
+ const handleFilterChange = useCallback2((columnKey, value) => {
725
+ setFilters((prev) => {
726
+ const next = { ...prev };
727
+ if (value === "" || Array.isArray(value) && value.length === 0) {
728
+ delete next[columnKey];
729
+ } else {
730
+ next[columnKey] = value;
731
+ }
732
+ return next;
733
+ });
734
+ }, []);
735
+ const clearFilters = useCallback2(() => {
736
+ setFilters({});
737
+ }, []);
738
+ const filteredData = useMemo3(() => {
739
+ let result = [...data];
740
+ for (const [columnKey, filterValue] of Object.entries(filters)) {
741
+ if (!filterValue || Array.isArray(filterValue) && filterValue.length === 0) continue;
742
+ const column = columns.find((c) => c.key === columnKey);
743
+ if (!column) continue;
744
+ const getFilterableValue = column.filterValue || ((item) => {
745
+ return item[columnKey];
746
+ });
747
+ result = result.filter((item) => {
748
+ const itemValue = getFilterableValue(item);
749
+ if (itemValue === null || itemValue === void 0) {
750
+ return filterValue === "";
751
+ }
752
+ const filterType = column.filterType ?? "text";
753
+ if (filterType === "boolean") {
754
+ const boolFilterValue = filterValue === "true";
755
+ return itemValue === boolFilterValue;
756
+ }
757
+ if (filterType === "multiselect" && Array.isArray(filterValue)) {
758
+ return filterValue.includes(String(itemValue));
759
+ }
760
+ if (filterType === "select") {
761
+ return String(itemValue).toLowerCase() === String(filterValue).toLowerCase();
762
+ }
763
+ return String(itemValue).toLowerCase().includes(String(filterValue).toLowerCase());
764
+ });
765
+ }
766
+ return result;
767
+ }, [data, filters, columns]);
768
+ const sortedData = useMemo3(() => {
769
+ if (!sortKey) return filteredData;
770
+ const column = columns.find((c) => c.key === sortKey);
771
+ if (!column) return filteredData;
772
+ const sorted = [...filteredData].sort((a, b) => {
773
+ if (column.sortFn) {
774
+ return column.sortFn(a, b);
775
+ }
776
+ const getValueForSort = column.filterValue || ((item) => {
777
+ return item[sortKey];
778
+ });
779
+ const aVal = getValueForSort(a);
780
+ const bVal = getValueForSort(b);
781
+ if (aVal === null || aVal === void 0) return 1;
782
+ if (bVal === null || bVal === void 0) return -1;
783
+ if (typeof aVal === "string" && typeof bVal === "string") {
784
+ return aVal.localeCompare(bVal, "sv");
785
+ }
786
+ if (aVal < bVal) return -1;
787
+ if (aVal > bVal) return 1;
788
+ return 0;
789
+ });
790
+ return sortDirection === "desc" ? sorted.reverse() : sorted;
791
+ }, [filteredData, sortKey, sortDirection, columns]);
792
+ useMemo3(() => {
793
+ onFilteredDataChange?.(sortedData);
794
+ }, [sortedData, onFilteredDataChange]);
795
+ const handleSort = useCallback2((columnKey) => {
796
+ if (sortKey === columnKey) {
797
+ setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
798
+ } else {
799
+ setSortKey(columnKey);
800
+ setSortDirection("asc");
801
+ }
802
+ }, [sortKey]);
803
+ const tableColumns = useMemo3(() => {
804
+ return columns.map((column) => {
805
+ const isSortable = column.sortable !== false && column.filterType !== "none";
806
+ const isCurrentSort = sortKey === column.key;
807
+ return {
808
+ key: column.key,
809
+ header: isSortable ? /* @__PURE__ */ jsxs11(
810
+ "button",
811
+ {
812
+ onClick: () => handleSort(column.key),
813
+ className: "flex items-center gap-1 hover:text-gray-900 transition-colors w-full text-left",
814
+ children: [
815
+ column.header,
816
+ isCurrentSort ? /* @__PURE__ */ jsx15("span", { className: "text-primary-600", children: sortDirection === "asc" ? "\u2191" : "\u2193" }) : /* @__PURE__ */ jsx15("span", { className: "text-gray-400 opacity-50", children: "\u2195" })
817
+ ]
818
+ }
819
+ ) : column.header,
820
+ render: column.render,
821
+ width: column.width,
822
+ align: column.align,
823
+ sortable: false
824
+ // We handle sorting ourselves
825
+ };
826
+ });
827
+ }, [columns, sortKey, sortDirection, handleSort]);
828
+ const hasActiveFilters = Object.keys(filters).length > 0;
829
+ return /* @__PURE__ */ jsxs11("div", { className, children: [
830
+ showFilters && /* @__PURE__ */ jsxs11("div", { className: "mb-4", children: [
831
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-2 mb-2", children: [
832
+ /* @__PURE__ */ jsxs11("span", { className: "text-sm text-gray-500", children: [
833
+ "Visar ",
834
+ sortedData.length,
835
+ " av ",
836
+ data.length
837
+ ] }),
838
+ hasActiveFilters && /* @__PURE__ */ jsx15(
839
+ "button",
840
+ {
841
+ onClick: clearFilters,
842
+ className: "text-sm text-primary-600 hover:underline",
843
+ children: "Rensa filter"
844
+ }
845
+ )
846
+ ] }),
847
+ /* @__PURE__ */ jsx15("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-3 p-3 bg-gray-50 border border-gray-200 rounded-lg", children: columns.filter((col) => col.filterType !== "none").map((column) => /* @__PURE__ */ jsxs11("div", { className: "min-w-0", children: [
848
+ /* @__PURE__ */ jsx15("label", { className: "text-xs text-gray-500 mb-1 block truncate", children: column.header }),
849
+ /* @__PURE__ */ jsx15(
850
+ ColumnFilter,
851
+ {
852
+ column,
853
+ value: filters[column.key] || "",
854
+ onChange: (value) => handleFilterChange(column.key, value)
855
+ }
856
+ )
857
+ ] }, column.key)) })
858
+ ] }),
859
+ /* @__PURE__ */ jsx15(
860
+ Table,
861
+ {
862
+ data: sortedData,
863
+ columns: tableColumns,
864
+ getRowKey,
865
+ onRowClick,
866
+ loading,
867
+ emptyMessage
868
+ }
869
+ )
870
+ ] });
871
+ }
872
+ function createFilterOptions(data, getValue, labelMap) {
873
+ const uniqueValues = /* @__PURE__ */ new Set();
874
+ for (const item of data) {
875
+ const value = getValue(item);
876
+ if (value !== null && value !== void 0 && value !== "") {
877
+ uniqueValues.add(value);
878
+ }
879
+ }
880
+ return Array.from(uniqueValues).sort((a, b) => a.localeCompare(b, "sv")).map((value) => ({
881
+ value,
882
+ label: labelMap?.[value] || value
883
+ }));
884
+ }
885
+
886
+ // src/components/Tabs.tsx
887
+ import { useState as useState4 } from "react";
888
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
575
889
  function Tabs({
576
890
  tabs,
577
891
  activeTab: controlledActiveTab,
@@ -582,7 +896,7 @@ function Tabs({
582
896
  className = ""
583
897
  }) {
584
898
  const isControlled = controlledActiveTab !== void 0;
585
- const [internalActiveTab, setInternalActiveTab] = useState3(
899
+ const [internalActiveTab, setInternalActiveTab] = useState4(
586
900
  defaultTab || tabs.find((t) => !t.disabled)?.id || tabs[0]?.id
587
901
  );
588
902
  const activeTab = isControlled ? controlledActiveTab : internalActiveTab;
@@ -650,11 +964,11 @@ function Tabs({
650
964
  }
651
965
  };
652
966
  const styles = variantStyles[variant];
653
- return /* @__PURE__ */ jsxs11("div", { className, children: [
654
- /* @__PURE__ */ jsx15("div", { className: `flex ${styles.container}`, role: "tablist", children: tabs.map((tab, index) => {
967
+ return /* @__PURE__ */ jsxs12("div", { className, children: [
968
+ /* @__PURE__ */ jsx16("div", { className: `flex ${styles.container}`, role: "tablist", children: tabs.map((tab, index) => {
655
969
  const isActive = tab.id === activeTab;
656
970
  const isDisabled = tab.disabled ?? false;
657
- return /* @__PURE__ */ jsx15(
971
+ return /* @__PURE__ */ jsx16(
658
972
  "button",
659
973
  {
660
974
  type: "button",
@@ -666,7 +980,7 @@ function Tabs({
666
980
  className: styles.tab(isActive, isDisabled),
667
981
  onClick: () => handleTabClick(tab),
668
982
  onKeyDown: (e) => handleKeyDown(e, index),
669
- children: /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-2", children: [
983
+ children: /* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-2", children: [
670
984
  tab.icon,
671
985
  tab.label
672
986
  ] })
@@ -674,13 +988,13 @@ function Tabs({
674
988
  tab.id
675
989
  );
676
990
  }) }),
677
- renderContent && activeContent && /* @__PURE__ */ jsx15("div", { className: "pt-4", role: "tabpanel", children: activeContent })
991
+ renderContent && activeContent && /* @__PURE__ */ jsx16("div", { className: "pt-4", role: "tabpanel", children: activeContent })
678
992
  ] });
679
993
  }
680
994
 
681
995
  // src/components/DatePicker.tsx
682
- import { useState as useState4, useRef as useRef2, useEffect as useEffect3, useMemo as useMemo3 } from "react";
683
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
996
+ import { useState as useState5, useRef as useRef3, useEffect as useEffect3, useMemo as useMemo4 } from "react";
997
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
684
998
  var WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
685
999
  var MONTHS = [
686
1000
  "January",
@@ -783,11 +1097,11 @@ function DatePicker({
783
1097
  disabled = false,
784
1098
  className = ""
785
1099
  }) {
786
- const [isOpen, setIsOpen] = useState4(false);
787
- const [viewDate, setViewDate] = useState4(/* @__PURE__ */ new Date());
788
- const [hoverDate, setHoverDate] = useState4(null);
789
- const containerRef = useRef2(null);
790
- const [selectingEnd, setSelectingEnd] = useState4(false);
1100
+ const [isOpen, setIsOpen] = useState5(false);
1101
+ const [viewDate, setViewDate] = useState5(/* @__PURE__ */ new Date());
1102
+ const [hoverDate, setHoverDate] = useState5(null);
1103
+ const containerRef = useRef3(null);
1104
+ const [selectingEnd, setSelectingEnd] = useState5(false);
791
1105
  useEffect3(() => {
792
1106
  const handleClickOutside = (e) => {
793
1107
  if (containerRef.current && !containerRef.current.contains(e.target)) {
@@ -798,7 +1112,7 @@ function DatePicker({
798
1112
  document.addEventListener("mousedown", handleClickOutside);
799
1113
  return () => document.removeEventListener("mousedown", handleClickOutside);
800
1114
  }, []);
801
- const displayText = useMemo3(() => {
1115
+ const displayText = useMemo4(() => {
802
1116
  if (!value) return "";
803
1117
  if (range) {
804
1118
  const rangeValue = value;
@@ -815,7 +1129,7 @@ function DatePicker({
815
1129
  }
816
1130
  return formatDate(value);
817
1131
  }, [value, range]);
818
- const calendarDays = useMemo3(() => {
1132
+ const calendarDays = useMemo4(() => {
819
1133
  const year = viewDate.getFullYear();
820
1134
  const month = viewDate.getMonth();
821
1135
  const firstDay = new Date(year, month, 1);
@@ -898,9 +1212,9 @@ function DatePicker({
898
1212
  };
899
1213
  const today = /* @__PURE__ */ new Date();
900
1214
  today.setHours(0, 0, 0, 0);
901
- return /* @__PURE__ */ jsxs12("div", { ref: containerRef, className: `relative ${className}`, children: [
902
- label && /* @__PURE__ */ jsx16("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: label }),
903
- /* @__PURE__ */ jsx16(
1215
+ return /* @__PURE__ */ jsxs13("div", { ref: containerRef, className: `relative ${className}`, children: [
1216
+ label && /* @__PURE__ */ jsx17("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: label }),
1217
+ /* @__PURE__ */ jsx17(
904
1218
  "div",
905
1219
  {
906
1220
  className: `
@@ -910,15 +1224,15 @@ function DatePicker({
910
1224
  transition-all
911
1225
  `,
912
1226
  onClick: () => !disabled && setIsOpen(!isOpen),
913
- children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between", children: [
914
- /* @__PURE__ */ jsx16("span", { className: displayText ? "text-gray-800" : "text-gray-400", children: displayText || placeholder }),
915
- /* @__PURE__ */ jsx16("svg", { className: "w-5 h-5 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx16("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" }) })
1227
+ children: /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between", children: [
1228
+ /* @__PURE__ */ jsx17("span", { className: displayText ? "text-gray-800" : "text-gray-400", children: displayText || placeholder }),
1229
+ /* @__PURE__ */ jsx17("svg", { className: "w-5 h-5 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx17("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" }) })
916
1230
  ] })
917
1231
  }
918
1232
  ),
919
- error && /* @__PURE__ */ jsx16("p", { className: "mt-1 text-xs text-red-600", children: error }),
920
- isOpen && /* @__PURE__ */ jsx16("div", { className: "absolute z-50 mt-1 bg-white border border-gray-200 rounded-xl shadow-lg p-4", children: /* @__PURE__ */ jsxs12("div", { className: range ? "flex gap-4" : "", children: [
921
- range && /* @__PURE__ */ jsx16("div", { className: "border-r border-gray-100 pr-4 space-y-1", children: PRESET_RANGES.map((preset) => /* @__PURE__ */ jsx16(
1233
+ error && /* @__PURE__ */ jsx17("p", { className: "mt-1 text-xs text-red-600", children: error }),
1234
+ isOpen && /* @__PURE__ */ jsx17("div", { className: "absolute z-50 mt-1 bg-white border border-gray-200 rounded-xl shadow-lg p-4", children: /* @__PURE__ */ jsxs13("div", { className: range ? "flex gap-4" : "", children: [
1235
+ range && /* @__PURE__ */ jsx17("div", { className: "border-r border-gray-100 pr-4 space-y-1", children: PRESET_RANGES.map((preset) => /* @__PURE__ */ jsx17(
922
1236
  "button",
923
1237
  {
924
1238
  type: "button",
@@ -928,42 +1242,42 @@ function DatePicker({
928
1242
  },
929
1243
  preset.label
930
1244
  )) }),
931
- /* @__PURE__ */ jsxs12("div", { children: [
932
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between mb-4", children: [
933
- /* @__PURE__ */ jsx16(
1245
+ /* @__PURE__ */ jsxs13("div", { children: [
1246
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between mb-4", children: [
1247
+ /* @__PURE__ */ jsx17(
934
1248
  "button",
935
1249
  {
936
1250
  type: "button",
937
1251
  className: "p-1 hover:bg-gray-100 rounded",
938
1252
  onClick: goToPrevMonth,
939
- children: /* @__PURE__ */ jsx16("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx16("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) })
1253
+ children: /* @__PURE__ */ jsx17("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx17("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) })
940
1254
  }
941
1255
  ),
942
- /* @__PURE__ */ jsxs12("span", { className: "text-sm font-semibold text-gray-800", children: [
1256
+ /* @__PURE__ */ jsxs13("span", { className: "text-sm font-semibold text-gray-800", children: [
943
1257
  MONTHS[viewDate.getMonth()],
944
1258
  " ",
945
1259
  viewDate.getFullYear()
946
1260
  ] }),
947
- /* @__PURE__ */ jsx16(
1261
+ /* @__PURE__ */ jsx17(
948
1262
  "button",
949
1263
  {
950
1264
  type: "button",
951
1265
  className: "p-1 hover:bg-gray-100 rounded",
952
1266
  onClick: goToNextMonth,
953
- children: /* @__PURE__ */ jsx16("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx16("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })
1267
+ children: /* @__PURE__ */ jsx17("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx17("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })
954
1268
  }
955
1269
  )
956
1270
  ] }),
957
- /* @__PURE__ */ jsx16("div", { className: "grid grid-cols-7 gap-1 mb-2", children: WEEKDAYS.map((day) => /* @__PURE__ */ jsx16("div", { className: "text-center text-xs font-medium text-gray-500 py-1", children: day }, day)) }),
958
- /* @__PURE__ */ jsx16("div", { className: "grid grid-cols-7 gap-1", children: calendarDays.map((date, index) => {
1271
+ /* @__PURE__ */ jsx17("div", { className: "grid grid-cols-7 gap-1 mb-2", children: WEEKDAYS.map((day) => /* @__PURE__ */ jsx17("div", { className: "text-center text-xs font-medium text-gray-500 py-1", children: day }, day)) }),
1272
+ /* @__PURE__ */ jsx17("div", { className: "grid grid-cols-7 gap-1", children: calendarDays.map((date, index) => {
959
1273
  if (!date) {
960
- return /* @__PURE__ */ jsx16("div", { className: "w-9 h-9" }, `empty-${index}`);
1274
+ return /* @__PURE__ */ jsx17("div", { className: "w-9 h-9" }, `empty-${index}`);
961
1275
  }
962
1276
  const isDisabled = isDateDisabled(date);
963
1277
  const isSelected = isDateSelected(date);
964
1278
  const inRange = isDateInRange(date);
965
1279
  const isToday = isSameDay(date, today);
966
- return /* @__PURE__ */ jsx16(
1280
+ return /* @__PURE__ */ jsx17(
967
1281
  "button",
968
1282
  {
969
1283
  type: "button",
@@ -989,8 +1303,8 @@ function DatePicker({
989
1303
  }
990
1304
 
991
1305
  // src/components/Timer.tsx
992
- import { useState as useState5, useEffect as useEffect4, useCallback, useRef as useRef3 } from "react";
993
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1306
+ import { useState as useState6, useEffect as useEffect4, useCallback as useCallback3, useRef as useRef4 } from "react";
1307
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
994
1308
  function formatTime(totalSeconds) {
995
1309
  const hours = Math.floor(totalSeconds / 3600);
996
1310
  const minutes = Math.floor(totalSeconds % 3600 / 60);
@@ -1014,12 +1328,12 @@ function Timer({
1014
1328
  elapsedSeconds: controlledElapsedSeconds
1015
1329
  }) {
1016
1330
  const isControlled = controlledIsRunning !== void 0;
1017
- const [internalIsRunning, setInternalIsRunning] = useState5(false);
1018
- const [internalElapsedSeconds, setInternalElapsedSeconds] = useState5(initialSeconds);
1331
+ const [internalIsRunning, setInternalIsRunning] = useState6(false);
1332
+ const [internalElapsedSeconds, setInternalElapsedSeconds] = useState6(initialSeconds);
1019
1333
  const isRunning = isControlled ? controlledIsRunning : internalIsRunning;
1020
1334
  const elapsedSeconds = isControlled ? controlledElapsedSeconds ?? 0 : internalElapsedSeconds;
1021
- const intervalRef = useRef3(null);
1022
- const startTimeRef = useRef3(null);
1335
+ const intervalRef = useRef4(null);
1336
+ const startTimeRef = useRef4(null);
1023
1337
  useEffect4(() => {
1024
1338
  return () => {
1025
1339
  if (intervalRef.current) {
@@ -1050,7 +1364,7 @@ function Timer({
1050
1364
  }
1051
1365
  };
1052
1366
  }, [isRunning, isControlled, onTick]);
1053
- const handleStart = useCallback(() => {
1367
+ const handleStart = useCallback3(() => {
1054
1368
  const now = /* @__PURE__ */ new Date();
1055
1369
  startTimeRef.current = now;
1056
1370
  if (!isControlled) {
@@ -1058,13 +1372,13 @@ function Timer({
1058
1372
  }
1059
1373
  onStart?.(now);
1060
1374
  }, [isControlled, onStart]);
1061
- const handleStop = useCallback(() => {
1375
+ const handleStop = useCallback3(() => {
1062
1376
  if (!isControlled) {
1063
1377
  setInternalIsRunning(false);
1064
1378
  }
1065
1379
  onStop?.(elapsedSeconds);
1066
1380
  }, [isControlled, elapsedSeconds, onStop]);
1067
- const handleReset = useCallback(() => {
1381
+ const handleReset = useCallback3(() => {
1068
1382
  if (!isControlled) {
1069
1383
  setInternalIsRunning(false);
1070
1384
  setInternalElapsedSeconds(0);
@@ -1072,7 +1386,7 @@ function Timer({
1072
1386
  startTimeRef.current = null;
1073
1387
  onReset?.();
1074
1388
  }, [isControlled, onReset]);
1075
- const handleToggle = useCallback(() => {
1389
+ const handleToggle = useCallback3(() => {
1076
1390
  if (isRunning) {
1077
1391
  handleStop();
1078
1392
  } else {
@@ -1100,10 +1414,10 @@ function Timer({
1100
1414
  }
1101
1415
  };
1102
1416
  const styles = sizeClasses3[size];
1103
- return /* @__PURE__ */ jsxs13("div", { className: `flex items-center ${styles.container} ${className}`, children: [
1104
- /* @__PURE__ */ jsx17("div", { className: `font-mono font-bold text-gray-800 ${styles.time} tabular-nums`, children: formatTime(elapsedSeconds) }),
1105
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
1106
- /* @__PURE__ */ jsx17(
1417
+ return /* @__PURE__ */ jsxs14("div", { className: `flex items-center ${styles.container} ${className}`, children: [
1418
+ /* @__PURE__ */ jsx18("div", { className: `font-mono font-bold text-gray-800 ${styles.time} tabular-nums`, children: formatTime(elapsedSeconds) }),
1419
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
1420
+ /* @__PURE__ */ jsx18(
1107
1421
  "button",
1108
1422
  {
1109
1423
  type: "button",
@@ -1117,14 +1431,14 @@ function Timer({
1117
1431
  title: isRunning ? "Pause" : "Start",
1118
1432
  children: isRunning ? (
1119
1433
  // Pause icon
1120
- /* @__PURE__ */ jsx17("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx17("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" }) })
1434
+ /* @__PURE__ */ jsx18("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx18("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" }) })
1121
1435
  ) : (
1122
1436
  // Play icon
1123
- /* @__PURE__ */ jsx17("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx17("path", { d: "M8 5v14l11-7z" }) })
1437
+ /* @__PURE__ */ jsx18("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx18("path", { d: "M8 5v14l11-7z" }) })
1124
1438
  )
1125
1439
  }
1126
1440
  ),
1127
- elapsedSeconds > 0 && /* @__PURE__ */ jsx17(
1441
+ elapsedSeconds > 0 && /* @__PURE__ */ jsx18(
1128
1442
  "button",
1129
1443
  {
1130
1444
  type: "button",
@@ -1135,10 +1449,10 @@ function Timer({
1135
1449
  transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500
1136
1450
  `,
1137
1451
  title: "Stop",
1138
- children: /* @__PURE__ */ jsx17("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx17("path", { d: "M6 6h12v12H6z" }) })
1452
+ children: /* @__PURE__ */ jsx18("svg", { className: styles.icon, fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx18("path", { d: "M6 6h12v12H6z" }) })
1139
1453
  }
1140
1454
  ),
1141
- showReset && elapsedSeconds > 0 && !isRunning && /* @__PURE__ */ jsx17(
1455
+ showReset && elapsedSeconds > 0 && !isRunning && /* @__PURE__ */ jsx18(
1142
1456
  "button",
1143
1457
  {
1144
1458
  type: "button",
@@ -1149,7 +1463,7 @@ function Timer({
1149
1463
  transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-400
1150
1464
  `,
1151
1465
  title: "Reset",
1152
- children: /* @__PURE__ */ jsx17("svg", { className: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx17("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" }) })
1466
+ children: /* @__PURE__ */ jsx18("svg", { className: styles.icon, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx18("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" }) })
1153
1467
  }
1154
1468
  )
1155
1469
  ] })
@@ -1158,6 +1472,7 @@ function Timer({
1158
1472
  export {
1159
1473
  Button,
1160
1474
  Card,
1475
+ DataTable,
1161
1476
  DatePicker,
1162
1477
  EmptyState,
1163
1478
  FormButtonGroup,
@@ -1173,6 +1488,7 @@ export {
1173
1488
  Table,
1174
1489
  Tabs,
1175
1490
  Timer,
1491
+ createFilterOptions,
1176
1492
  formatTime
1177
1493
  };
1178
1494
  //# sourceMappingURL=index.mjs.map