@siyahi/ui 0.1.9-next.20 → 0.1.9-next.21

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 (88) hide show
  1. package/DESIGN.md +30 -0
  2. package/README.md +3 -3
  3. package/agents/examples/alert.tsx +4 -6
  4. package/agents/examples/bar-list-sides.tsx +11 -3
  5. package/agents/examples/button-states.tsx +23 -0
  6. package/agents/examples/callout.tsx +33 -0
  7. package/agents/examples/change-bar.tsx +104 -0
  8. package/agents/examples/chart-area.tsx +5 -4
  9. package/agents/examples/chart-bar-flows.tsx +3 -2
  10. package/agents/examples/chart-bar-sectors.tsx +2 -0
  11. package/agents/examples/data-table-compact.tsx +95 -0
  12. package/agents/examples/heatmap-calendar.tsx +17 -7
  13. package/agents/examples/heatmap-status.tsx +17 -11
  14. package/agents/examples/heatmap-weekday.tsx +33 -11
  15. package/agents/examples/input-states.tsx +33 -0
  16. package/agents/examples/metric-strip.tsx +9 -3
  17. package/agents/examples/overview-dashboard.tsx +18 -0
  18. package/agents/examples/page-hero.tsx +38 -0
  19. package/agents/examples/range-position-table.tsx +101 -0
  20. package/agents/examples/range-position.tsx +53 -0
  21. package/agents/examples/section-cards-select.tsx +8 -5
  22. package/agents/examples/surfaces.tsx +3 -2
  23. package/agents/examples/target-rows.tsx +70 -0
  24. package/agents/examples/treemap.tsx +2 -1
  25. package/agents/manifest.json +153 -3
  26. package/dist/components/registry/charts/area-chart-card.js +25 -7
  27. package/dist/components/registry/charts/bar-chart-card.d.ts +0 -1
  28. package/dist/components/registry/charts/bar-chart-card.js +11 -4
  29. package/dist/components/registry/charts/line-chart-card.js +2 -2
  30. package/dist/components/registry/dashboardcn/bar-list.js +11 -5
  31. package/dist/components/registry/dashboardcn/kpi-row-card.js +2 -2
  32. package/dist/components/registry/dashboardcn/sparkline.d.ts +5 -1
  33. package/dist/components/registry/dashboardcn/sparkline.js +40 -18
  34. package/dist/components/registry/heatmap/calendar-heatmap.d.ts +4 -2
  35. package/dist/components/registry/heatmap/calendar-heatmap.js +67 -33
  36. package/dist/components/registry/heatmap/status-heatmap.d.ts +5 -3
  37. package/dist/components/registry/heatmap/status-heatmap.js +30 -15
  38. package/dist/components/registry/heatmap/weekday-heatmap.d.ts +4 -2
  39. package/dist/components/registry/heatmap/weekday-heatmap.js +28 -12
  40. package/dist/components/registry/niko-table/core/data-table-context.d.ts +16 -1
  41. package/dist/components/registry/niko-table/core/data-table-context.js +5 -20
  42. package/dist/components/registry/niko-table/core/data-table-root.js +39 -4
  43. package/dist/components/registry/niko-table/core/data-table-structure.js +2 -0
  44. package/dist/components/registry/niko-table/lib/styles.js +1 -1
  45. package/dist/components/registry/niko-table/types/index.d.ts +9 -0
  46. package/dist/components/registry/sidebar-07/nav-main.d.ts +1 -0
  47. package/dist/components/registry/sidebar-07/nav-main.js +9 -2
  48. package/dist/components/siyahi/callout.d.ts +24 -0
  49. package/dist/components/siyahi/callout.js +40 -0
  50. package/dist/components/siyahi/change-bar.d.ts +21 -0
  51. package/dist/components/siyahi/change-bar.js +35 -0
  52. package/dist/components/siyahi/data-table-compact-columns-toggle.d.ts +4 -0
  53. package/dist/components/siyahi/data-table-compact-columns-toggle.js +19 -0
  54. package/dist/components/siyahi/data-table-footnote.d.ts +12 -0
  55. package/dist/components/siyahi/data-table-footnote.js +35 -0
  56. package/dist/components/siyahi/default-sidebar-open.d.ts +8 -0
  57. package/dist/components/siyahi/default-sidebar-open.js +13 -0
  58. package/dist/components/siyahi/page-hero.d.ts +25 -0
  59. package/dist/components/siyahi/page-hero.js +41 -0
  60. package/dist/components/siyahi/page-toc.d.ts +20 -0
  61. package/dist/components/siyahi/page-toc.js +76 -0
  62. package/dist/components/siyahi/quote.d.ts +12 -0
  63. package/dist/components/siyahi/quote.js +23 -0
  64. package/dist/components/siyahi/range-position.d.ts +24 -0
  65. package/dist/components/siyahi/range-position.js +105 -0
  66. package/dist/components/siyahi/siyahi-provider.d.ts +1 -1
  67. package/dist/components/siyahi/siyahi-provider.js +1 -1
  68. package/dist/components/siyahi/target-rows.d.ts +34 -0
  69. package/dist/components/siyahi/target-rows.js +97 -0
  70. package/dist/components/siyahi/treemap.d.ts +4 -1
  71. package/dist/components/siyahi/treemap.js +33 -25
  72. package/dist/components/siyahi/warning-alert.d.ts +10 -0
  73. package/dist/components/siyahi/warning-alert.js +20 -0
  74. package/dist/index.d.ts +15 -4
  75. package/dist/index.js +12 -1
  76. package/dist/lib/chart-frame.d.ts +12 -2
  77. package/dist/lib/chart-frame.js +51 -7
  78. package/dist/lib/chart-series.d.ts +5 -2
  79. package/dist/lib/compact-columns.d.ts +19 -0
  80. package/dist/lib/compact-columns.js +55 -0
  81. package/dist/lib/format.d.ts +7 -2
  82. package/dist/lib/format.js +24 -5
  83. package/dist/lib/treemap.js +6 -1
  84. package/dist/lib/use-element-width.d.ts +5 -0
  85. package/dist/lib/use-element-width.js +15 -0
  86. package/package.json +2 -1
  87. package/report.css +26 -25
  88. package/styles/siyahi.css +159 -43
