@agent-native/toolkit 0.8.2 → 0.9.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 (60) hide show
  1. package/README.md +30 -0
  2. package/agent-native.eject.json +33 -0
  3. package/dist/dashboard/DataTable.d.ts +20 -0
  4. package/dist/dashboard/DataTable.d.ts.map +1 -0
  5. package/dist/dashboard/DataTable.js +87 -0
  6. package/dist/dashboard/DataTable.js.map +1 -0
  7. package/dist/dashboard/DateRangePicker.d.ts +15 -0
  8. package/dist/dashboard/DateRangePicker.d.ts.map +1 -0
  9. package/dist/dashboard/DateRangePicker.js +15 -0
  10. package/dist/dashboard/DateRangePicker.js.map +1 -0
  11. package/dist/dashboard/GenericChartPanel.d.ts +57 -0
  12. package/dist/dashboard/GenericChartPanel.d.ts.map +1 -0
  13. package/dist/dashboard/GenericChartPanel.js +119 -0
  14. package/dist/dashboard/GenericChartPanel.js.map +1 -0
  15. package/dist/dashboard/GenericChartPanel.spec.d.ts +2 -0
  16. package/dist/dashboard/GenericChartPanel.spec.d.ts.map +1 -0
  17. package/dist/dashboard/GenericChartPanel.spec.js +41 -0
  18. package/dist/dashboard/GenericChartPanel.spec.js.map +1 -0
  19. package/dist/dashboard/MetricCard.d.ts +14 -0
  20. package/dist/dashboard/MetricCard.d.ts.map +1 -0
  21. package/dist/dashboard/MetricCard.js +10 -0
  22. package/dist/dashboard/MetricCard.js.map +1 -0
  23. package/dist/dashboard/StatsCard.d.ts +16 -0
  24. package/dist/dashboard/StatsCard.d.ts.map +1 -0
  25. package/dist/dashboard/StatsCard.js +8 -0
  26. package/dist/dashboard/StatsCard.js.map +1 -0
  27. package/dist/dashboard/dashboard-layout.d.ts +49 -0
  28. package/dist/dashboard/dashboard-layout.d.ts.map +1 -0
  29. package/dist/dashboard/dashboard-layout.js +231 -0
  30. package/dist/dashboard/dashboard-layout.js.map +1 -0
  31. package/dist/dashboard/dashboard-layout.spec.d.ts +2 -0
  32. package/dist/dashboard/dashboard-layout.spec.d.ts.map +1 -0
  33. package/dist/dashboard/dashboard-layout.spec.js +52 -0
  34. package/dist/dashboard/dashboard-layout.spec.js.map +1 -0
  35. package/dist/dashboard/index.d.ts +7 -0
  36. package/dist/dashboard/index.d.ts.map +1 -0
  37. package/dist/dashboard/index.js +7 -0
  38. package/dist/dashboard/index.js.map +1 -0
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/ui/calendar.js +8 -8
  44. package/dist/ui/calendar.js.map +1 -1
  45. package/dist/ui/date-picker.d.ts.map +1 -1
  46. package/dist/ui/date-picker.js +1 -1
  47. package/dist/ui/date-picker.js.map +1 -1
  48. package/package.json +15 -1
  49. package/src/dashboard/DataTable.tsx +273 -0
  50. package/src/dashboard/DateRangePicker.tsx +57 -0
  51. package/src/dashboard/GenericChartPanel.spec.tsx +53 -0
  52. package/src/dashboard/GenericChartPanel.tsx +461 -0
  53. package/src/dashboard/MetricCard.tsx +54 -0
  54. package/src/dashboard/StatsCard.tsx +49 -0
  55. package/src/dashboard/dashboard-layout.spec.ts +79 -0
  56. package/src/dashboard/dashboard-layout.ts +329 -0
  57. package/src/dashboard/index.ts +6 -0
  58. package/src/index.ts +1 -0
  59. package/src/ui/calendar.tsx +8 -8
  60. package/src/ui/date-picker.tsx +4 -1
