@cryptlex/web-components 5.2.0 → 5.3.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 (121) hide show
  1. package/dist/components/data-table/data-table-filter.d.ts +27 -0
  2. package/dist/components/data-table/data-table-filter.js +112 -0
  3. package/dist/components/data-table/data-table.d.ts +73 -0
  4. package/dist/components/data-table/data-table.js +265 -0
  5. package/dist/components/data-table/table-commons.d.ts +56 -0
  6. package/dist/components/data-table/table-commons.js +137 -0
  7. package/dist/components/inputs/checkbox.d.ts +8 -0
  8. package/dist/components/inputs/checkbox.js +25 -0
  9. package/dist/components/inputs/date-picker.d.ts +11 -0
  10. package/dist/components/inputs/date-picker.js +22 -0
  11. package/dist/components/inputs/datefield.d.ts +14 -0
  12. package/dist/components/inputs/datefield.js +25 -0
  13. package/dist/components/inputs/field.d.ts +21 -0
  14. package/dist/components/inputs/field.js +48 -0
  15. package/dist/components/inputs/id-search.d.ts +20 -0
  16. package/dist/components/inputs/id-search.js +40 -0
  17. package/dist/components/inputs/input-otp.d.ts +8 -0
  18. package/dist/components/inputs/input-otp.js +19 -0
  19. package/dist/components/inputs/multi-select.d.ts +17 -0
  20. package/dist/components/inputs/multi-select.js +18 -0
  21. package/dist/components/inputs/numberfield.d.ts +7 -0
  22. package/dist/components/inputs/numberfield.js +25 -0
  23. package/dist/components/inputs/searchfield.d.ts +5 -0
  24. package/dist/components/inputs/searchfield.js +24 -0
  25. package/dist/components/inputs/select-options.d.ts +8 -0
  26. package/dist/components/inputs/select-options.js +286 -0
  27. package/dist/components/inputs/select.d.ts +17 -0
  28. package/dist/components/inputs/select.js +34 -0
  29. package/dist/components/inputs/textfield.d.ts +7 -0
  30. package/dist/components/inputs/textfield.js +28 -0
  31. package/dist/components/key-value-card/key-value-card.d.ts +17 -0
  32. package/dist/components/key-value-card/key-value-card.js +40 -0
  33. package/dist/components/ui/alert.d.ts +8 -0
  34. package/dist/components/ui/alert.js +18 -0
  35. package/dist/components/ui/avatar.d.ts +8 -0
  36. package/dist/components/ui/avatar.js +5 -0
  37. package/dist/components/ui/badge.d.ts +2 -0
  38. package/dist/components/ui/badge.js +5 -0
  39. package/dist/components/ui/breadcrumbs.d.ts +10 -0
  40. package/dist/components/ui/breadcrumbs.js +28 -0
  41. package/dist/components/ui/button.d.ts +11 -0
  42. package/dist/components/ui/button.js +34 -0
  43. package/dist/components/ui/calendar.d.ts +17 -0
  44. package/dist/components/ui/calendar.js +63 -0
  45. package/dist/components/ui/card.d.ts +7 -0
  46. package/dist/components/ui/card.js +20 -0
  47. package/dist/components/ui/dialog.d.ts +19 -0
  48. package/dist/components/ui/dialog.js +42 -0
  49. package/dist/components/ui/disclosure.d.ts +19 -0
  50. package/dist/components/ui/disclosure.js +20 -0
  51. package/dist/components/ui/list-box.d.ts +5 -0
  52. package/dist/components/ui/list-box.js +24 -0
  53. package/dist/components/ui/loader.d.ts +5 -0
  54. package/dist/components/ui/loader.js +6 -0
  55. package/dist/components/ui/menu.d.ts +25 -0
  56. package/dist/components/ui/menu.js +38 -0
  57. package/dist/components/ui/popover.d.ts +4 -0
  58. package/dist/components/ui/popover.js +14 -0
  59. package/dist/components/ui/sidebar.d.ts +53 -0
  60. package/dist/components/ui/sidebar.js +177 -0
  61. package/dist/components/ui/skeleton.d.ts +1 -0
  62. package/dist/components/ui/skeleton.js +5 -0
  63. package/dist/components/ui/sonner.d.ts +4 -0
  64. package/dist/components/ui/sonner.js +14 -0
  65. package/dist/components/ui/table.d.ts +9 -0
  66. package/dist/components/ui/table.js +26 -0
  67. package/dist/components/ui/tabs.d.ts +5 -0
  68. package/dist/components/ui/tabs.js +25 -0
  69. package/dist/components/ui/timeline.d.ts +15 -0
  70. package/dist/components/ui/timeline.js +31 -0
  71. package/dist/components/ui/tooltip.d.ts +4 -0
  72. package/dist/components/ui/tooltip.js +12 -0
  73. package/dist/utils/form-context.d.ts +4 -0
  74. package/{lib/utils/form-context.tsx → dist/utils/form-context.js} +1 -3
  75. package/dist/utils/form-hook.d.ts +25 -0
  76. package/{lib/utils/form-hook.tsx → dist/utils/form-hook.js} +15 -18
  77. package/dist/utils/primitives.d.ts +30 -0
  78. package/{lib/utils/primitives.ts → dist/utils/primitives.js} +8 -37
  79. package/dist/utils/resource-names.d.ts +23 -0
  80. package/{lib/utils/resource-names.tsx → dist/utils/resource-names.js} +42 -75
  81. package/dist/utils/use-mobile.d.ts +1 -0
  82. package/dist/utils/use-mobile.js +15 -0
  83. package/package.json +10 -5
  84. package/lib/components/data-table/data-table-filter.tsx +0 -220
  85. package/lib/components/data-table/data-table.tsx +0 -593
  86. package/lib/components/data-table/table-commons.tsx +0 -233
  87. package/lib/components/inputs/checkbox.tsx +0 -72
  88. package/lib/components/inputs/date-picker.tsx +0 -130
  89. package/lib/components/inputs/datefield.tsx +0 -109
  90. package/lib/components/inputs/field.tsx +0 -106
  91. package/lib/components/inputs/id-search.tsx +0 -83
  92. package/lib/components/inputs/input-otp.tsx +0 -63
  93. package/lib/components/inputs/multi-select.tsx +0 -62
  94. package/lib/components/inputs/numberfield.tsx +0 -110
  95. package/lib/components/inputs/searchfield.tsx +0 -87
  96. package/lib/components/inputs/select-options.tsx +0 -303
  97. package/lib/components/inputs/select.tsx +0 -140
  98. package/lib/components/inputs/textfield.tsx +0 -96
  99. package/lib/components/key-value-card/key-value-card.tsx +0 -115
  100. package/lib/components/ui/alert.tsx +0 -32
  101. package/lib/components/ui/avatar.tsx +0 -22
  102. package/lib/components/ui/badge.tsx +0 -19
  103. package/lib/components/ui/breadcrumbs.tsx +0 -104
  104. package/lib/components/ui/button.tsx +0 -66
  105. package/lib/components/ui/calendar.tsx +0 -220
  106. package/lib/components/ui/card.tsx +0 -58
  107. package/lib/components/ui/dialog.tsx +0 -172
  108. package/lib/components/ui/disclosure.tsx +0 -113
  109. package/lib/components/ui/list-box.tsx +0 -86
  110. package/lib/components/ui/loader.tsx +0 -10
  111. package/lib/components/ui/menu.tsx +0 -168
  112. package/lib/components/ui/popover.tsx +0 -37
  113. package/lib/components/ui/sidebar.tsx +0 -552
  114. package/lib/components/ui/skeleton.tsx +0 -7
  115. package/lib/components/ui/sonner.tsx +0 -26
  116. package/lib/components/ui/table.tsx +0 -79
  117. package/lib/components/ui/tabs.tsx +0 -82
  118. package/lib/components/ui/timeline.tsx +0 -52
  119. package/lib/components/ui/tooltip.tsx +0 -30
  120. package/lib/tokens.scss +0 -89
  121. package/lib/utils/use-mobile.tsx +0 -21