@@ -90,8 +90,10 @@ export type WeekdayHeatmapProps = HTMLAttributes<HTMLDivElement> & {
90
90
  * @param extraRow - Appends an aggregated row below the grid. `compute` receives all activities and must return 24 values (one per hour). Rendered through `renderExtraRow` on the body, or falls back to `children`.
91
91
  * @param extraColumn - Appends an aggregated column to the right of the grid. `compute` receives all activities and must return 7 values indexed by weekday 0–6. Rendered through `renderExtraColumn` on the body, or falls back to `children`.
92
92
  */
93
- export declare const WeekdayHeatmap: ({ data, weekStart, hours, weekdays: displayedWeekdays, use12Hour, blockSize, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style, className, children, ...props }: WeekdayHeatmapProps) => string | number | bigint | true | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
93
+ export declare const WeekdayHeatmap: ({ data, weekStart, hours, weekdays: displayedWeekdays, use12Hour, blockSize: blockSizeProp, blockMargin, blockRadius, blockAspectRatio, levels: levelsProp, isNormalized, colors, locale, labels: labelsProp, fontSize, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style, className, children, ...props }: WeekdayHeatmapProps) => string | number | bigint | true | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
94
94
  export type WeekdayHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
95
+ /** The cell the page has picked: drawn with a ring and announced as pressed. */
96
+ selected?: boolean;
95
97
  activity: WeekdayHourlyActivityWithLevel;
96
98
  extra?: "row" | "column";
97
99
  highlighted?: boolean;
@@ -99,7 +101,7 @@ export type WeekdayHeatmapBlockProps = HTMLAttributes<SVGRectElement> & {
99
101
  onCellHover?: (activity: WeekdayHourlyActivityWithLevel | null) => void;
100
102
  className?: string;
101
103
  };
102
- export declare function WeekdayHeatmapBlock({ ref, activity, extra, highlighted, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: WeekdayHeatmapBlockProps & {
104
+ export declare function WeekdayHeatmapBlock({ ref, activity, extra, highlighted, selected, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }: WeekdayHeatmapBlockProps & {
103
105
  ref?: React.RefObject<SVGRectElement | null>;
104
106
  }): import("react").JSX.Element | null;
105
107
  export declare namespace WeekdayHeatmapBlock {
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
2
  import { useHeatmapNavigation } from "../../../lib/heatmap-navigation.js";
3
+ import { useElementWidth } from "../../../lib/use-element-width.js";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
5
  import { Fragment as Fragment$1, createContext, use, useMemo } from "react";
5
6
  import { format } from "date-fns";
@@ -48,6 +49,10 @@ const generateWeekdayLabels = (locale) => Array.from({ length: 7 }, (_, i) => {
48
49
  });
49
50
  const EMPTY_STYLE = {};
50
51
  const LABEL_MARGIN = 8;
52
+ const LEGEND_SWATCH = 12;
53
+ const MIN_FITTED_BLOCK = 14;
54
+ const MAX_FITTED_BLOCK = 44;
55
+ const DEFAULT_BLOCK = 24;
51
56
  const WeekdayHeatmapContext = createContext(null);
52
57
  const useWeekdayHeatmap = () => {
53
58
  const context = use(WeekdayHeatmapContext);
@@ -85,8 +90,9 @@ const useWeekdayHeatmap = () => {
85
90
  * @param extraRow - Appends an aggregated row below the grid. `compute` receives all activities and must return 24 values (one per hour). Rendered through `renderExtraRow` on the body, or falls back to `children`.
86
91
  * @param extraColumn - Appends an aggregated column to the right of the grid. `compute` receives all activities and must return 7 values indexed by weekday 0–6. Rendered through `renderExtraColumn` on the body, or falls back to `children`.
87
92
  */
88
- const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displayedWeekdays, use12Hour = false, blockSize = 24, blockMargin = 4, blockRadius = 2, blockAspectRatio = 1, levels: levelsProp = 5, isNormalized = false, colors, locale, labels: labelsProp, fontSize = 14, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style = EMPTY_STYLE, className, children, ...props }) => {
93
+ const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displayedWeekdays, use12Hour = false, blockSize: blockSizeProp, blockMargin = 4, blockRadius = 2, blockAspectRatio = 1, levels: levelsProp = 5, isNormalized = false, colors, locale, labels: labelsProp, fontSize = 14, emptyState, totalCount: totalCountProp, extraRow, extraColumn, style = EMPTY_STYLE, className, children, ...props }) => {
89
94
  const levels = Math.max(1, levelsProp);
95
+ const [rootRef, rootWidth] = useElementWidth();
90
96
  const weekdays = useMemo(() => displayedWeekdays ?? Array.from({ length: 7 }, (_, i) => (weekStart + i) % 7), [displayedWeekdays, weekStart]);
91
97
  const dataWithLevels = useMemo(() => {
92
98
  if (data.length === 0) return [];
@@ -159,9 +165,12 @@ const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displaye
159
165
  ]);
160
166
  const labelWidth = fontSize * 3.5 + LABEL_MARGIN;
161
167
  const totalCount = typeof totalCountProp === "number" ? totalCountProp : dataWithLevels.reduce((sum, a) => sum + a.value, 0);
168
+ const hasExtraColumn = resolvedExtraColumn !== null;
169
+ const columns = hours.length + (hasExtraColumn ? 2 : 0);
170
+ const fitted = (rootWidth - 24 - labelWidth - columns * blockMargin + (hasExtraColumn ? 0 : blockMargin)) / (columns + (hasExtraColumn ? 1 / (2 * blockAspectRatio) : 0));
171
+ const blockSize = blockSizeProp ?? (rootWidth > 0 ? Math.min(MAX_FITTED_BLOCK, Math.max(MIN_FITTED_BLOCK, fitted)) / blockAspectRatio : DEFAULT_BLOCK);
162
172
  const blockWidth = blockSize * blockAspectRatio;
163
173
  const labelHeight = fontSize + LABEL_MARGIN;
164
- const hasExtraColumn = resolvedExtraColumn !== null;
165
174
  const hasExtraRow = resolvedExtraRow !== null;
166
175
  const width = hours.length * (blockWidth + blockMargin) + (hasExtraColumn ? blockSize / 2 + (blockWidth + blockMargin) * 2 : 0) - blockMargin + labelWidth;
167
176
  const rowCount = weekdays.length + (hasExtraRow ? 1 : 0);
@@ -216,8 +225,9 @@ const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displaye
216
225
  return /* @__PURE__ */ jsx(WeekdayHeatmapContext, {
217
226
  value: contextValue,
218
227
  children: /* @__PURE__ */ jsx("div", {
228
+ ref: rootRef,
219
229
  "data-slot": "weekday-heatmap",
220
- className: cn("flex w-max max-w-full flex-col gap-2", className),
230
+ className: cn("flex flex-col gap-2", blockSizeProp === void 0 ? "w-full" : "w-max max-w-full", className),
221
231
  style: {
222
232
  fontSize,
223
233
  ...style
@@ -227,7 +237,7 @@ const WeekdayHeatmap = ({ data, weekStart = 0, hours = HOURS, weekdays: displaye
227
237
  })
228
238
  });
229
239
  };
230
- const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
240
+ const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, selected = false, onCellClick, onCellHover, onClick, onKeyDown, onMouseEnter, onMouseLeave, className, style: styleProp, ...props }) => {
231
241
  const { blockSize, blockWidth, blockMargin, blockRadius, labelWidth, labelHeight, levels, isNormalized, hours, weekdays, labels, colors } = useWeekdayHeatmap();
232
242
  const level = Math.max(0, Math.min(colorStepCount(levels, isNormalized), activity.level));
233
243
  const isExtraRow = extra === "row";
@@ -246,6 +256,7 @@ const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, onCell
246
256
  ref,
247
257
  "data-slot": "weekday-heatmap-block",
248
258
  role: onCellClick ? "button" : "img",
259
+ "aria-pressed": onCellClick ? selected : void 0,
249
260
  tabIndex: onCellClick ? 0 : -1,
250
261
  "aria-label": ariaLabel,
251
262
  className: cn("motion-safe:transition-opacity motion-safe:hover:opacity-70", onCellClick && "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", className),
@@ -254,6 +265,7 @@ const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, onCell
254
265
  "data-hour": isExtraColumn ? "extra" : activity.hour,
255
266
  "data-level": level,
256
267
  "data-highlighted": highlighted || void 0,
268
+ "data-selected": selected || void 0,
257
269
  height: blockSize,
258
270
  rx: blockRadius,
259
271
  ry: blockRadius,
@@ -262,6 +274,10 @@ const WeekdayHeatmapBlock = ({ ref, activity, extra, highlighted = false, onCell
262
274
  y: yPosition,
263
275
  style: {
264
276
  fill: getLevelFill(level, levels, isNormalized, highlighted, colors),
277
+ ...selected ? {
278
+ stroke: "var(--foreground)",
279
+ strokeWidth: 2
280
+ } : {},
265
281
  ...styleProp
266
282
  },
267
283
  onClick: (event) => {
@@ -374,9 +390,9 @@ const WeekdayHeatmapBody = ({ hideHourLabels = false, hideWeekdayLabels = false,
374
390
  role: "img",
375
391
  "aria-label": labels.heatmapLabel ?? "Activity heatmap by weekday and hour",
376
392
  className: "block overflow-visible rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
377
- height: svgHeight + 40,
378
- viewBox: `-20 -20 ${svgWidth + 40} ${svgHeight + 40}`,
379
- width: svgWidth + 40,
393
+ height: svgHeight + 24,
394
+ viewBox: `-12 -12 ${svgWidth + 24} ${svgHeight + 24}`,
395
+ width: svgWidth + 24,
380
396
  children: [
381
397
  !hideHourLabels && /* @__PURE__ */ jsxs("g", {
382
398
  className: cn("fill-current font-mono", labelClassName),
@@ -469,7 +485,7 @@ const WeekdayHeatmapStat = ({ compute, label, className, children, ...props }) =
469
485
  });
470
486
  };
471
487
  const WeekdayHeatmapLegend = ({ labels: labelsProp, className, children, ...props }) => {
472
- const { levels, isNormalized, blockSize, blockWidth, blockRadius, colors, labels } = useWeekdayHeatmap();
488
+ const { levels, isNormalized, blockRadius, colors, labels } = useWeekdayHeatmap();
473
489
  const lessLabel = labelsProp?.less ?? "Less";
474
490
  const moreLabel = labelsProp?.more ?? "More";
475
491
  const legendLevels = Array.from({ length: levels }, (_, i) => isNormalized ? i + 1 : i);
@@ -486,14 +502,14 @@ const WeekdayHeatmapLegend = ({ labels: labelsProp, className, children, ...prop
486
502
  legendLevels.map((level) => children ? /* @__PURE__ */ jsx(Fragment$1, { children: children({ level }) }, `legend-level-${level}`) : /* @__PURE__ */ jsx("svg", {
487
503
  role: "img",
488
504
  "aria-label": (labels.legendLevelLabel ?? "{{level}} contributions").replace("{{level}}", String(level)),
489
- height: blockSize,
490
- width: blockWidth,
505
+ height: LEGEND_SWATCH,
506
+ width: LEGEND_SWATCH,
491
507
  children: /* @__PURE__ */ jsx("rect", {
492
508
  "data-level": level,
493
- height: blockSize,
509
+ height: LEGEND_SWATCH,
494
510
  rx: blockRadius,
495
511
  ry: blockRadius,
496
- width: blockWidth,
512
+ width: LEGEND_SWATCH,
497
513
  style: { fill: getLevelFill(level, levels, isNormalized, false, colors) }
498
514
  })
499
515
  }, `legend-level-${level}`)),
@@ -93,6 +93,11 @@ type DataTableContextProps<TData extends RowData> = DataTableContextState & {
93
93
  scrollContainer: HTMLElement | null;
94
94
  /** True below 40rem of table space; use for compact toolbar and record layouts. */
95
95
  isCompact: boolean;
96
+ /**
97
+ * Set while some columns do not fit and the table has `meta.compact`
98
+ * columns: they hide, rightmost first, until the reader asks for all.
99
+ */
100
+ compactColumns: CompactColumns | null;
96
101
  /** Internal: `<DataTable>` registers its scroll container element here. */
97
102
  registerScrollContainer: (el: HTMLElement | null) => void;
98
103
  /**
@@ -148,12 +153,22 @@ export declare enum DataTableActions {
148
153
  SET = 0,
149
154
  SET_IS_LOADING = 1
150
155
  }
156
+ export interface CompactColumns {
157
+ /** True while columns are hidden to fit; false once the reader shows them all. */
158
+ collapsed: boolean;
159
+ /** The columns hidden to fit right now, for a cell to fold one under a kept figure. */
160
+ hidden: ReadonlySet<string>;
161
+ setCollapsed: (collapsed: boolean) => void;
162
+ }
151
163
  interface DataTableProviderProps<TData extends RowData> {
152
164
  children: React.ReactNode;
153
165
  table: DataTableInstance<TData>;
154
166
  columns?: DataTableColumnDef<TData>[];
155
167
  isLoading?: boolean;
168
+ isCompact: boolean;
169
+ onWidthChange: (width: number) => void;
170
+ compactColumns: CompactColumns | null;
156
171
  }
157
- export declare function DataTableProvider<TData extends RowData>({ children, table, columns, isLoading: externalIsLoading }: DataTableProviderProps<TData>): React.JSX.Element;
172
+ export declare function DataTableProvider<TData extends RowData>({ children, table, columns, isLoading: externalIsLoading, isCompact, onWidthChange, compactColumns }: DataTableProviderProps<TData>): React.JSX.Element;
158
173
  //#endregion
159
174
  export { DataTableContext };
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { useTableWidth } from "../../../../lib/compact-columns.js";
2
3
  import { useGeneratedOptions } from "../hooks/use-generated-options.js";
3
4
  import { useHeaderMinWidths } from "../lib/use-header-min-widths.js";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -113,7 +114,7 @@ function useFlashSet() {
113
114
  }
114
115
  }, [])];
115
116
  }
116
- function DataTableProvider({ children, table, columns, isLoading: externalIsLoading }) {
117
+ function DataTableProvider({ children, table, columns, isLoading: externalIsLoading, isCompact, onWidthChange, compactColumns }) {
117
118
  const [state, dispatch] = useReducer(dataTableReducer, deriveInitialState(externalIsLoading));
118
119
  const setIsLoading = useCallback((value) => {
119
120
  dispatch({
@@ -127,25 +128,7 @@ function DataTableProvider({ children, table, columns, isLoading: externalIsLoad
127
128
  }, []);
128
129
  const [scrollContainer, setScrollContainer] = React.useState(null);
129
130
  const registerScrollContainer = useCallback((el) => setScrollContainer(el), []);
130
- const [isCompact, setIsCompact] = React.useState(false);
131
- React.useLayoutEffect(() => {
132
- if (!scrollContainer) return;
133
- const measure = () => {
134
- const width = scrollContainer.clientWidth;
135
- if (width > 0) {
136
- const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
137
- setIsCompact(width < 40 * rem);
138
- }
139
- };
140
- measure();
141
- const observer = new ResizeObserver(measure);
142
- observer.observe(scrollContainer);
143
- window.addEventListener("resize", measure);
144
- return () => {
145
- observer.disconnect();
146
- window.removeEventListener("resize", measure);
147
- };
148
- }, [scrollContainer]);
131
+ useTableWidth(scrollContainer, onWidthChange);
149
132
  const scrollRowIntoView = useCallback((index, opts) => {
150
133
  if (rowScrollerRef.current) {
151
134
  rowScrollerRef.current(index, opts);
@@ -289,6 +272,7 @@ function DataTableProvider({ children, table, columns, isLoading: externalIsLoad
289
272
  registerRowScroller,
290
273
  scrollContainer,
291
274
  isCompact,
275
+ compactColumns,
292
276
  registerScrollContainer,
293
277
  headerMinWidths,
294
278
  setColumnResizePreview: setManualResizePreview,
@@ -307,6 +291,7 @@ function DataTableProvider({ children, table, columns, isLoading: externalIsLoad
307
291
  registerRowScroller,
308
292
  scrollContainer,
309
293
  isCompact,
294
+ compactColumns,
310
295
  registerScrollContainer,
311
296
  headerMinWidths,
312
297
  setManualResizePreview,
@@ -1,5 +1,6 @@
1
1
  import { TooltipProvider } from "../../../ui/tooltip.js";
2
2
  import { cn } from "../../../../lib/utils.js";
3
+ import { fitColumns, isCompactWidth } from "../../../../lib/compact-columns.js";
3
4
  import { FILTER_VARIANTS, SYSTEM_COLUMN_IDS, SYSTEM_COLUMN_ID_LIST } from "../lib/constants.js";
4
5
  import { DataTableProvider } from "./data-table-context.js";
5
6
  import { detectFeaturesFromChildren } from "../config/feature-detection.js";
@@ -216,7 +217,23 @@ function DataTableRootInternal({ columns, data, children, className, config, get
216
217
  ...detectFeatures.enableColumnResizing ? { minSize: 40 } : {}
217
218
  }), [detectFeatures.enableColumnResizing, detectFeatures.enableSorting]);
218
219
  const controlledSorting = restState?.sorting ?? sorting;
219
- const controlledColumnVisibility = restState?.columnVisibility ?? columnVisibility;
220
+ const [tableWidth, setTableWidth] = React.useState(0);
221
+ const isCompact = isCompactWidth(tableWidth);
222
+ const [showAllColumns, setShowAllColumns] = React.useState(false);
223
+ const requestedColumnVisibility = restState?.columnVisibility ?? columnVisibility;
224
+ const requestedColumnOrder = restState?.columnOrder ?? columnOrder;
225
+ const fitted = React.useMemo(() => fitColumns(columns, requestedColumnOrder, requestedColumnVisibility, tableWidth), [
226
+ columns,
227
+ requestedColumnOrder,
228
+ requestedColumnVisibility,
229
+ tableWidth
230
+ ]);
231
+ const controlledColumnVisibility = fitted && !showAllColumns ? fitted.visibility : requestedColumnVisibility;
232
+ const compactColumns = React.useMemo(() => fitted && fitted.hidden.size > 0 ? {
233
+ collapsed: !showAllColumns,
234
+ hidden: showAllColumns ? NOTHING_HIDDEN : fitted.hidden,
235
+ setCollapsed: (value) => setShowAllColumns(!value)
236
+ } : null, [fitted, showAllColumns]);
220
237
  const controlledRowSelection = restState?.rowSelection ?? rowSelection;
221
238
  const controlledColumnFilters = restState?.columnFilters ?? columnFilters;
222
239
  const controlledGlobalFilter = restState?.globalFilter !== void 0 ? restState.globalFilter : globalFilter$1;
@@ -369,6 +386,9 @@ function DataTableRootInternal({ columns, data, children, className, config, get
369
386
  table,
370
387
  columns: processedColumns,
371
388
  isLoading,
389
+ isCompact,
390
+ onWidthChange: setTableWidth,
391
+ compactColumns,
372
392
  children: /* @__PURE__ */ jsx(TooltipProvider, {
373
393
  ...tooltipProviderDelay,
374
394
  children: /* @__PURE__ */ jsx("div", {
@@ -378,11 +398,16 @@ function DataTableRootInternal({ columns, data, children, className, config, get
378
398
  })
379
399
  });
380
400
  }
381
- function DataTableRoot({ table: externalTable, columns, data, children, className, isLoading, ...rest }) {
382
- if (externalTable) return /* @__PURE__ */ jsx(DataTableProvider, {
383
- table: externalTable,
401
+ function ExternalTableRoot({ table, columns, children, className, isLoading }) {
402
+ const [tableWidth, setTableWidth] = React.useState(0);
403
+ const isCompact = isCompactWidth(tableWidth);
404
+ return /* @__PURE__ */ jsx(DataTableProvider, {
405
+ table,
384
406
  columns,
385
407
  isLoading,
408
+ isCompact,
409
+ onWidthChange: setTableWidth,
410
+ compactColumns: null,
386
411
  children: /* @__PURE__ */ jsx(TooltipProvider, {
387
412
  ...tooltipProviderDelay,
388
413
  children: /* @__PURE__ */ jsx("div", {
@@ -391,6 +416,16 @@ function DataTableRoot({ table: externalTable, columns, data, children, classNam
391
416
  })
392
417
  })
393
418
  });
419
+ }
420
+ const NOTHING_HIDDEN = /* @__PURE__ */ new Set();
421
+ function DataTableRoot({ table: externalTable, columns, data, children, className, isLoading, ...rest }) {
422
+ if (externalTable) return /* @__PURE__ */ jsx(ExternalTableRoot, {
423
+ table: externalTable,
424
+ columns,
425
+ className,
426
+ isLoading,
427
+ children
428
+ });
394
429
  if (!columns || !data) throw new Error("DataTableRoot: Either provide a 'table' prop or both 'columns' and 'data' props");
395
430
  return /* @__PURE__ */ jsx(DataTableRootInternal, {
396
431
  columns,
@@ -41,6 +41,7 @@ const DataTableHeader = React.memo(function DataTableHeader({ className, sticky
41
41
  };
42
42
  return /* @__PURE__ */ jsxs(TableHead, {
43
43
  "data-col-id": header.column.id,
44
+ "data-numeric": header.column.columnDef.meta?.variant === "number" || void 0,
44
45
  style: headerStyle,
45
46
  className: cn(!isCompact && header.column.getIsPinned() && "bg-background", resizing && "relative overflow-hidden"),
46
47
  children: [header.isPlaceholder ? null : /* @__PURE__ */ jsx(DataTableColumnHeaderRoot, {
@@ -82,6 +83,7 @@ const BodyRow = React.memo(function BodyRow({ row, displayIndex, expandColumnId,
82
83
  return /* @__PURE__ */ jsx(TableCell, {
83
84
  "data-flash": flashing ? "true" : void 0,
84
85
  "data-col-id": cell.column.id,
86
+ "data-numeric": cell.column.columnDef.meta?.variant === "number" || void 0,
85
87
  style: cellStyle,
86
88
  className: cn(!cell.getIsGrouped() && "truncate", !isCompact && cell.column.getIsPinned() && "bg-linear-to-r group-hover:from-muted/50 group-hover:to-muted/50 group-data-[context-menu-open]:from-muted/50 group-data-[context-menu-open]:to-muted/50 group-data-[state=selected]:from-muted group-data-[state=selected]:to-muted"),
87
89
  children: renderCellContent(cell)
@@ -17,7 +17,7 @@ const getCommonPinningStyles = (column, isHeader = false, pinningEnabled = true)
17
17
  flexShrink: 0,
18
18
  zIndex: isHeader ? 20 : 10,
19
19
  backgroundColor: "var(--card)",
20
- ...isLeft ? { boxShadow: "inset -2px 0 0 0 color-mix(in oklab, var(--foreground) 40%, transparent), 4px 0 8px -2px color-mix(in oklab, var(--foreground) 14%, transparent)" } : { boxShadow: "inset 2px 0 0 0 color-mix(in oklab, var(--foreground) 40%, transparent), -4px 0 8px -2px color-mix(in oklab, var(--foreground) 14%, transparent)" }
20
+ boxShadow: isLeft ? "inset -1px 0 0 0 var(--border)" : "inset 1px 0 0 0 var(--border)"
21
21
  };
22
22
  };
23
23
  //#endregion
@@ -23,6 +23,15 @@ declare module "@tanstack/react-table" {
23
23
  * `TableMeta.disableFlexFill`.
24
24
  */
25
25
  flex?: boolean;
26
+ /**
27
+ * Never hide this column to fit. Once any column sets it, a table whose
28
+ * columns do not fit its width hides the others, rightmost first, until
29
+ * they do, and `<DataTableCompactColumnsToggle />` shows them all again.
30
+ * Mark the few a reader needs to answer the table's question: the name and
31
+ * its result. Read `useDataTable().compactColumns?.hidden` in a cell to
32
+ * fold a hidden figure under a kept one.
33
+ */
34
+ compact?: boolean;
26
35
  variant?: FilterVariant;
27
36
  options?: Option[];
28
37
  range?: [number, number];
@@ -10,6 +10,7 @@ export declare function NavMain({ items, label }: {
10
10
  items?: {
11
11
  title: string;
12
12
  url: string;
13
+ isActive?: boolean;
13
14
  }[];
14
15
  }[];
15
16
  }): import("react").JSX.Element;
@@ -29,14 +29,21 @@ function NavMain({ items, label = "Navigation" }) {
29
29
  className: "group/collapsible",
30
30
  render: /* @__PURE__ */ jsx(SidebarMenuItem, {}),
31
31
  children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
32
- render: /* @__PURE__ */ jsx(SidebarMenuButton, { tooltip: item.title }),
32
+ render: /* @__PURE__ */ jsx(SidebarMenuButton, {
33
+ tooltip: item.title,
34
+ isActive: item.isActive && !item.items?.some((subItem) => subItem.isActive)
35
+ }),
33
36
  children: [
34
37
  item.icon,
35
38
  /* @__PURE__ */ jsx("span", { children: item.title }),
36
39
  /* @__PURE__ */ jsx(ChevronRightIcon, { className: "ml-auto transition-transform duration-200 group-data-open/collapsible:rotate-90" })
37
40
  ]
38
41
  }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: item.items?.map((subItem) => /* @__PURE__ */ jsx(SidebarMenuSubItem, { children: /* @__PURE__ */ jsx(SidebarMenuSubButton, {
39
- render: /* @__PURE__ */ jsx("a", { href: subItem.url }),
42
+ isActive: subItem.isActive,
43
+ render: /* @__PURE__ */ jsx("a", {
44
+ href: subItem.url,
45
+ "aria-current": subItem.isActive ? "page" : void 0
46
+ }),
40
47
  onClick: closeDrawer,
41
48
  children: /* @__PURE__ */ jsx("span", { children: subItem.title })
42
49
  }) }, subItem.title)) }) })]
@@ -0,0 +1,24 @@
1
+ import { ComponentProps, ReactNode } from "react";
2
+ //#region src/components/siyahi/callout.d.ts
3
+ declare const TONES: {
4
+ readonly neutral: "border-border bg-muted/50 [&_[data-slot=callout-icon]]:text-muted-foreground";
5
+ readonly accent: "border-chart-1/35 bg-chart-1/8 [&_[data-slot=callout-icon]]:text-link";
6
+ readonly warning: "border-warning/45 bg-warning/10 [&_[data-slot=callout-icon]]:text-warning";
7
+ readonly positive: "border-positive/40 bg-positive/8 [&_[data-slot=callout-icon]]:text-positive";
8
+ readonly negative: "border-negative/40 bg-negative/8 [&_[data-slot=callout-icon]]:text-negative";
9
+ };
10
+ export interface CalloutProps extends Omit<ComponentProps<"aside">, "title"> {
11
+ /** neutral for an aside, accent for a key point, warning for a caution, positive or negative for a gain or a loss. */
12
+ tone?: keyof typeof TONES;
13
+ /** A lucide icon, drawn in the tone's colour. */
14
+ icon?: ReactNode;
15
+ title?: ReactNode;
16
+ }
17
+ /**
18
+ * A point set apart from the prose: a takeaway, a caveat, a definition. The whole box takes the tone, a faint wash
19
+ * inside a border of the same colour; never a single coloured edge. For a notice that interrupts, use Alert or
20
+ * WarningAlert instead.
21
+ */
22
+ export declare function Callout({ tone, icon, title, className, children, ...props }: CalloutProps): import("react").JSX.Element;
23
+ //#endregion
24
+ export {};
@@ -0,0 +1,40 @@
1
+ import { cn } from "cn";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/components/siyahi/callout.tsx
4
+ const TONES = {
5
+ neutral: "border-border bg-muted/50 [&_[data-slot=callout-icon]]:text-muted-foreground",
6
+ accent: "border-chart-1/35 bg-chart-1/8 [&_[data-slot=callout-icon]]:text-link",
7
+ warning: "border-warning/45 bg-warning/10 [&_[data-slot=callout-icon]]:text-warning",
8
+ positive: "border-positive/40 bg-positive/8 [&_[data-slot=callout-icon]]:text-positive",
9
+ negative: "border-negative/40 bg-negative/8 [&_[data-slot=callout-icon]]:text-negative"
10
+ };
11
+ /**
12
+ * A point set apart from the prose: a takeaway, a caveat, a definition. The whole box takes the tone, a faint wash
13
+ * inside a border of the same colour; never a single coloured edge. For a notice that interrupts, use Alert or
14
+ * WarningAlert instead.
15
+ */
16
+ function Callout({ tone = "neutral", icon, title, className, children, ...props }) {
17
+ return /* @__PURE__ */ jsxs("aside", {
18
+ "data-slot": "callout",
19
+ "data-tone": tone,
20
+ className: cn("flex gap-3 rounded-lg border px-4 py-3 text-sm", TONES[tone], className),
21
+ ...props,
22
+ children: [icon && /* @__PURE__ */ jsx("span", {
23
+ "data-slot": "callout-icon",
24
+ "aria-hidden": true,
25
+ className: "mt-0.5 shrink-0 [&>svg]:size-4",
26
+ children: icon
27
+ }), /* @__PURE__ */ jsxs("div", {
28
+ className: "flex min-w-0 flex-col gap-1",
29
+ children: [title && /* @__PURE__ */ jsx("p", {
30
+ className: "font-medium",
31
+ children: title
32
+ }), /* @__PURE__ */ jsx("div", {
33
+ className: "text-pretty text-muted-foreground [&_strong]:text-foreground",
34
+ children
35
+ })]
36
+ })]
37
+ });
38
+ }
39
+ //#endregion
40
+ export { Callout };
@@ -0,0 +1,21 @@
1
+ import { DeltaProps } from "./delta.js";
2
+ //#region src/components/siyahi/change-bar.d.ts
3
+ export interface ChangeBarProps {
4
+ /** The signed change, as Delta takes it. */
5
+ value: number;
6
+ /** The column's largest change by size: Math.max(...rows.map((row) => Math.abs(row.change))). */
7
+ max: number;
8
+ /**
9
+ * Draws the bar from a centre line, losses leftward and gains rightward. Pass it when the column holds both signs:
10
+ * rows.some((row) => row.change < 0) && rows.some((row) => row.change > 0).
11
+ */
12
+ diverging?: boolean;
13
+ format?: DeltaProps["format"];
14
+ className?: string;
15
+ }
16
+ /**
17
+ * A table cell's change as a Delta over a 3px bar whose length is the change's size against the column's largest, so
18
+ * the rows rank by eye. The bar is neutral because it shows size; the Delta above it carries the gain or loss colour.
19
+ */
20
+ export declare function ChangeBar({ value, max, diverging, format, className }: ChangeBarProps): import("react").JSX.Element;
21
+ //#endregion
@@ -0,0 +1,35 @@
1
+ import { Delta } from "./delta.js";
2
+ import { cn } from "cn";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/components/siyahi/change-bar.tsx
5
+ /**
6
+ * A table cell's change as a Delta over a 3px bar whose length is the change's size against the column's largest, so
7
+ * the rows rank by eye. The bar is neutral because it shows size; the Delta above it carries the gain or loss colour.
8
+ */
9
+ function ChangeBar({ value, max, diverging = false, format, className }) {
10
+ const share = max > 0 ? Math.min(1, Math.abs(value) / max) : 0;
11
+ const half = diverging ? 50 : 100;
12
+ const side = diverging && value < 0 ? "right" : "left";
13
+ return /* @__PURE__ */ jsxs("div", {
14
+ "data-slot": "change-bar",
15
+ className: cn("flex w-full flex-col items-end gap-1.5", className),
16
+ children: [/* @__PURE__ */ jsx(Delta, {
17
+ value,
18
+ format,
19
+ className: "font-medium"
20
+ }), /* @__PURE__ */ jsxs("div", {
21
+ "aria-hidden": "true",
22
+ className: "relative h-0.75 w-full rounded-full bg-muted",
23
+ children: [/* @__PURE__ */ jsx("div", {
24
+ "data-slot": "change-bar-fill",
25
+ className: "absolute inset-y-0 rounded-full bg-foreground",
26
+ style: {
27
+ [side]: diverging ? "50%" : 0,
28
+ width: `${share * half}%`
29
+ }
30
+ }), diverging && /* @__PURE__ */ jsx("div", { className: "absolute -inset-y-1 left-1/2 w-px -translate-x-1/2 bg-muted-foreground" })]
31
+ })]
32
+ });
33
+ }
34
+ //#endregion
35
+ export { ChangeBar };
@@ -0,0 +1,4 @@
1
+ //#region src/components/siyahi/data-table-compact-columns-toggle.d.ts
2
+ /** Renders only while some columns are hidden to fit, or the reader has shown them all. */
3
+ export declare function DataTableCompactColumnsToggle(): import("react").JSX.Element | null;
4
+ //#endregion
@@ -0,0 +1,19 @@
1
+ import { Button } from "../ui/button.js";
2
+ import { useDataTable } from "../registry/niko-table/core/data-table-context.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/components/siyahi/data-table-compact-columns-toggle.tsx
5
+ /** Renders only while some columns are hidden to fit, or the reader has shown them all. */
6
+ function DataTableCompactColumnsToggle() {
7
+ const { compactColumns } = useDataTable();
8
+ if (!compactColumns) return null;
9
+ const { collapsed, setCollapsed } = compactColumns;
10
+ return /* @__PURE__ */ jsx(Button, {
11
+ variant: "outline",
12
+ size: "sm",
13
+ "aria-pressed": !collapsed,
14
+ onClick: () => setCollapsed(!collapsed),
15
+ children: collapsed ? `All columns (+${compactColumns.hidden.size})` : "Key columns"
16
+ });
17
+ }
18
+ //#endregion
19
+ export { DataTableCompactColumnsToggle };
@@ -0,0 +1,12 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/components/siyahi/data-table-footnote.d.ts
3
+ export interface DataTableFootnoteProps {
4
+ /** One sentence on how to read the table, such as what each bar measures. */
5
+ children: ReactNode;
6
+ }
7
+ /**
8
+ * A full-width last row, inside `DataTable` after its body, that explains the table's encoding. On a table wider than
9
+ * its card the note stays put at the card's width while the columns scroll under it.
10
+ */
11
+ export declare function DataTableFootnote({ children }: DataTableFootnoteProps): import("react").JSX.Element;
12
+ //#endregion
@@ -0,0 +1,35 @@
1
+ import { useDataTable } from "../registry/niko-table/core/data-table-context.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ //#region src/components/siyahi/data-table-footnote.tsx
5
+ /**
6
+ * A full-width last row, inside `DataTable` after its body, that explains the table's encoding. On a table wider than
7
+ * its card the note stays put at the card's width while the columns scroll under it.
8
+ */
9
+ function DataTableFootnote({ children }) {
10
+ const { table, scrollContainer } = useDataTable();
11
+ const [viewport, setViewport] = useState();
12
+ useEffect(() => {
13
+ if (!scrollContainer) return;
14
+ const observer = new ResizeObserver(() => setViewport(scrollContainer.clientWidth));
15
+ observer.observe(scrollContainer);
16
+ return () => observer.disconnect();
17
+ }, [scrollContainer]);
18
+ return /* @__PURE__ */ jsx("tfoot", {
19
+ "data-slot": "data-table-footnote",
20
+ children: /* @__PURE__ */ jsx("tr", {
21
+ className: "border-t",
22
+ children: /* @__PURE__ */ jsx("td", {
23
+ colSpan: table.getVisibleLeafColumns().length,
24
+ className: "p-0",
25
+ children: /* @__PURE__ */ jsx("p", {
26
+ className: "sticky left-0 px-2 py-3 text-sm whitespace-normal text-muted-foreground",
27
+ style: { maxWidth: viewport },
28
+ children
29
+ })
30
+ })
31
+ })
32
+ });
33
+ }
34
+ //#endregion
35
+ export { DataTableFootnote };
@@ -0,0 +1,8 @@
1
+ //#region src/components/siyahi/default-sidebar-open.d.ts
2
+ /**
3
+ * SidebarProvider's `defaultOpen` for an app shell: the reader's last choice, which the provider keeps in the
4
+ * `sidebar_state` cookie, else open — except on a short landscape screen, a phone held sideways, where an open
5
+ * sidebar leaves no room for the page. There it starts as the icon rail.
6
+ */
7
+ export declare function defaultSidebarOpen(): boolean;
8
+ //#endregion
@@ -0,0 +1,13 @@
1
+ //#region src/components/siyahi/default-sidebar-open.ts
2
+ /**
3
+ * SidebarProvider's `defaultOpen` for an app shell: the reader's last choice, which the provider keeps in the
4
+ * `sidebar_state` cookie, else open — except on a short landscape screen, a phone held sideways, where an open
5
+ * sidebar leaves no room for the page. There it starts as the icon rail.
6
+ */
7
+ function defaultSidebarOpen() {
8
+ const saved = document.cookie.split("; ").find((entry) => entry.startsWith("sidebar_state="))?.slice(14);
9
+ if (saved !== void 0) return saved === "true";
10
+ return !matchMedia("(orientation: landscape) and (max-height: 30rem)").matches;
11
+ }
12
+ //#endregion
13
+ export { defaultSidebarOpen };