@@ -0,0 +1,329 @@
1
+ export const MIN_DASHBOARD_COLUMNS = 1;
2
+ export const MAX_DASHBOARD_COLUMNS = 6;
3
+ export const DEFAULT_DASHBOARD_COLUMNS = 2;
4
+
5
+ export interface DashboardLayoutPanel {
6
+ id: string;
7
+ width?: number;
8
+ columns?: number;
9
+ }
10
+
11
+ export type DashboardPanelRow<TPanel extends DashboardLayoutPanel> = {
12
+ key: string;
13
+ panels: TPanel[];
14
+ };
15
+
16
+ export type DashboardPanelGroup<TPanel extends DashboardLayoutPanel> = {
17
+ key: string;
18
+ section: TPanel | null;
19
+ panels: TPanel[];
20
+ rows: DashboardPanelRow<TPanel>[];
21
+ columns: number;
22
+ };
23
+
24
+ export type DashboardDropSlot =
25
+ | { type: "row"; groupKey: string; rowIndex: number }
26
+ | { type: "column"; groupKey: string; rowIndex: number; columnIndex: number };
27
+
28
+ export type DashboardColumnExpansion = {
29
+ columns: number;
30
+ sectionPanelId: string | null;
31
+ };
32
+
33
+ export type DashboardLayoutOptions<TPanel extends DashboardLayoutPanel> = {
34
+ isSection?: (panel: TPanel) => boolean;
35
+ };
36
+
37
+ export function clampDashboardColumns(value: unknown): number {
38
+ if (typeof value !== "number" || !Number.isFinite(value))
39
+ return DEFAULT_DASHBOARD_COLUMNS;
40
+ return Math.min(
41
+ MAX_DASHBOARD_COLUMNS,
42
+ Math.max(MIN_DASHBOARD_COLUMNS, Math.floor(value)),
43
+ );
44
+ }
45
+
46
+ export function clampPanelWidth(value: unknown, gridColumns: number): number {
47
+ if (typeof value !== "number" || !Number.isFinite(value)) return 1;
48
+ return Math.min(
49
+ clampDashboardColumns(gridColumns),
50
+ Math.max(1, Math.floor(value)),
51
+ );
52
+ }
53
+
54
+ export function rebalanceRowWidths<TPanel extends DashboardLayoutPanel>(
55
+ panels: TPanel[],
56
+ columns: number,
57
+ ): TPanel[] {
58
+ if (panels.length === 0) return [];
59
+ const safeColumns = clampDashboardColumns(columns);
60
+ const base = Math.max(1, Math.floor(safeColumns / panels.length));
61
+ const remainder = safeColumns % panels.length;
62
+ return panels.map((panel, index) => ({
63
+ ...panel,
64
+ width: base + (index < remainder ? 1 : 0),
65
+ }));
66
+ }
67
+
68
+ export function buildDashboardRows<TPanel extends DashboardLayoutPanel>(
69
+ panels: TPanel[],
70
+ columns: number,
71
+ ): DashboardPanelRow<TPanel>[] {
72
+ const safeColumns = clampDashboardColumns(columns);
73
+ const rows: DashboardPanelRow<TPanel>[] = [];
74
+ let current: TPanel[] = [];
75
+ let usedColumns = 0;
76
+ const push = () => {
77
+ if (!current.length) return;
78
+ rows.push({
79
+ key: current.map((panel) => panel.id).join(":") || `empty-${rows.length}`,
80
+ panels: current,
81
+ });
82
+ current = [];
83
+ usedColumns = 0;
84
+ };
85
+ for (const panel of panels) {
86
+ const width = clampPanelWidth(panel.width, safeColumns);
87
+ if (
88
+ current.length &&
89
+ (usedColumns + width > safeColumns || current.length >= safeColumns)
90
+ )
91
+ push();
92
+ current.push(panel);
93
+ usedColumns += width;
94
+ if (usedColumns >= safeColumns || current.length >= safeColumns) push();
95
+ }
96
+ push();
97
+ return rows;
98
+ }
99
+
100
+ export function buildDashboardPanelGroups<TPanel extends DashboardLayoutPanel>(
101
+ panels: TPanel[],
102
+ dashboardColumns: number,
103
+ { isSection = () => false }: DashboardLayoutOptions<TPanel> = {},
104
+ ): DashboardPanelGroup<TPanel>[] {
105
+ const defaultColumns = clampDashboardColumns(dashboardColumns);
106
+ const groups: DashboardPanelGroup<TPanel>[] = [];
107
+ let current: Omit<DashboardPanelGroup<TPanel>, "rows"> = {
108
+ key: "intro",
109
+ section: null,
110
+ panels: [],
111
+ columns: defaultColumns,
112
+ };
113
+ const push = () => {
114
+ if (!current.section && !current.panels.length) return;
115
+ groups.push({
116
+ ...current,
117
+ rows: buildDashboardRows(current.panels, current.columns),
118
+ });
119
+ };
120
+ for (const panel of panels) {
121
+ if (isSection(panel)) {
122
+ push();
123
+ current = {
124
+ key: panel.id,
125
+ section: panel,
126
+ panels: [],
127
+ columns: clampDashboardColumns(panel.columns ?? defaultColumns),
128
+ };
129
+ } else {
130
+ current.panels.push(panel);
131
+ }
132
+ }
133
+ push();
134
+ return groups;
135
+ }
136
+
137
+ function flattenGroups<TPanel extends DashboardLayoutPanel>(
138
+ groups: DashboardPanelGroup<TPanel>[],
139
+ ): TPanel[] {
140
+ return groups.flatMap((group) => [
141
+ ...(group.section ? [group.section] : []),
142
+ ...group.rows.flatMap((row) =>
143
+ rebalanceRowWidths(row.panels, group.columns),
144
+ ),
145
+ ]);
146
+ }
147
+
148
+ export function removePanelFromLayout<TPanel extends DashboardLayoutPanel>(
149
+ panels: TPanel[],
150
+ panelId: string,
151
+ dashboardColumns: number,
152
+ options?: DashboardLayoutOptions<TPanel>,
153
+ ): TPanel[] {
154
+ const groups = buildDashboardPanelGroups(panels, dashboardColumns, options);
155
+ return flattenGroups(
156
+ groups
157
+ .map((group) => ({
158
+ ...group,
159
+ section: group.section?.id === panelId ? null : group.section,
160
+ rows: group.rows
161
+ .map((row) => ({
162
+ ...row,
163
+ panels: row.panels.filter((panel) => panel.id !== panelId),
164
+ }))
165
+ .filter((row) => row.panels.length),
166
+ }))
167
+ .filter((group) => group.section || group.rows.length),
168
+ );
169
+ }
170
+
171
+ export function dropSlotId(slot: DashboardDropSlot): string {
172
+ return slot.type === "row"
173
+ ? `dashboard-drop:row:${slot.groupKey}:${slot.rowIndex}`
174
+ : `dashboard-drop:column:${slot.groupKey}:${slot.rowIndex}:${slot.columnIndex}`;
175
+ }
176
+
177
+ export function readDropSlot(value: unknown): DashboardDropSlot | null {
178
+ if (!value || typeof value !== "object") return null;
179
+ const slot = (value as { slot?: unknown }).slot;
180
+ if (!slot || typeof slot !== "object") return null;
181
+ const candidate = slot as Partial<DashboardDropSlot>;
182
+ if (
183
+ candidate.type === "row" &&
184
+ typeof candidate.groupKey === "string" &&
185
+ typeof candidate.rowIndex === "number"
186
+ ) {
187
+ return {
188
+ type: "row",
189
+ groupKey: candidate.groupKey,
190
+ rowIndex: candidate.rowIndex,
191
+ };
192
+ }
193
+ if (
194
+ candidate.type === "column" &&
195
+ typeof candidate.groupKey === "string" &&
196
+ typeof candidate.rowIndex === "number" &&
197
+ typeof candidate.columnIndex === "number"
198
+ ) {
199
+ return {
200
+ type: "column",
201
+ groupKey: candidate.groupKey,
202
+ rowIndex: candidate.rowIndex,
203
+ columnIndex: candidate.columnIndex,
204
+ };
205
+ }
206
+ return null;
207
+ }
208
+
209
+ export function sameDropSlot(
210
+ a: DashboardDropSlot | null,
211
+ b: DashboardDropSlot,
212
+ ): boolean {
213
+ return (
214
+ !!a &&
215
+ a.type === b.type &&
216
+ a.groupKey === b.groupKey &&
217
+ a.rowIndex === b.rowIndex &&
218
+ (a.type === "row" ||
219
+ (b.type === "column" && a.columnIndex === b.columnIndex))
220
+ );
221
+ }
222
+
223
+ function findPanel<TPanel extends DashboardLayoutPanel>(
224
+ groups: DashboardPanelGroup<TPanel>[],
225
+ panelId: string,
226
+ ) {
227
+ for (const group of groups) {
228
+ for (let rowIndex = 0; rowIndex < group.rows.length; rowIndex++) {
229
+ const columnIndex = group.rows[rowIndex].panels.findIndex(
230
+ (panel) => panel.id === panelId,
231
+ );
232
+ if (columnIndex >= 0) return { group, rowIndex, columnIndex };
233
+ }
234
+ }
235
+ return null;
236
+ }
237
+
238
+ export function columnExpansionForDropSlot<TPanel extends DashboardLayoutPanel>(
239
+ groups: DashboardPanelGroup<TPanel>[],
240
+ panelId: string,
241
+ slot: DashboardDropSlot,
242
+ ): DashboardColumnExpansion | null {
243
+ if (slot.type !== "column") return null;
244
+ const group = groups.find((item) => item.key === slot.groupKey);
245
+ const row = group?.rows[slot.rowIndex];
246
+ if (!group || !row) return null;
247
+ const required = row.panels.some((panel) => panel.id === panelId)
248
+ ? row.panels.length
249
+ : row.panels.length + 1;
250
+ return required > group.columns
251
+ ? {
252
+ columns: clampDashboardColumns(required),
253
+ sectionPanelId: group.section?.id ?? null,
254
+ }
255
+ : null;
256
+ }
257
+
258
+ /** Moves a panel by visible row/column slot and rebalances persisted widths. */
259
+ export function movePanelToDropSlot<TPanel extends DashboardLayoutPanel>(
260
+ panels: TPanel[],
261
+ panelId: string,
262
+ slot: DashboardDropSlot,
263
+ dashboardColumns: number,
264
+ options?: DashboardLayoutOptions<TPanel>,
265
+ ): TPanel[] {
266
+ const groups = buildDashboardPanelGroups(panels, dashboardColumns, options);
267
+ const source = findPanel(groups, panelId);
268
+ if (!source) return panels;
269
+ const moving = source.group.rows[source.rowIndex].panels[source.columnIndex];
270
+ const sourceWasSingle =
271
+ source.group.rows[source.rowIndex].panels.length === 1;
272
+ const next = groups.map((group) => ({
273
+ ...group,
274
+ rows: group.rows
275
+ .map((row) => ({
276
+ ...row,
277
+ panels: row.panels.filter((panel) => panel.id !== panelId),
278
+ }))
279
+ .filter((row) => row.panels.length),
280
+ }));
281
+ const target = next.find((group) => group.key === slot.groupKey);
282
+ if (!target) return panels;
283
+ if (slot.type === "row") {
284
+ let rowIndex = slot.rowIndex;
285
+ if (
286
+ source.group.key === target.key &&
287
+ sourceWasSingle &&
288
+ source.rowIndex < rowIndex
289
+ )
290
+ rowIndex--;
291
+ if (
292
+ source.group.key === target.key &&
293
+ sourceWasSingle &&
294
+ source.rowIndex === rowIndex
295
+ )
296
+ return panels;
297
+ target.rows.splice(Math.max(0, rowIndex), 0, {
298
+ key: moving.id,
299
+ panels: [moving],
300
+ });
301
+ } else {
302
+ let rowIndex = slot.rowIndex;
303
+ if (
304
+ source.group.key === target.key &&
305
+ sourceWasSingle &&
306
+ source.rowIndex < rowIndex
307
+ )
308
+ rowIndex--;
309
+ const targetRow = target.rows[rowIndex];
310
+ if (!targetRow) return panels;
311
+ let columnIndex = slot.columnIndex;
312
+ if (
313
+ source.group.key === target.key &&
314
+ source.rowIndex === slot.rowIndex &&
315
+ source.columnIndex < columnIndex
316
+ )
317
+ columnIndex--;
318
+ targetRow.panels.splice(
319
+ Math.max(0, Math.min(columnIndex, targetRow.panels.length)),
320
+ 0,
321
+ moving,
322
+ );
323
+ target.columns = Math.max(
324
+ target.columns,
325
+ clampDashboardColumns(targetRow.panels.length),
326
+ );
327
+ }
328
+ return flattenGroups(next);
329
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./DataTable.js";
2
+ export * from "./DateRangePicker.js";
3
+ export * from "./GenericChartPanel.js";
4
+ export * from "./MetricCard.js";
5
+ export * from "./StatsCard.js";
6
+ export * from "./dashboard-layout.js";
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./app-shell/index.js";
2
2
  export * from "./collab-ui/index.js";
3
+ export * from "./dashboard/index.js";
3
4
  export * from "./hooks/index.js";
4
5
  export * from "./onboarding/index.js";
5
6
  export * from "./provider.js";
@@ -27,7 +27,7 @@ function Calendar({
27
27
  <DayPicker
28
28
  showOutsideDays={showOutsideDays}
29
29
  className={cn(
30
- "bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
30
+ "bg-background group/calendar p-3 [--cell-size:1.75rem] sm:[--cell-size:1.6rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
31
31
  String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
32
32
  String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
33
33
  className,
@@ -51,20 +51,20 @@ function Calendar({
51
51
  ),
52
52
  button_previous: cn(
53
53
  buttonVariants({ variant: buttonVariant }),
54
- "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
54
+ "h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50",
55
55
  defaultClassNames.button_previous,
56
56
  ),
57
57
  button_next: cn(
58
58
  buttonVariants({ variant: buttonVariant }),
59
- "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
59
+ "h-(--cell-size) w-(--cell-size) select-none p-0 aria-disabled:opacity-50",
60
60
  defaultClassNames.button_next,
61
61
  ),
62
62
  month_caption: cn(
63
- "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
63
+ "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
64
64
  defaultClassNames.month_caption,
65
65
  ),
66
66
  dropdowns: cn(
67
- "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
67
+ "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
68
68
  defaultClassNames.dropdowns,
69
69
  ),
70
70
  dropdown_root: cn(
@@ -90,7 +90,7 @@ function Calendar({
90
90
  ),
91
91
  week: cn("mt-2 flex w-full", defaultClassNames.week),
92
92
  week_number_header: cn(
93
- "w-[--cell-size] select-none",
93
+ "w-(--cell-size) select-none",
94
94
  defaultClassNames.week_number_header,
95
95
  ),
96
96
  week_number: cn(
@@ -160,7 +160,7 @@ function Calendar({
160
160
  WeekNumber: ({ children, ...props }) => {
161
161
  return (
162
162
  <td {...props}>
163
- <div className="flex size-[--cell-size] items-center justify-center text-center">
163
+ <div className="flex size-(--cell-size) items-center justify-center text-center">
164
164
  {children}
165
165
  </div>
166
166
  </td>
@@ -202,7 +202,7 @@ function CalendarDayButton({
202
202
  data-range-end={modifiers.range_end}
203
203
  data-range-middle={modifiers.range_middle}
204
204
  className={cn(
205
- "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
205
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-(--cell-size) flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
206
206
  defaultClassNames.day,
207
207
  className,
208
208
  )}
@@ -45,7 +45,10 @@ export function DatePicker({
45
45
  )}
46
46
  </Button>
47
47
  </PopoverTrigger>
48
- <PopoverContent className="w-auto p-0" align="start">
48
+ <PopoverContent
49
+ className="w-auto max-w-[calc(100vw-2rem)] p-0"
50
+ align="start"
51
+ >
49
52
  <Calendar
50
53
  mode="single"
51
54
  selected={validDate}