@@ -0,0 +1,27 @@
1
+ import { type OperationKeys } from "lib/components/data-table/data-table";
2
+ import { type ApiFilters } from "lib/components/data-table/table-commons";
3
+ import type { MultiSelectProps } from "lib/components/inputs/multi-select";
4
+ export declare const FILTER_COMPARISON_OPERATORS: readonly ["eq", "ne", "cn", "nc", "sw", "ew", "in", "nin", "gt", "gte", "lt", "lte"];
5
+ export type FilterComparisonOperator = (typeof FILTER_COMPARISON_OPERATORS)[number];
6
+ export declare const COMPARISON_OPERATOR_LABELS: Record<FilterComparisonOperator, string>;
7
+ export type FilterConfig = {
8
+ type: 'enum';
9
+ options: MultiSelectProps['items'];
10
+ } | {
11
+ type: 'id-search';
12
+ search: () => Promise<{
13
+ id: string;
14
+ name: string;
15
+ }[]>;
16
+ } | {
17
+ type: 'id';
18
+ } | {
19
+ type: 'bool';
20
+ } | {
21
+ type: 'string';
22
+ } | {
23
+ type: 'date';
24
+ };
25
+ export type FilterType = FilterConfig['type'];
26
+ export type FiltersConfig<TOperation extends OperationKeys> = Required<Record<keyof ApiFilters<TOperation>, FilterConfig>>;
27
+ export declare function DataTableFilter({ className, ...props }: React.ComponentProps<'section'>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,112 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useDataTable } from "lib/components/data-table/data-table";
3
+ import { TfDatePicker } from "lib/components/inputs/date-picker";
4
+ import { TfIdSearchInput } from "lib/components/inputs/id-search";
5
+ import { Button } from "lib/components/ui/button";
6
+ import { EasyMenu, MenuItem } from "lib/components/ui/menu";
7
+ import { Popover, PopoverTrigger } from "lib/components/ui/popover";
8
+ import { useAppForm } from "lib/utils/form-hook";
9
+ import { cn } from "lib/utils/primitives";
10
+ import { useResourceFormatter } from "lib/utils/resource-names";
11
+ import { Filter, Trash } from "lucide-react";
12
+ import { useRef } from "react";
13
+ export const FILTER_COMPARISON_OPERATORS = [
14
+ 'eq',
15
+ 'ne',
16
+ 'cn',
17
+ 'nc',
18
+ 'sw',
19
+ 'ew',
20
+ 'in',
21
+ 'nin',
22
+ 'gt',
23
+ 'gte',
24
+ 'lt',
25
+ 'lte',
26
+ ];
27
+ export const COMPARISON_OPERATOR_LABELS = {
28
+ eq: 'equal to',
29
+ ne: 'not equal to',
30
+ cn: 'contains',
31
+ nc: 'does not contain',
32
+ sw: 'starts with',
33
+ ew: 'ends with',
34
+ in: 'includes',
35
+ nin: 'does not include',
36
+ gt: 'greater than',
37
+ gte: 'greater than or equal to',
38
+ lt: 'less than',
39
+ lte: 'less than or equal to',
40
+ };
41
+ const FilterOperations = {
42
+ 'bool': ['eq'],
43
+ 'enum': ['in', 'nin'],
44
+ 'id': ['in', 'nin'],
45
+ 'id-search': ['in', 'nin'],
46
+ 'string': ['eq', 'ne', 'cn', 'nc', 'sw', 'ew', 'in', 'nin'],
47
+ 'date': ['lt', 'gt'],
48
+ };
49
+ export function DataTableFilter({ className, ...props }) {
50
+ const { filters, query, setFilters, filterConfig } = useDataTable();
51
+ // TODO focus
52
+ // const localFilterRefs = useRef([]);
53
+ const addFilterButtonRef = useRef(null);
54
+ const resourceFormatter = useResourceFormatter();
55
+ const DEFAULT_VALUES = {
56
+ // TODO, initialize with filters from tableState
57
+ filters: []
58
+ };
59
+ const form = useAppForm({
60
+ defaultValues: DEFAULT_VALUES,
61
+ onSubmit: ({ value }) => {
62
+ // TODO, instead of generic transformation, use type based tranform()
63
+ setFilters(value.filters.map(lf => { return { [lf.property]: { [lf.operator]: [lf.value] } }; }));
64
+ }
65
+ });
66
+ // More reason to hate TS https://github.com/Microsoft/TypeScript/issues/12870
67
+ const filterKeys = Object.keys(filterConfig).sort();
68
+ return (_jsx("section", { ...props, className: cn("flex gap-icon items-center", className), children: _jsxs(PopoverTrigger, { onOpenChange: (o) => {
69
+ // Set filters when the popover closes
70
+ if (!o) {
71
+ form.handleSubmit();
72
+ }
73
+ else {
74
+ addFilterButtonRef.current?.focus();
75
+ }
76
+ }, children: [_jsx(Button, { active: filters.length > 0, isDisabled: query.isPending || filterKeys.length === 0, type: "button", size: 'icon', variant: 'neutral', children: _jsx(Filter, {}) }), _jsx(Popover, { className: "w-full p-icon", children: _jsx("form", { onSubmit: (e) => { e.preventDefault(); }, className: "flex flex-col gap-icon max-h-table overflow-auto ", children: _jsx(form.Field, { mode: "array", name: "filters", children: (field) => {
77
+ return _jsxs(_Fragment, { children: [field.state.value.map((lf, i) => {
78
+ return (_jsxs("div", { className: "flex gap-icon items-center justify-normal", children: [_jsx("span", { className: "text-sm", children: resourceFormatter(lf.property) }), _jsx(form.AppField, { name: `filters[${i}].operator`, children: (sf) => (_jsx(sf.TfSingleSelect, { items: FilterOperations[lf.type].map(op => ({ id: op, label: _jsx(_Fragment, { children: COMPARISON_OPERATOR_LABELS[op] }) })) })) }, lf.id), lf.type === 'bool' && _jsx(form.AppField, { name: `filters[${i}].value`, children: (sf) => (_jsx(sf.TfCheckbox, {})) }), lf.type === 'date' && _jsx(form.AppField, { name: `filters[${i}].value`, children: (_) => (_jsx(TfDatePicker, {})) }), lf.type === 'id' && _jsx(form.AppField, { name: `filters[${i}].value`, children: (sf) => (_jsx(sf.TfTextField, {})) }), lf.type === 'string' && _jsx(form.AppField, { name: `filters[${i}].value`, children: (sf) => (_jsx(sf.TfTextField, {})) }), lf.type === 'enum' && _jsx(form.AppField, { name: `filters[${i}].value`, children: (sf) => (_jsx(sf.TfMultiSelect, { items: filterConfig[lf.property].options })) }), lf.type === 'id-search' && _jsx(form.AppField, { name: `filters[${i}].value`, children: (_) => (_jsx(TfIdSearchInput, { multiple: true, accessor: "id", searchFn: filterConfig[lf.property].search })) }), _jsx(Button, { type: "button", variant: "destructive", size: "icon", onPress: () => field.removeValue(i), children: _jsx(Trash, {}) })] }, lf.id));
79
+ }), _jsxs("div", { className: "flex w-full items-center justify-end gap-2 not-first:mt-icon", children: [_jsx(EasyMenu, { label: "Add Filter", children: filterKeys.map(k => _jsx(MenuItem, { onAction: () => {
80
+ //@ts-ignore
81
+ const type = filterConfig[k].type;
82
+ const defaultValue = (t) => {
83
+ // TODO, ensure exhaustive???
84
+ switch (t) {
85
+ case 'bool':
86
+ return true;
87
+ case 'string':
88
+ return '';
89
+ case 'date':
90
+ return new Date().toISOString();
91
+ case 'id':
92
+ return '';
93
+ case 'id-search':
94
+ return [];
95
+ case 'enum':
96
+ return [];
97
+ }
98
+ };
99
+ const newFilter = {
100
+ id: `${k}-${Date.now()}`,
101
+ operator: FilterOperations[type][0],
102
+ //@ts-ignore
103
+ value: defaultValue(type),
104
+ type: type,
105
+ property: k,
106
+ };
107
+ field.pushValue(newFilter);
108
+ // TODO Focus to ref
109
+ }, className: "dropdown-item", children: resourceFormatter(k) }, k)) }), field.state.value.length > 0 ? (_jsx(Button, { type: "button", variant: "neutral", onPress: () => { field.setValue([]); }, children: "Reset filters" })) : null] })] });
110
+ } }) }) })] }) }));
111
+ }
112
+ ;
@@ -0,0 +1,73 @@
1
+ import type { components, operations } from "@cryptlex/web-api-types";
2
+ import { type ColumnDef, type TableState, type VisibilityState } from "@tanstack/react-table";
3
+ import React from "react";
4
+ /** Reserved name for actions column */
5
+ export declare const ACTIONS_COLUMN_ID = "tableActions";
6
+ export type Schemas = ApiSchema<keyof components['schemas']>;
7
+ export type OperationKeys = keyof operations;
8
+ type DataTableFactory<TData extends Schemas> = {
9
+ fetchFn: TableFetchFn<TData, OperationKeys>;
10
+ columns: ColumnDef<TData, any>[];
11
+ allowSelection?: boolean;
12
+ columnsToHideByDefault?: VisibilityState;
13
+ filterConfig: FiltersConfig<OperationKeys>;
14
+ };
15
+ type DataTableState = Pick<TableState, 'sorting' | 'rowSelection' | 'pagination'> & {};
16
+ /**
17
+ * Hook for handling all data-table state. Used in DataTableContext
18
+ */
19
+ export declare function useDataTableState<TData extends Schemas>({ columns, fetchFn, columnsToHideByDefault, allowSelection, filterConfig }: DataTableFactory<TData>): {
20
+ tableState: Pick<TableState, "sorting" | "rowSelection" | "pagination">;
21
+ updateTableState: (updates: Partial<DataTableState>) => void;
22
+ query: import("@tanstack/react-query").UseQueryResult<{
23
+ total: number;
24
+ data: TData[] | undefined;
25
+ }, Error>;
26
+ setSearchQuery: React.Dispatch<React.SetStateAction<string>>;
27
+ searchQuery: string;
28
+ tanTable: import("@tanstack/react-table").Table<any>;
29
+ mergedFilters: ApiFilters<keyof operations>;
30
+ filters: ApiFilters<keyof operations>[];
31
+ setFilters: React.Dispatch<React.SetStateAction<ApiFilters<keyof operations>[]>>;
32
+ filterConfig: Required<Record<never, import("lib/components/data-table/data-table-filter").FilterConfig>>;
33
+ };
34
+ export declare const DataTableContext: React.Context<{
35
+ tableState: Pick<TableState, "sorting" | "rowSelection" | "pagination">;
36
+ updateTableState: (updates: Partial<DataTableState>) => void;
37
+ query: import("@tanstack/react-query").UseQueryResult<{
38
+ total: number;
39
+ data: Schemas[] | undefined;
40
+ }, Error>;
41
+ setSearchQuery: React.Dispatch<React.SetStateAction<string>>;
42
+ searchQuery: string;
43
+ tanTable: import("@tanstack/react-table").Table<any>;
44
+ mergedFilters: ApiFilters<keyof operations>;
45
+ filters: ApiFilters<keyof operations>[];
46
+ setFilters: React.Dispatch<React.SetStateAction<ApiFilters<keyof operations>[]>>;
47
+ filterConfig: Required<Record<never, import("lib/components/data-table/data-table-filter").FilterConfig>>;
48
+ } | null>;
49
+ export declare const useDataTable: () => {
50
+ tableState: Pick<TableState, "sorting" | "rowSelection" | "pagination">;
51
+ updateTableState: (updates: Partial<DataTableState>) => void;
52
+ query: import("@tanstack/react-query").UseQueryResult<{
53
+ total: number;
54
+ data: Schemas[] | undefined;
55
+ }, Error>;
56
+ setSearchQuery: React.Dispatch<React.SetStateAction<string>>;
57
+ searchQuery: string;
58
+ tanTable: import("@tanstack/react-table").Table<any>;
59
+ mergedFilters: ApiFilters<keyof operations>;
60
+ filters: ApiFilters<keyof operations>[];
61
+ setFilters: React.Dispatch<React.SetStateAction<ApiFilters<keyof operations>[]>>;
62
+ filterConfig: Required<Record<never, import("lib/components/data-table/data-table-filter").FilterConfig>>;
63
+ };
64
+ export declare function DataTableProvider({ children, ...props }: {
65
+ children: React.ReactNode;
66
+ } & ReturnType<typeof useDataTableState>): import("react/jsx-runtime").JSX.Element;
67
+ export type DataTableProps = React.ComponentProps<'section'> & {
68
+ tableActions: TableActions;
69
+ };
70
+ export declare function DataTable({ tableActions, className, ...props }: DataTableProps): import("react/jsx-runtime").JSX.Element;
71
+ import { type FiltersConfig } from "lib/components/data-table/data-table-filter";
72
+ import { type ApiFilters, type ApiSchema, type TableActions, type TableFetchFn } from "lib/components/data-table/table-commons";
73
+ export {};
@@ -0,0 +1,265 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { keepPreviousData, useQuery } from "@tanstack/react-query";
4
+ import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
5
+ import { ArrowDownNarrowWide, ArrowDownWideNarrow, ArrowUpDown, Columns3, GripVertical, Info } from "lucide-react";
6
+ import { createContext, useContext, useEffect, useId, useMemo, useState } from "react";
7
+ /** Reserved name for actions column */
8
+ export const ACTIONS_COLUMN_ID = "tableActions";
9
+ /**
10
+ * Hook for handling all data-table state. Used in DataTableContext
11
+ */
12
+ export function useDataTableState({ columns, fetchFn, columnsToHideByDefault = {}, allowSelection = false, filterConfig }) {
13
+ const id = useId();
14
+ // TODO: Would it be better for this state to be more granular?
15
+ const [tableState, _setTableState] = useState({
16
+ /** TODO Reflect in URL */
17
+ pagination: { pageIndex: 0, pageSize: 20 }, // Pagination state
18
+ sorting: [], // Sorting state
19
+ /** Ephemeral */
20
+ rowSelection: {}, // Row selection state
21
+ });
22
+ /** TODO Reflect in URL */
23
+ const [searchQuery, setSearchQuery] = useState('');
24
+ /** TODO Store on browser as preference */
25
+ const [columnOrder, setColumnOrder] = useState([]);
26
+ // TODO Store on browser
27
+ const [columnVisibility, setColumnVisibility] = useState({
28
+ id: false,
29
+ updatedAt: false,
30
+ ...columnsToHideByDefault,
31
+ });
32
+ const [filters, setFilters] = useState([]);
33
+ const mergedFilters = useMemo(() => {
34
+ return filters.reduce((acc, current) => {
35
+ return merge(acc, current);
36
+ }, {});
37
+ }, [filters]);
38
+ // Update table state with new values
39
+ const updateTableState = (updates) => {
40
+ _setTableState((prev) => ({ ...prev, ...updates }));
41
+ };
42
+ const { sorting, rowSelection, pagination, } = tableState;
43
+ const query = useQuery({
44
+ queryKey: [id, pagination, sorting, searchQuery],
45
+ queryFn: () => fetchFn(pagination, sorting, searchQuery, mergedFilters),
46
+ placeholderData: keepPreviousData, // Keep previous data while loading new data
47
+ retry: 0,
48
+ refetchOnWindowFocus: false,
49
+ });
50
+ useEffect(() => {
51
+ // TODO, store in localStorage
52
+ }, [columnVisibility]);
53
+ const columnHelper = useMemo(() => createColumnHelper(), []);
54
+ const metadataColumns = useMemo(() => {
55
+ const data = query.data?.data;
56
+ if (!data?.length)
57
+ return [];
58
+ const rowHasMetadata = (row) => row != null &&
59
+ typeof row === 'object' &&
60
+ 'metadata' in row &&
61
+ Array.isArray((row).metadata);
62
+ const rowsWithMeta = data.filter(rowHasMetadata);
63
+ if (rowsWithMeta.length === 0)
64
+ return [];
65
+ const keys = Array.from(new Set(rowsWithMeta.flatMap(r => r.metadata?.map(m => m.key) ?? [])));
66
+ return keys.map(key => columnHelper.accessor((row) => {
67
+ if (rowHasMetadata(row)) {
68
+ return row?.metadata?.find(m => m.key === key)?.value ?? '';
69
+ }
70
+ return '';
71
+ }, {
72
+ id: key,
73
+ header: key, // tooltip header
74
+ enableSorting: false,
75
+ cell: (info) => {
76
+ const value = info.getValue();
77
+ // Handle null/undefined values
78
+ if (value === null || value === undefined)
79
+ return "";
80
+ // For primitive types, return the string representation
81
+ return String(value);
82
+ },
83
+ }));
84
+ }, [query.data?.data]);
85
+ /**
86
+ * ID,createdAt and updatedAt will be added by default for all tables
87
+ * If selection is allowed, checkbox will be added
88
+ * If the dto has metadata, dynamics columns for all the metadata key-value will be added(particular for a view)
89
+ * If there are actions for the table, they will be placed fixed at the right side of table.
90
+ */
91
+ const cols = [
92
+ ...(allowSelection ? TABLE_CHECK_BOX_COLUMN : []),
93
+ ...TABLE_ID_COLUMN,
94
+ ...columns.filter((col) => col.id !== ACTIONS_COLUMN_ID),
95
+ ...(metadataColumns.length ? metadataColumns : []),
96
+ ...TABLE_DEFAULT_DATE_COLUMNS,
97
+ // Actions column
98
+ ...columns.filter((col) => col.id === ACTIONS_COLUMN_ID),
99
+ ];
100
+ // Type-guard for updater
101
+ function isUpdaterFunction(updater) {
102
+ return typeof updater === "function";
103
+ }
104
+ // Utility function to resolve updater
105
+ function resolveUpdater(updater, currentValue) {
106
+ if (isUpdaterFunction(updater)) {
107
+ return updater(currentValue);
108
+ }
109
+ return updater;
110
+ }
111
+ // Use react-table's hook to create the table instance
112
+ const tanTable = useReactTable({
113
+ data: query.data?.data ?? [],
114
+ columns: cols,
115
+ getCoreRowModel: getCoreRowModel(),
116
+ rowCount: query.data?.total,
117
+ manualPagination: true, // Handle pagination manually since pagination is done server side for data tables
118
+ onPaginationChange: (updater) => {
119
+ updateTableState({ pagination: resolveUpdater(updater, pagination) });
120
+ },
121
+ manualSorting: true, // Handle sorting manually since sorting is done server side for data tables
122
+ onSortingChange: (updater) => {
123
+ updateTableState({ sorting: [...resolveUpdater(updater, sorting)], rowSelection: {} }); // Reset selection when sorting.
124
+ },
125
+ manualFiltering: true, // Handle filtering manually since filtering is done server side for data tables
126
+ onColumnVisibilityChange: (updater) => {
127
+ setColumnVisibility(resolveUpdater(updater, columnVisibility));
128
+ },
129
+ onRowSelectionChange: (updater) => {
130
+ updateTableState({ rowSelection: resolveUpdater(updater, rowSelection) });
131
+ },
132
+ onColumnOrderChange: (updater) => {
133
+ setColumnOrder(resolveUpdater(updater, columnOrder));
134
+ },
135
+ state: {
136
+ sorting: sorting,
137
+ columnVisibility: columnVisibility,
138
+ pagination: pagination,
139
+ rowSelection: rowSelection,
140
+ columnOrder: columnOrder
141
+ },
142
+ meta: {
143
+ refetch: query.refetch,
144
+ },
145
+ });
146
+ // By default, ColumnDef does not give guarantees of column.id existing. Once useReactTable is called, all columns are assigned IDs.
147
+ // This populates the columnIds in the columnOrder state
148
+ // TODO, add localStorage access layer for this.
149
+ useEffect(() => {
150
+ setColumnOrder([...tanTable.getAllLeafColumns().map(c => c.id)]);
151
+ }, []);
152
+ return { tableState, updateTableState, query, setSearchQuery, searchQuery, tanTable, mergedFilters, filters, setFilters, filterConfig };
153
+ }
154
+ export const DataTableContext = createContext(null);
155
+ export const useDataTable = () => {
156
+ const ctx = useContext(DataTableContext);
157
+ if (!ctx) {
158
+ throw Error("DataTable should be used within DataTableProvider.");
159
+ }
160
+ return ctx;
161
+ };
162
+ export function DataTableProvider({ children, ...props }) {
163
+ return (_jsx(DataTableContext.Provider, { value: props, children: children }));
164
+ }
165
+ export function DataTable({ tableActions, className, ...props
166
+ // filterableFields,
167
+ }) {
168
+ // State for managing table data and filters
169
+ const { query, tanTable } = useDataTable();
170
+ return (_jsx(_Fragment, { children: _jsxs("section", { ...props, className: cn("flex flex-col bg-card", className), children: [_jsx(Actions, { tableActions: tableActions }), _jsxs("div", { className: "w-full overflow-auto border-x grow min-h-table relative", tabIndex: 0, children: [query.isLoading && (_jsx(TableOverlay, { className: "cursor-wait", children: _jsx(Loader, {}) })), !query.isLoading && tanTable.getRowModel().rows.length === 0 && (
171
+ // Empty table
172
+ _jsx(TableOverlay, { className: "cursor-not-allowed", children: !query.isFetching &&
173
+ (query.isError ? (_jsxs("span", { className: "flex gap-3 justify-center items-center", children: [_jsx(Info, {}), _jsx("span", { children: "You don't have the required permissions. Please contact your admin." })] })) : !query.data?.data ? (_jsx(_Fragment, { children: "No results found." })) : (_jsx(_Fragment, { children: "Unknown error. Please contact customer support." }))) })), !query.isLoading && tanTable.getRowModel().rows.length !== 0 &&
174
+ _jsx(TableContent, { className: "size-full" })] }), _jsxs("div", { className: "flex w-full justify-between border gap-icon p-icon overflow-x-auto", children: [_jsxs("div", { className: "flex gap-icon", children: [_jsx(ColumnPicker, {}), _jsx(PageSize, {})] }), _jsx(Paginator, {})] })] }) }));
175
+ }
176
+ /** Table overlay to be shown for loaders or other messages */
177
+ function TableOverlay({ children, className, }) {
178
+ return (_jsxs(_Fragment, { children: [_jsx("span", { className: cn(className, "absolute top-0 bg-card z-20 size-full text-sm flex items-center justify-center"), children: children }), _jsx("span", { className: "relative h-full w-0 block" })] }));
179
+ }
180
+ import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
181
+ function ColumnPicker() {
182
+ const { tanTable } = useDataTable();
183
+ const [activeId, setActiveId] = useState(null);
184
+ const resourceFormatter = useResourceFormatter();
185
+ const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
186
+ coordinateGetter: sortableKeyboardCoordinates,
187
+ }));
188
+ return (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragStart: (event) => {
189
+ const { active } = event;
190
+ setActiveId(active.id.toString());
191
+ }, onDragEnd: (event) => {
192
+ const { active, over } = event;
193
+ if (over && active.id !== over.id) {
194
+ const columnOrder = tanTable.getState().columnOrder;
195
+ const oldIndex = columnOrder.indexOf(active.id.toString());
196
+ const newIndex = columnOrder.indexOf(over.id.toString());
197
+ tanTable.setColumnOrder([...arrayMove(columnOrder, oldIndex, newIndex)]);
198
+ }
199
+ setActiveId(null);
200
+ }, children: _jsxs(SortableContext, { items: tanTable.getState().columnOrder, strategy: verticalListSortingStrategy, children: [_jsxs(EasyMenu, { label: _jsxs(_Fragment, { children: [_jsx(Columns3, {}), "Columns"] }), selectionMode: "multiple", items: tanTable.getAllFlatColumns(), selectedKeys: tanTable.getIsAllColumnsVisible() ? 'all' : tanTable.getVisibleFlatColumns().map(c => c.id), children: [_jsx(MenuItem, { onAction: () => tanTable.toggleAllColumnsVisible(), className: 'italic', children: "(select all)" }), tanTable.getState().columnOrder.map(colId => {
201
+ const col = tanTable.getAllFlatColumns().find(c => c.id === colId);
202
+ if (!col)
203
+ return null;
204
+ return _jsx(SortableItem, { column: col }, col.id);
205
+ })] }), _jsx(DragOverlay, { children: activeId ? _jsx("div", { className: "dropdown-item opacity-70 border-2 border-primary", children: resourceFormatter(activeId) }) : null })] }) }));
206
+ function SortableItem({ column }) {
207
+ const { attributes, listeners, setNodeRef, transform, transition, } = useSortable({ id: column.id });
208
+ const style = {
209
+ transform: CSS.Transform.toString(transform),
210
+ transition,
211
+ zIndex: '999'
212
+ };
213
+ return _jsxs(MenuItem, { ref: setNodeRef, style: style, ...attributes, id: column.id, onAction: () => column.toggleVisibility(), isDisabled: !column.getCanHide(), className: "flex items-center", children: [_jsx(GripVertical, { ...listeners, className: "size-icon cursor-grab" }), resourceFormatter(column.id), column.getIsSorted() && _jsx(SortIcon, { className: "size-icon", direction: column.getIsSorted() })] });
214
+ }
215
+ }
216
+ import { ChevronFirst, ChevronLast, ChevronLeft, ChevronRight, } from "lucide-react";
217
+ function Paginator() {
218
+ const { tanTable, query } = useDataTable();
219
+ const rowCount = query.data?.total ?? 0;
220
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "whitespace-nowrap caption text-muted", children: `${tanTable.getState().pagination.pageIndex * tanTable.getState().pagination.pageSize + 1} - ${Math.min((tanTable.getState().pagination.pageIndex + 1) *
221
+ tanTable.getState().pagination.pageSize, rowCount)} of ${rowCount?.toLocaleString()}` }), _jsx(Button, { onPress: () => tanTable.firstPage(), isDisabled: !tanTable.getCanPreviousPage(), variant: "neutral", size: "icon", children: _jsx(ChevronFirst, {}) }), _jsx(Button, { onPress: () => tanTable.previousPage(), isDisabled: !tanTable.getCanPreviousPage(), variant: "neutral", size: "icon", children: _jsx(ChevronLeft, {}) }), _jsx(Button, { onPress: () => tanTable.nextPage(), isDisabled: !tanTable.getCanNextPage(), variant: "neutral", size: "icon", children: _jsx(ChevronRight, {}) }), _jsx(Button, { onClick: () => tanTable.lastPage(), isDisabled: !tanTable.getCanNextPage(), variant: "neutral", size: "icon", children: _jsx(ChevronLast, {}) })] }));
222
+ }
223
+ function PageSize() {
224
+ const { tanTable } = useDataTable();
225
+ const PAGE_SIZES = [10, 20, 30, 40, 50];
226
+ return (_jsx(EasyMenu, { label: tanTable.getState().pagination.pageSize.toString(), selectionMode: "single", selectedKeys: [tanTable.getState().pagination.pageSize.toString()], items: PAGE_SIZES.map(s => ({ id: s.toString(), value: s })), children: (items) => _jsx(MenuItem, { onAction: () => tanTable.setPageSize(items.value), children: items.value }) }));
227
+ }
228
+ import { RotateCw } from "lucide-react";
229
+ function Actions({ tableActions }) {
230
+ const { query, tanTable, searchQuery, setSearchQuery } = useDataTable();
231
+ return (_jsxs("section", { className: "flex bg-card justify-between my-0 p-icon border gap-icon overflow-auto", children: [_jsxs("div", { className: "flex gap-icon", children: [_jsx(Button, { isPending: query.isFetching, onClick: () => query.refetch(), variant: "neutral", size: "icon", children: _jsx(RotateCw, {}) }), tableActions
232
+ .filter(ta => ta.bulk === tanTable.getSelectedRowModel().rows.length > 0)
233
+ .map((ta, i) => {
234
+ const Icon = ta.icon;
235
+ return (_jsx(Button, { type: "button", isDisabled: query.isFetching, className: "animate-in fade-in slide-in-from-left-15 duration-300 transition-transform", onPress: (e) => { ta.onClick(e, tanTable); }, size: 'icon', children: _jsx(Icon, {}) }, `${i}-${ta.bulk}`));
236
+ })] }), _jsxs("div", { className: "flex gap-icon", children: [_jsx(DataTableFilter, {}), (_jsx(SearchField, { value: searchQuery, onChange: setSearchQuery }))] })] }));
237
+ }
238
+ import { closestCenter, DndContext, DragOverlay, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
239
+ import { CSS } from "@dnd-kit/utilities";
240
+ import { flexRender } from "@tanstack/react-table";
241
+ import { DataTableFilter } from "lib/components/data-table/data-table-filter";
242
+ import { TABLE_CHECK_BOX_COLUMN, TABLE_DEFAULT_DATE_COLUMNS, TABLE_ID_COLUMN } from "lib/components/data-table/table-commons";
243
+ import { SearchField } from "lib/components/inputs/searchfield";
244
+ import { Button } from "lib/components/ui/button";
245
+ import { Loader } from "lib/components/ui/loader";
246
+ import { EasyMenu, MenuItem } from "lib/components/ui/menu";
247
+ import { TableBody, TableCell, Table as TableComponent, TableHead, TableHeader, TableRow, } from "lib/components/ui/table";
248
+ import { cn } from "lib/utils/primitives";
249
+ import { useResourceFormatter } from "lib/utils/resource-names";
250
+ import { merge } from "lodash-es";
251
+ function SortIcon({ direction, ...props }) {
252
+ if (direction === 'asc')
253
+ return _jsx(ArrowDownNarrowWide, { ...props });
254
+ else if (direction === 'desc')
255
+ return _jsx(ArrowDownWideNarrow, { ...props });
256
+ else
257
+ return _jsx(ArrowUpDown, { ...props });
258
+ }
259
+ // TODO, automate checking valid HTML
260
+ function TableContent({ className }) {
261
+ const { tanTable } = useDataTable();
262
+ const tableCellStyle = (isSticky, className) => cn("animate-in fade-in slide-in-from-top-10", "px-icon py-2 text-left text-sm font-medium whitespace-nowrap", isSticky && "bg-card sticky right-0 z-50 text-center", className);
263
+ return (_jsxs(TableComponent, { className: cn(className), children: [_jsx(TableHeader, { className: "sticky top-0 z-10", children: tanTable.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: cn("h-input"), children: headerGroup.headers.map((header) => (_jsxs(TableHead, { className: tableCellStyle(false, "bg-card"), children: [!header.column.getCanSort() && !header.isPlaceholder
264
+ && _jsx(_Fragment, { children: flexRender(header.column.columnDef.header, header.getContext()) }), header.column.getCanSort() && (_jsxs(Button, { variant: "ghost", className: "w-full !justify-start !px-1.5", onPress: header.column.getToggleSortingHandler(), children: [flexRender(header.column.columnDef.header, header.getContext()), _jsx(SortIcon, { direction: header.column.getIsSorted() })] }))] }, header.id))) }, headerGroup.id))) }), _jsx(TableBody, { className: "flex-1 overflow-y-auto relative", children: tanTable.getRowModel().rows.map((row) => (_jsx(TableRow, { className: cn("h-input transition-colors data-[selected=true]:bg-primary/10 hover:bg-muted-foreground/20"), "data-selected": row.getIsSelected(), children: row.getVisibleCells().map((cell) => (_jsx(TableCell, { className: tableCellStyle(cell.column.id === ACTIONS_COLUMN_ID), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id))) })] }));
265
+ }
@@ -0,0 +1,56 @@
1
+ import type { components, operations, paths } from "@cryptlex/web-api-types";
2
+ import type { PaginationState, SortingState, Table } from "@tanstack/react-table";
3
+ import type { LucideIcon } from "lucide-react";
4
+ import type createClient from "openapi-fetch";
5
+ import type { PressEvent } from "react-aria-components";
6
+ export declare const TABLE_CHECK_BOX_COLUMN: import("@tanstack/react-table").AccessorKeyColumnDef<any, any>[];
7
+ export declare const TABLE_ID_COLUMN: import("@tanstack/react-table").AccessorKeyColumnDef<any, any>[];
8
+ export declare const TABLE_DEFAULT_DATE_COLUMNS: import("@tanstack/react-table").AccessorKeyColumnDef<any, any>[];
9
+ export declare function formatDate(date: string | null | undefined): string | null;
10
+ /**
11
+ * Format multiple license parameters (expired, suspended, revoked) into a single status
12
+ */
13
+ export declare function getLicenseStatus(license: any): string;
14
+ export declare function getValidityDisplay(validity: number | undefined): string;
15
+ export declare function secondsToDuration(seconds: number): string;
16
+ export declare function getValueFromData(data: any, accessor: string | number | symbol): any;
17
+ export declare const ALL_OS: {
18
+ [key: string]: string;
19
+ };
20
+ export type VisibilityState<T> = {
21
+ [K in keyof T]?: boolean;
22
+ };
23
+ export type TableActions = ({
24
+ onClick: (e: PressEvent, t: Table<any>) => void;
25
+ bulk: boolean;
26
+ icon: LucideIcon;
27
+ tooltip?: string;
28
+ })[];
29
+ export type TableFetchFn<TData, TOperation extends keyof operations> = (p: PaginationState, s: SortingState, q: string, f: ApiFilters<TOperation>) => Promise<{
30
+ total: number;
31
+ data: TData[] | undefined;
32
+ }>;
33
+ /*** Type for hide some of the columns based on the dto of the particular page
34
+ ** `id`, `updatedAt` are by default hidden
35
+ */
36
+ export type DefaultVisibilityState<T> = {
37
+ [K in keyof T]?: boolean;
38
+ };
39
+ type Client = ReturnType<typeof createClient<paths>>;
40
+ type GetPaths = {
41
+ [P in keyof paths]: paths[P] extends {
42
+ get: any;
43
+ } ? P : never;
44
+ }[keyof paths];
45
+ export type ApiSchema<T extends keyof components['schemas']> = components['schemas'][T];
46
+ export type ApiQuery<T extends keyof operations> = NonNullable<operations[T]['parameters']['query']>;
47
+ export type ApiGetAllParameters = {
48
+ page: number;
49
+ limit: number;
50
+ search?: string;
51
+ sort?: string;
52
+ };
53
+ export type ApiFilter<T extends keyof operations> = Omit<ApiQuery<T>, 'page' | 'limit' | 'sort' | 'search'>;
54
+ export type ApiFilters<T extends keyof operations> = NonNullable<Omit<ApiQuery<T>, 'page' | 'limit' | 'sort' | 'search'>>;
55
+ export declare function createTableFetchFn<Return, Operation extends keyof operations>(ctxclient: Client, path: GetPaths): TableFetchFn<Return, Operation>;
56
+ export {};