@hexclave/dashboard-ui-components 1.0.2 → 1.0.5

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 (52) hide show
  1. package/dist/components/button.d.ts +4 -4
  2. package/dist/components/pill-toggle.js +2 -2
  3. package/dist/components/pill-toggle.js.map +1 -1
  4. package/dist/dashboard-ui-components.global.js +7447 -9242
  5. package/dist/dashboard-ui-components.global.js.map +4 -4
  6. package/dist/esm/components/button.d.ts +4 -4
  7. package/dist/esm/components/pill-toggle.js +3 -3
  8. package/dist/esm/components/pill-toggle.js.map +1 -1
  9. package/package.json +4 -3
  10. package/src/components/alert.tsx +120 -0
  11. package/src/components/analytics-chart/analytics-chart-pie.tsx +369 -0
  12. package/src/components/analytics-chart/analytics-chart.tsx +1585 -0
  13. package/src/components/analytics-chart/default-analytics-chart-tooltip.tsx +265 -0
  14. package/src/components/analytics-chart/format.ts +101 -0
  15. package/src/components/analytics-chart/index.ts +75 -0
  16. package/src/components/analytics-chart/palette.ts +68 -0
  17. package/src/components/analytics-chart/render-data-series.tsx +169 -0
  18. package/src/components/analytics-chart/state.ts +165 -0
  19. package/src/components/analytics-chart/strings.ts +72 -0
  20. package/src/components/analytics-chart/types.ts +220 -0
  21. package/src/components/badge.tsx +108 -0
  22. package/src/components/button.tsx +104 -0
  23. package/src/components/card.tsx +263 -0
  24. package/src/components/chart-card.tsx +101 -0
  25. package/src/components/chart-container.tsx +155 -0
  26. package/src/components/chart-legend.tsx +65 -0
  27. package/src/components/chart-theme.tsx +52 -0
  28. package/src/components/chart-tooltip.tsx +165 -0
  29. package/src/components/cursor-blast-effect.tsx +334 -0
  30. package/src/components/data-grid/data-grid-sizing.ts +51 -0
  31. package/src/components/data-grid/data-grid-toolbar.tsx +373 -0
  32. package/src/components/data-grid/data-grid.test.tsx +366 -0
  33. package/src/components/data-grid/data-grid.tsx +1220 -0
  34. package/src/components/data-grid/index.ts +64 -0
  35. package/src/components/data-grid/state.ts +235 -0
  36. package/src/components/data-grid/strings.ts +45 -0
  37. package/src/components/data-grid/types.ts +401 -0
  38. package/src/components/data-grid/use-data-source.ts +413 -0
  39. package/src/components/data-grid/use-url-state.test.tsx +88 -0
  40. package/src/components/data-grid/use-url-state.ts +298 -0
  41. package/src/components/dialog.tsx +207 -0
  42. package/src/components/edit-mode.tsx +17 -0
  43. package/src/components/empty-state.tsx +64 -0
  44. package/src/components/input.tsx +85 -0
  45. package/src/components/metric-card.tsx +142 -0
  46. package/src/components/pill-toggle.tsx +159 -0
  47. package/src/components/progress-bar.tsx +82 -0
  48. package/src/components/separator.tsx +36 -0
  49. package/src/components/skeleton.tsx +30 -0
  50. package/src/components/table.tsx +113 -0
  51. package/src/components/tabs.tsx +214 -0
  52. package/src/index.ts +69 -0
@@ -0,0 +1,64 @@
1
+ export { DataGrid, isDataGridInteractiveRowClickTarget } from "./data-grid";
2
+ export { DataGridToolbar } from "./data-grid-toolbar";
3
+
4
+ // Sizing helpers — re-exported so external consumers that depended on the
5
+ // previous API can still measure column widths consistently with the grid.
6
+ export {
7
+ getEffectiveMinWidth,
8
+ getEffectiveMaxWidth,
9
+ clampColumnWidth,
10
+ DEFAULT_COL_WIDTH,
11
+ DEFAULT_MAX_COL_WIDTH,
12
+ } from "./data-grid-sizing";
13
+
14
+ export { useDataSource } from "./use-data-source";
15
+ export type { UseDataSourceResult } from "./use-data-source";
16
+
17
+ export { useDataGridUrlState } from "./use-url-state";
18
+
19
+ export {
20
+ createDefaultDataGridState,
21
+ resolveColumnValue,
22
+ buildRowComparator,
23
+ paginateRows,
24
+ exportToCsv,
25
+ defaultParseDate,
26
+ defaultFormatRelative,
27
+ defaultFormatAbsolute,
28
+ formatGridDate,
29
+ defaultMatchRow,
30
+ applyQuickSearch,
31
+ } from "./state";
32
+
33
+ export { DATA_GRID_DEFAULT_STRINGS, resolveDataGridStrings } from "./strings";
34
+
35
+ export type {
36
+ RowId,
37
+ DataGridColumnType,
38
+ DataGridColumnAlign,
39
+ DataGridColumnPin,
40
+ DataGridDateDisplay,
41
+ DataGridDateFormat,
42
+ DataGridCellContext,
43
+ DataGridHeaderContext,
44
+ DataGridColumnDef,
45
+ DataGridSelectOption,
46
+ DataGridSortItem,
47
+ DataGridSortModel,
48
+ DataGridSelectionMode,
49
+ DataGridSelectionModel,
50
+ DataGridColumnVisibility,
51
+ DataGridColumnPinning,
52
+ DataGridPaginationMode,
53
+ DataGridDataPaginationMode,
54
+ DataGridPaginationModel,
55
+ DataGridState,
56
+ DataGridFetchParams,
57
+ DataGridFetchResult,
58
+ DataGridDataSource,
59
+ DataGridCallbacks,
60
+ DataGridProps,
61
+ DataGridToolbarContext,
62
+ DataGridFooterContext,
63
+ DataGridStrings,
64
+ } from "./types";
@@ -0,0 +1,235 @@
1
+ import { stringCompare } from "@hexclave/shared/dist/utils/strings";
2
+ import { clampColumnWidth, DEFAULT_COL_WIDTH } from "./data-grid-sizing";
3
+ import type {
4
+ DataGridColumnDef,
5
+ DataGridDateDisplay,
6
+ DataGridDateFormat,
7
+ DataGridPaginationModel,
8
+ DataGridSortModel,
9
+ DataGridState,
10
+ } from "./types";
11
+
12
+ /**
13
+ * Build the initial `DataGridState` for a set of columns. Pass this as the
14
+ * lazy initializer to `useState` — never hand-assemble the state object.
15
+ *
16
+ * ```tsx
17
+ * const [gridState, setGridState] = React.useState(() =>
18
+ * createDefaultDataGridState(columns)
19
+ * );
20
+ * ```
21
+ */
22
+ export function createDefaultDataGridState(
23
+ columns: readonly DataGridColumnDef<any>[],
24
+ ): DataGridState {
25
+ const columnWidths: Record<string, number> = {};
26
+ const columnOrder: string[] = [];
27
+
28
+ for (const col of columns) {
29
+ columnWidths[col.id] = clampColumnWidth(col, col.width ?? DEFAULT_COL_WIDTH);
30
+ columnOrder.push(col.id);
31
+ }
32
+
33
+ return {
34
+ sorting: [],
35
+ columnVisibility: {},
36
+ columnWidths,
37
+ columnPinning: { left: [], right: [] },
38
+ columnOrder,
39
+ pagination: { pageIndex: 0, pageSize: 50 },
40
+ selection: { selectedIds: new Set(), anchorId: null },
41
+ dateDisplay: "relative",
42
+ quickSearch: "",
43
+ };
44
+ }
45
+
46
+ // ─── Column value resolution ─────────────────────────────────────────
47
+
48
+ export function resolveColumnValue<TRow>(
49
+ col: DataGridColumnDef<TRow>,
50
+ row: TRow,
51
+ ): unknown {
52
+ if (typeof col.accessor === "function") return col.accessor(row);
53
+ const key = (col.accessor ?? col.id) as keyof TRow;
54
+ return row[key];
55
+ }
56
+
57
+ // ─── Default sort comparator (used by client-mode useDataSource) ────
58
+
59
+ function defaultComparator(a: unknown, b: unknown): number {
60
+ if (a == null && b == null) return 0;
61
+ if (a == null) return -1;
62
+ if (b == null) return 1;
63
+ if (typeof a === "number" && typeof b === "number") return a - b;
64
+ if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
65
+ return stringCompare(String(a), String(b));
66
+ }
67
+
68
+ export function buildRowComparator<TRow>(
69
+ sortModel: DataGridSortModel,
70
+ columns: readonly DataGridColumnDef<TRow>[],
71
+ ): ((a: TRow, b: TRow) => number) | null {
72
+ if (sortModel.length === 0) return null;
73
+ const colMap = new Map(columns.map((c) => [c.id, c]));
74
+ return (a, b) => {
75
+ for (const { columnId, direction } of sortModel) {
76
+ const col = colMap.get(columnId);
77
+ if (!col) continue;
78
+ const va = resolveColumnValue(col, a);
79
+ const vb = resolveColumnValue(col, b);
80
+ const cmp = col.sortComparator ? col.sortComparator(va, vb) : defaultComparator(va, vb);
81
+ if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
82
+ }
83
+ return 0;
84
+ };
85
+ }
86
+
87
+ // ─── Pagination ──────────────────────────────────────────────────────
88
+
89
+ export function paginateRows<TRow>(
90
+ rows: readonly TRow[],
91
+ pagination: DataGridPaginationModel,
92
+ ): TRow[] {
93
+ const start = pagination.pageIndex * pagination.pageSize;
94
+ return rows.slice(start, start + pagination.pageSize) as TRow[];
95
+ }
96
+
97
+ // ─── Quick search ────────────────────────────────────────────────────
98
+
99
+ /** Default row matcher: case-insensitive substring across every column. */
100
+ export function defaultMatchRow<TRow>(
101
+ row: TRow,
102
+ query: string,
103
+ columns: readonly DataGridColumnDef<TRow>[],
104
+ ): boolean {
105
+ for (const col of columns) {
106
+ const v = resolveColumnValue(col, row);
107
+ if (v == null) continue;
108
+ if (String(v).toLowerCase().includes(query)) return true;
109
+ }
110
+ return false;
111
+ }
112
+
113
+ export function applyQuickSearch<TRow>(
114
+ rows: readonly TRow[],
115
+ query: string,
116
+ columns: readonly DataGridColumnDef<TRow>[],
117
+ matchRow: (
118
+ row: TRow,
119
+ query: string,
120
+ columns: readonly DataGridColumnDef<TRow>[],
121
+ ) => boolean = defaultMatchRow,
122
+ ): readonly TRow[] {
123
+ const trimmed = query.trim().toLowerCase();
124
+ if (!trimmed) return rows;
125
+ return rows.filter((r) => matchRow(r, trimmed, columns));
126
+ }
127
+
128
+ // ─── Date helpers ────────────────────────────────────────────────────
129
+
130
+ export function defaultParseDate(value: unknown): Date | null {
131
+ if (value == null) return null;
132
+ if (value instanceof Date) return isNaN(value.getTime()) ? null : value;
133
+ if (typeof value === "number" || typeof value === "string") {
134
+ const d = new Date(value);
135
+ return isNaN(d.getTime()) ? null : d;
136
+ }
137
+ return null;
138
+ }
139
+
140
+ const DIVISIONS: Array<{ amount: number; unit: Intl.RelativeTimeFormatUnit }> = [
141
+ { amount: 60, unit: "second" },
142
+ { amount: 60, unit: "minute" },
143
+ { amount: 24, unit: "hour" },
144
+ { amount: 7, unit: "day" },
145
+ { amount: 4.34524, unit: "week" },
146
+ { amount: 12, unit: "month" },
147
+ { amount: Number.POSITIVE_INFINITY, unit: "year" },
148
+ ];
149
+
150
+ const relativeTimeFormatterCache = new Map<string, Intl.RelativeTimeFormat>();
151
+ function getRelativeTimeFormatter(locale?: string): Intl.RelativeTimeFormat {
152
+ const key = locale ?? "__default__";
153
+ let cached = relativeTimeFormatterCache.get(key);
154
+ if (cached == null) {
155
+ cached = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
156
+ relativeTimeFormatterCache.set(key, cached);
157
+ }
158
+ return cached;
159
+ }
160
+
161
+ export function defaultFormatRelative(date: Date): string {
162
+ const rtf = getRelativeTimeFormatter();
163
+ // Wall-clock comparison to "now" (e.g. "5 minutes ago"). performance.now()
164
+ // would be wrong here — it's a monotonic timer, not a wall-clock instant.
165
+ let duration = (date.getTime() - Date.now()) / 1000;
166
+ for (const div of DIVISIONS) {
167
+ if (Math.abs(duration) < div.amount) return rtf.format(Math.round(duration), div.unit);
168
+ duration /= div.amount;
169
+ }
170
+ return rtf.format(Math.round(duration), "year");
171
+ }
172
+
173
+ export function defaultFormatAbsolute(date: Date): string {
174
+ return date.toLocaleString();
175
+ }
176
+
177
+ export function formatGridDate(
178
+ value: unknown,
179
+ mode: DataGridDateDisplay,
180
+ opts?: {
181
+ parseValue?: (value: unknown) => Date | null;
182
+ dateFormat?: DataGridDateFormat;
183
+ },
184
+ ): { display: string | null; tooltip: string | null } {
185
+ const parse = opts?.parseValue ?? defaultParseDate;
186
+ const date = parse(value);
187
+ if (!date) return { display: null, tooltip: null };
188
+ const relative = opts?.dateFormat?.relative ?? defaultFormatRelative;
189
+ const absolute = opts?.dateFormat?.absolute ?? defaultFormatAbsolute;
190
+ const tooltip = absolute(date);
191
+ const display = mode === "relative" ? relative(date) : tooltip;
192
+ return { display, tooltip };
193
+ }
194
+
195
+ // ─── CSV Export ──────────────────────────────────────────────────────
196
+
197
+ export function exportToCsv<TRow>(
198
+ rows: readonly TRow[],
199
+ columns: readonly DataGridColumnDef<TRow>[],
200
+ filename: string,
201
+ ): void {
202
+ const header = columns.map((col) =>
203
+ typeof col.header === "string" ? col.header : col.id,
204
+ );
205
+ const csvRows = rows.map((row) =>
206
+ columns.map((col) => {
207
+ const val = resolveColumnValue(col, row);
208
+ const formatted = col.formatValue
209
+ ? String((col.formatValue(val, row) as string | null | undefined) ?? "")
210
+ : String(val ?? "");
211
+ if (formatted.includes(",") || formatted.includes('"') || formatted.includes("\n")) {
212
+ return `"${formatted.replace(/"/g, '""')}"`;
213
+ }
214
+ return formatted;
215
+ }),
216
+ );
217
+ // UTF-8 BOM so Excel opens the CSV as UTF-8.
218
+ const csvContent = "\uFEFF" + [
219
+ header.join(","),
220
+ ...csvRows.map((row) => row.join(",")),
221
+ ].join("\n");
222
+
223
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
224
+ const url = URL.createObjectURL(blob);
225
+ const link = document.createElement("a");
226
+ link.href = url;
227
+ link.download = `${filename}.csv`;
228
+ document.body.appendChild(link);
229
+ try {
230
+ link.click();
231
+ } finally {
232
+ link.remove();
233
+ URL.revokeObjectURL(url);
234
+ }
235
+ }
@@ -0,0 +1,45 @@
1
+ import type { DataGridStrings } from "./types";
2
+
3
+ export const DATA_GRID_DEFAULT_STRINGS: DataGridStrings = {
4
+ // toolbar
5
+ searchPlaceholder: "Search\u2026",
6
+ columns: "Columns",
7
+ export: "Export",
8
+ density: "Density",
9
+ // column manager
10
+ showAll: "Show all",
11
+ hideAll: "Hide all",
12
+ resetColumns: "Reset",
13
+ // date display
14
+ dateFormat: "Date format",
15
+ dateFormatRelative: "Relative",
16
+ dateFormatAbsolute: "Absolute",
17
+ // selection
18
+ rowsSelected: (count) => `${count} row${count === 1 ? "" : "s"} selected`,
19
+ // pagination
20
+ rowsPerPage: "Rows per page",
21
+ pageOf: (page, total) => `${page} of ${total}`,
22
+ // empty / loading
23
+ noData: "No data",
24
+ loading: "Loading\u2026",
25
+ loadingMore: "Loading more\u2026",
26
+ // export
27
+ exportCsv: "Export CSV",
28
+ exportCopied: "Copied!",
29
+ // sort
30
+ sortAsc: "Sort ascending",
31
+ sortDesc: "Sort descending",
32
+ unsort: "Remove sort",
33
+ // misc
34
+ pinLeft: "Pin left",
35
+ pinRight: "Pin right",
36
+ unpin: "Unpin",
37
+ hideColumn: "Hide column",
38
+ };
39
+
40
+ export function resolveDataGridStrings(
41
+ override: Partial<DataGridStrings> | undefined,
42
+ ): DataGridStrings {
43
+ if (!override) return DATA_GRID_DEFAULT_STRINGS;
44
+ return { ...DATA_GRID_DEFAULT_STRINGS, ...override };
45
+ }