@stackframe/dashboard-ui-components 2.8.86 → 2.8.89

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 (64) hide show
  1. package/dist/components/analytics-chart/analytics-chart-pie.js +3 -3
  2. package/dist/components/analytics-chart/analytics-chart-pie.js.map +1 -1
  3. package/dist/components/data-grid/data-grid-sizing.d.ts +2 -1
  4. package/dist/components/data-grid/data-grid-sizing.d.ts.map +1 -1
  5. package/dist/components/data-grid/data-grid-sizing.js +33 -4
  6. package/dist/components/data-grid/data-grid-sizing.js.map +1 -1
  7. package/dist/components/data-grid/data-grid-toolbar.js +18 -15
  8. package/dist/components/data-grid/data-grid-toolbar.js.map +1 -1
  9. package/dist/components/data-grid/data-grid.d.ts +35 -1
  10. package/dist/components/data-grid/data-grid.d.ts.map +1 -1
  11. package/dist/components/data-grid/data-grid.js +329 -127
  12. package/dist/components/data-grid/data-grid.js.map +1 -1
  13. package/dist/components/data-grid/data-grid.test.d.ts +1 -0
  14. package/dist/components/data-grid/data-grid.test.js +215 -0
  15. package/dist/components/data-grid/data-grid.test.js.map +1 -0
  16. package/dist/components/data-grid/index.d.ts +3 -2
  17. package/dist/components/data-grid/index.js +13 -0
  18. package/dist/components/data-grid/state.d.ts.map +1 -1
  19. package/dist/components/data-grid/state.js +24 -7
  20. package/dist/components/data-grid/state.js.map +1 -1
  21. package/dist/components/data-grid/types.d.ts +34 -3
  22. package/dist/components/data-grid/types.d.ts.map +1 -1
  23. package/dist/components/data-grid/use-data-source.d.ts +6 -0
  24. package/dist/components/data-grid/use-data-source.d.ts.map +1 -1
  25. package/dist/components/data-grid/use-data-source.js +10 -2
  26. package/dist/components/data-grid/use-data-source.js.map +1 -1
  27. package/dist/components/tabs.d.ts +5 -1
  28. package/dist/components/tabs.d.ts.map +1 -1
  29. package/dist/components/tabs.js +40 -27
  30. package/dist/components/tabs.js.map +1 -1
  31. package/dist/dashboard-ui-components.global.js +672 -368
  32. package/dist/dashboard-ui-components.global.js.map +4 -4
  33. package/dist/esm/components/analytics-chart/analytics-chart-pie.js +3 -3
  34. package/dist/esm/components/analytics-chart/analytics-chart-pie.js.map +1 -1
  35. package/dist/esm/components/data-grid/data-grid-sizing.d.ts +2 -1
  36. package/dist/esm/components/data-grid/data-grid-sizing.d.ts.map +1 -1
  37. package/dist/esm/components/data-grid/data-grid-sizing.js +33 -5
  38. package/dist/esm/components/data-grid/data-grid-sizing.js.map +1 -1
  39. package/dist/esm/components/data-grid/data-grid-toolbar.js +18 -15
  40. package/dist/esm/components/data-grid/data-grid-toolbar.js.map +1 -1
  41. package/dist/esm/components/data-grid/data-grid.d.ts +35 -1
  42. package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
  43. package/dist/esm/components/data-grid/data-grid.js +329 -128
  44. package/dist/esm/components/data-grid/data-grid.js.map +1 -1
  45. package/dist/esm/components/data-grid/data-grid.test.d.ts +1 -0
  46. package/dist/esm/components/data-grid/data-grid.test.js +215 -0
  47. package/dist/esm/components/data-grid/data-grid.test.js.map +1 -0
  48. package/dist/esm/components/data-grid/index.d.ts +3 -2
  49. package/dist/esm/components/data-grid/index.js +3 -2
  50. package/dist/esm/components/data-grid/state.d.ts.map +1 -1
  51. package/dist/esm/components/data-grid/state.js +24 -7
  52. package/dist/esm/components/data-grid/state.js.map +1 -1
  53. package/dist/esm/components/data-grid/types.d.ts +34 -3
  54. package/dist/esm/components/data-grid/types.d.ts.map +1 -1
  55. package/dist/esm/components/data-grid/use-data-source.d.ts +6 -0
  56. package/dist/esm/components/data-grid/use-data-source.d.ts.map +1 -1
  57. package/dist/esm/components/data-grid/use-data-source.js +10 -2
  58. package/dist/esm/components/data-grid/use-data-source.js.map +1 -1
  59. package/dist/esm/components/tabs.d.ts +5 -1
  60. package/dist/esm/components/tabs.d.ts.map +1 -1
  61. package/dist/esm/components/tabs.js +40 -27
  62. package/dist/esm/components/tabs.js.map +1 -1
  63. package/dist/index.d.ts +3 -2
  64. package/package.json +4 -4
@@ -100,7 +100,7 @@ function AnalyticsChartPie({ wrapperRef, primarySegmentSeries, compareSegmentSer
100
100
  const activeIdx = hoverKey ? legendRows.findIndex((r) => r.key === hoverKey) : -1;
101
101
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
102
102
  ref: wrapperRef,
103
- className: "relative w-full select-none",
103
+ className: "relative flex h-full min-h-0 w-full min-w-0 flex-1 flex-col select-none",
104
104
  onClick: (e) => e.stopPropagation(),
105
105
  children: [zoomRange && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
106
106
  className: "absolute right-2 top-2 z-20",
@@ -115,7 +115,7 @@ function AnalyticsChartPie({ wrapperRef, primarySegmentSeries, compareSegmentSer
115
115
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: strings.resetZoom })]
116
116
  })
117
117
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
118
- className: "flex min-h-[260px] flex-col items-center gap-6 sm:min-h-[320px] sm:flex-row sm:items-center sm:justify-center sm:gap-10",
118
+ className: "flex h-full min-h-0 w-full flex-1 flex-wrap content-center items-center justify-center gap-x-10 gap-y-6",
119
119
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
120
120
  className: "flex shrink-0 flex-col items-center gap-3",
121
121
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -201,7 +201,7 @@ function AnalyticsChartPie({ wrapperRef, primarySegmentSeries, compareSegmentSer
201
201
  })]
202
202
  })]
203
203
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
204
- className: "flex w-full max-w-[380px] flex-col gap-1",
204
+ className: "flex min-w-[200px] max-w-[300px] flex-col gap-1",
205
205
  children: legendRows.map((r) => {
206
206
  const isActive = hoverKey === r.key;
207
207
  const dimmed = hoverKey != null && !isActive;
@@ -1 +1 @@
1
- {"version":3,"file":"analytics-chart-pie.js","names":["MagnifyingGlassMinusIcon","DesignChartContainer","PieChart","Pie","Cell","TrendPill"],"sources":["../../../src/components/analytics-chart/analytics-chart-pie.tsx"],"sourcesContent":["import { MagnifyingGlassMinusIcon } from \"@phosphor-icons/react\";\nimport { cn } from \"@stackframe/stack-ui\";\nimport { type DesignChartConfig, DesignChartContainer } from \"../chart-container\";\nimport { type CSSProperties, type Ref, useMemo } from \"react\";\nimport { Cell, Pie, PieChart } from \"recharts\";\nimport { TrendPill } from \"./default-analytics-chart-tooltip\";\nimport { formatDelta } from \"./format\";\nimport type { AnalyticsChartStrings } from \"./strings\";\nimport {\n cssIdent,\n type AnalyticsChartPieProps,\n type AnalyticsChartSeries,\n type FormatKind,\n type Point,\n} from \"./types\";\n\ntype SegmentColors = {\n primary: { light: string[], dark: string[] },\n compare: { light: string[], dark: string[] },\n};\n\ntype AnalyticsChartPieBodyProps = {\n wrapperRef: Ref<HTMLDivElement>,\n primarySegmentSeries: readonly AnalyticsChartSeries[],\n compareSegmentSeries: readonly AnalyticsChartSeries[],\n aggregatedPrimarySegments: number[],\n aggregatedCompareSegments: number[],\n aggregatedPrimaryTotal: number,\n aggregatedCompareTotal: number,\n segmentColors: SegmentColors,\n showPrimary: boolean,\n showCompare: boolean,\n xFormatKind: FormatKind,\n yFormatKind: FormatKind,\n hoverKey: string | null,\n setHoverKey: (k: string | null) => void,\n zoomRange: [number, number] | null,\n onResetZoom: () => void,\n visibleStart: number,\n visibleEnd: number,\n fullData: Point[],\n strings: AnalyticsChartStrings,\n fmtValue: (value: number, kind: FormatKind) => string,\n pie: AnalyticsChartPieProps | undefined,\n};\n\nconst DEFAULT_PIE_CLASSNAME = \"aspect-square h-[220px] w-[220px] sm:h-[240px] sm:w-[240px]\";\n\nexport function AnalyticsChartPie({\n wrapperRef,\n primarySegmentSeries,\n compareSegmentSeries,\n aggregatedPrimarySegments,\n aggregatedCompareSegments,\n aggregatedPrimaryTotal,\n aggregatedCompareTotal,\n segmentColors,\n showPrimary,\n showCompare,\n xFormatKind,\n yFormatKind,\n hoverKey,\n setHoverKey,\n zoomRange,\n onResetZoom,\n visibleStart,\n visibleEnd,\n fullData,\n strings,\n fmtValue,\n pie,\n}: AnalyticsChartPieBodyProps) {\n const innerRadius = pie?.innerRadius ?? 60;\n const outerRadius = pie?.outerRadius ?? 84;\n const compareInnerRadius = pie?.compareInnerRadius ?? 36;\n const compareOuterRadius = pie?.compareOuterRadius ?? 52;\n const containerClassName = pie?.className ?? DEFAULT_PIE_CLASSNAME;\n\n const canonicalSeries = primarySegmentSeries.length > 0\n ? primarySegmentSeries\n : compareSegmentSeries;\n const usePrimaryForCanonical = primarySegmentSeries.length > 0;\n\n const compareValueByKey = useMemo(() => {\n const m = new Map<string, number>();\n compareSegmentSeries.forEach((s, sIdx) => {\n m.set(s.key, aggregatedCompareSegments[sIdx] ?? 0);\n });\n return m;\n }, [compareSegmentSeries, aggregatedCompareSegments]);\n const primaryValueByKey = useMemo(() => {\n const m = new Map<string, number>();\n primarySegmentSeries.forEach((s, sIdx) => {\n m.set(s.key, aggregatedPrimarySegments[sIdx] ?? 0);\n });\n return m;\n }, [primarySegmentSeries, aggregatedPrimarySegments]);\n\n const canonicalTotal = usePrimaryForCanonical\n ? aggregatedPrimaryTotal\n : aggregatedCompareTotal;\n\n const legendRows = useMemo(\n () =>\n canonicalSeries\n .map((s, sIdx) => {\n const value = usePrimaryForCanonical\n ? (aggregatedPrimarySegments[sIdx] ?? 0)\n : (aggregatedCompareSegments[sIdx] ?? 0);\n const prevValue = usePrimaryForCanonical\n ? (compareValueByKey.get(s.key) ?? 0)\n : (primaryValueByKey.get(s.key) ?? 0);\n return {\n key: s.key,\n label: s.label,\n sIdx,\n value,\n prevValue,\n pct: canonicalTotal > 0 ? value / canonicalTotal : 0,\n fill: segmentColors.primary.light[sIdx],\n fillDark: segmentColors.primary.dark[sIdx],\n fillCompare: segmentColors.compare.light[sIdx],\n fillCompareDark: segmentColors.compare.dark[sIdx],\n };\n })\n .sort((a, b) => b.value - a.value),\n [\n canonicalSeries,\n usePrimaryForCanonical,\n aggregatedPrimarySegments,\n aggregatedCompareSegments,\n compareValueByKey,\n primaryValueByKey,\n canonicalTotal,\n segmentColors,\n ],\n );\n\n const chartConfig = useMemo<DesignChartConfig>(() => {\n const config: DesignChartConfig = {};\n canonicalSeries.forEach((s, sIdx) => {\n config[cssIdent(s.key)] = {\n label: s.label,\n theme: {\n light: segmentColors.primary.light[sIdx],\n dark: segmentColors.primary.dark[sIdx],\n },\n };\n config[`compare-${cssIdent(s.key)}`] = {\n label: s.label,\n theme: {\n light: segmentColors.compare.light[sIdx],\n dark: segmentColors.compare.dark[sIdx],\n },\n };\n });\n return config;\n }, [canonicalSeries, segmentColors]);\n\n const activeRow = hoverKey\n ? legendRows.find((r) => r.key === hoverKey) ?? null\n : null;\n const activeDelta = activeRow\n ? formatDelta(activeRow.value, activeRow.prevValue)\n : formatDelta(aggregatedPrimaryTotal, aggregatedCompareTotal);\n\n const windowDays = visibleEnd - visibleStart + 1;\n const startLabel = fmtValue(fullData[visibleStart]!.ts, xFormatKind);\n const endLabel = fmtValue(fullData[visibleEnd]!.ts, xFormatKind);\n\n const outerData = legendRows.map((r) => ({ name: cssIdent(r.key), hoverKey: r.key, value: r.value, fill: r.fill }));\n const innerData = legendRows.map((r) => ({ name: cssIdent(r.key), hoverKey: r.key, value: r.prevValue, fill: r.fillCompare }));\n const activeIdx = hoverKey ? legendRows.findIndex((r) => r.key === hoverKey) : -1;\n\n return (\n <div\n ref={wrapperRef}\n className=\"relative w-full select-none\"\n onClick={(e) => e.stopPropagation()}\n >\n {zoomRange && (\n <div className=\"absolute right-2 top-2 z-20\">\n <button\n type=\"button\"\n onClick={onResetZoom}\n className=\"inline-flex items-center gap-1.5 rounded-full bg-blue-500/10 px-2.5 py-1 font-mono text-[10px] font-semibold uppercase tracking-wider text-blue-600 ring-1 ring-blue-500/30 transition-colors duration-150 hover:bg-blue-500/15 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50 dark:text-blue-300 dark:ring-blue-400/30\"\n >\n <MagnifyingGlassMinusIcon weight=\"bold\" className=\"size-3\" aria-hidden=\"true\" />\n <span>{strings.resetZoom}</span>\n </button>\n </div>\n )}\n\n <div className=\"flex min-h-[260px] flex-col items-center gap-6 sm:min-h-[320px] sm:flex-row sm:items-center sm:justify-center sm:gap-10\">\n <div className=\"flex shrink-0 flex-col items-center gap-3\">\n <div className=\"relative\">\n <DesignChartContainer config={chartConfig} className={containerClassName}>\n <PieChart\n role=\"img\"\n aria-label={strings.pieAriaLabel({\n segmentCount: canonicalSeries.length,\n windowDays,\n })}\n >\n {showPrimary && (\n <Pie\n data={outerData}\n dataKey=\"value\"\n nameKey=\"name\"\n cx=\"50%\"\n cy=\"50%\"\n innerRadius={innerRadius}\n outerRadius={outerRadius}\n paddingAngle={1}\n startAngle={90}\n endAngle={-270}\n isAnimationActive={false}\n stroke=\"none\"\n >\n {outerData.map((d, i) => {\n const inactive = activeIdx >= 0 && activeIdx !== i;\n return (\n <Cell\n key={`outer-${d.name}`}\n fill={`var(--color-${d.name})`}\n opacity={inactive ? 0.22 : 1}\n onMouseEnter={() => setHoverKey(d.hoverKey)}\n onMouseLeave={() => setHoverKey(null)}\n />\n );\n })}\n </Pie>\n )}\n {showCompare && (\n <Pie\n data={innerData}\n dataKey=\"value\"\n nameKey=\"name\"\n cx=\"50%\"\n cy=\"50%\"\n innerRadius={compareInnerRadius}\n outerRadius={compareOuterRadius}\n paddingAngle={1}\n startAngle={90}\n endAngle={-270}\n isAnimationActive={false}\n stroke=\"none\"\n >\n {innerData.map((d, i) => {\n const inactive = activeIdx >= 0 && activeIdx !== i;\n return (\n <Cell\n key={`inner-${d.name}`}\n fill={`var(--color-compare-${d.name})`}\n opacity={inactive ? 0.22 : 0.95}\n onMouseEnter={() => setHoverKey(d.hoverKey)}\n onMouseLeave={() => setHoverKey(null)}\n />\n );\n })}\n </Pie>\n )}\n </PieChart>\n </DesignChartContainer>\n\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"block max-w-[68px] truncate font-mono text-[9px] uppercase tracking-wider text-muted-foreground\">\n {activeRow ? activeRow.label : strings.pieTotalCenter}\n </span>\n <span className=\"mt-0.5 block max-w-[72px] truncate font-mono text-xl font-semibold leading-none tabular-nums text-foreground\">\n {fmtValue(activeRow ? activeRow.value : canonicalTotal, yFormatKind)}\n </span>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 text-center\">\n <span className=\"font-mono text-[10px] uppercase tracking-wider text-muted-foreground/80\">\n {startLabel} – {endLabel}\n </span>\n {showCompare && (\n <TrendPill delta={activeDelta} size=\"sm\" label={strings.pieVsPrev} />\n )}\n </div>\n </div>\n\n <ul className=\"flex w-full max-w-[380px] flex-col gap-1\">\n {legendRows.map((r) => {\n const isActive = hoverKey === r.key;\n const dimmed = hoverKey != null && !isActive;\n const rowDelta = formatDelta(r.value, r.prevValue);\n return (\n <li key={r.key}>\n <button\n type=\"button\"\n onMouseEnter={() => setHoverKey(r.key)}\n onMouseLeave={() => setHoverKey(null)}\n onFocus={() => setHoverKey(r.key)}\n onBlur={() => setHoverKey(null)}\n className={cn(\n \"flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left transition-[background-color,opacity] duration-150 hover:bg-foreground/[0.04] hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/20\",\n isActive && \"bg-foreground/[0.05]\",\n dimmed && \"opacity-50\",\n )}\n >\n <span\n className=\"size-2.5 shrink-0 rounded-[3px] bg-[var(--c-l)] dark:bg-[var(--c-d)]\"\n style={{ \"--c-l\": r.fill, \"--c-d\": r.fillDark } as CSSProperties}\n />\n <span className=\"min-w-0 flex-1 truncate text-[12px] font-medium text-foreground\">\n {r.label}\n </span>\n <span className=\"shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground\">\n {(r.pct * 100).toFixed(1)}%\n </span>\n <span className=\"shrink-0 min-w-[48px] text-right font-mono text-[11px] font-semibold tabular-nums text-foreground\">\n {fmtValue(r.value, yFormatKind)}\n </span>\n {showCompare && (\n <span className=\"shrink-0\">\n <TrendPill delta={rowDelta} size=\"sm\" />\n </span>\n )}\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"analytics-chart-pie.js","names":["MagnifyingGlassMinusIcon","DesignChartContainer","PieChart","Pie","Cell","TrendPill"],"sources":["../../../src/components/analytics-chart/analytics-chart-pie.tsx"],"sourcesContent":["import { MagnifyingGlassMinusIcon } from \"@phosphor-icons/react\";\nimport { cn } from \"@stackframe/stack-ui\";\nimport { type DesignChartConfig, DesignChartContainer } from \"../chart-container\";\nimport { type CSSProperties, type Ref, useMemo } from \"react\";\nimport { Cell, Pie, PieChart } from \"recharts\";\nimport { TrendPill } from \"./default-analytics-chart-tooltip\";\nimport { formatDelta } from \"./format\";\nimport type { AnalyticsChartStrings } from \"./strings\";\nimport {\n cssIdent,\n type AnalyticsChartPieProps,\n type AnalyticsChartSeries,\n type FormatKind,\n type Point,\n} from \"./types\";\n\ntype SegmentColors = {\n primary: { light: string[], dark: string[] },\n compare: { light: string[], dark: string[] },\n};\n\ntype AnalyticsChartPieBodyProps = {\n wrapperRef: Ref<HTMLDivElement>,\n primarySegmentSeries: readonly AnalyticsChartSeries[],\n compareSegmentSeries: readonly AnalyticsChartSeries[],\n aggregatedPrimarySegments: number[],\n aggregatedCompareSegments: number[],\n aggregatedPrimaryTotal: number,\n aggregatedCompareTotal: number,\n segmentColors: SegmentColors,\n showPrimary: boolean,\n showCompare: boolean,\n xFormatKind: FormatKind,\n yFormatKind: FormatKind,\n hoverKey: string | null,\n setHoverKey: (k: string | null) => void,\n zoomRange: [number, number] | null,\n onResetZoom: () => void,\n visibleStart: number,\n visibleEnd: number,\n fullData: Point[],\n strings: AnalyticsChartStrings,\n fmtValue: (value: number, kind: FormatKind) => string,\n pie: AnalyticsChartPieProps | undefined,\n};\n\nconst DEFAULT_PIE_CLASSNAME = \"aspect-square h-[220px] w-[220px] sm:h-[240px] sm:w-[240px]\";\n\nexport function AnalyticsChartPie({\n wrapperRef,\n primarySegmentSeries,\n compareSegmentSeries,\n aggregatedPrimarySegments,\n aggregatedCompareSegments,\n aggregatedPrimaryTotal,\n aggregatedCompareTotal,\n segmentColors,\n showPrimary,\n showCompare,\n xFormatKind,\n yFormatKind,\n hoverKey,\n setHoverKey,\n zoomRange,\n onResetZoom,\n visibleStart,\n visibleEnd,\n fullData,\n strings,\n fmtValue,\n pie,\n}: AnalyticsChartPieBodyProps) {\n const innerRadius = pie?.innerRadius ?? 60;\n const outerRadius = pie?.outerRadius ?? 84;\n const compareInnerRadius = pie?.compareInnerRadius ?? 36;\n const compareOuterRadius = pie?.compareOuterRadius ?? 52;\n const containerClassName = pie?.className ?? DEFAULT_PIE_CLASSNAME;\n\n const canonicalSeries = primarySegmentSeries.length > 0\n ? primarySegmentSeries\n : compareSegmentSeries;\n const usePrimaryForCanonical = primarySegmentSeries.length > 0;\n\n const compareValueByKey = useMemo(() => {\n const m = new Map<string, number>();\n compareSegmentSeries.forEach((s, sIdx) => {\n m.set(s.key, aggregatedCompareSegments[sIdx] ?? 0);\n });\n return m;\n }, [compareSegmentSeries, aggregatedCompareSegments]);\n const primaryValueByKey = useMemo(() => {\n const m = new Map<string, number>();\n primarySegmentSeries.forEach((s, sIdx) => {\n m.set(s.key, aggregatedPrimarySegments[sIdx] ?? 0);\n });\n return m;\n }, [primarySegmentSeries, aggregatedPrimarySegments]);\n\n const canonicalTotal = usePrimaryForCanonical\n ? aggregatedPrimaryTotal\n : aggregatedCompareTotal;\n\n const legendRows = useMemo(\n () =>\n canonicalSeries\n .map((s, sIdx) => {\n const value = usePrimaryForCanonical\n ? (aggregatedPrimarySegments[sIdx] ?? 0)\n : (aggregatedCompareSegments[sIdx] ?? 0);\n const prevValue = usePrimaryForCanonical\n ? (compareValueByKey.get(s.key) ?? 0)\n : (primaryValueByKey.get(s.key) ?? 0);\n return {\n key: s.key,\n label: s.label,\n sIdx,\n value,\n prevValue,\n pct: canonicalTotal > 0 ? value / canonicalTotal : 0,\n fill: segmentColors.primary.light[sIdx],\n fillDark: segmentColors.primary.dark[sIdx],\n fillCompare: segmentColors.compare.light[sIdx],\n fillCompareDark: segmentColors.compare.dark[sIdx],\n };\n })\n .sort((a, b) => b.value - a.value),\n [\n canonicalSeries,\n usePrimaryForCanonical,\n aggregatedPrimarySegments,\n aggregatedCompareSegments,\n compareValueByKey,\n primaryValueByKey,\n canonicalTotal,\n segmentColors,\n ],\n );\n\n const chartConfig = useMemo<DesignChartConfig>(() => {\n const config: DesignChartConfig = {};\n canonicalSeries.forEach((s, sIdx) => {\n config[cssIdent(s.key)] = {\n label: s.label,\n theme: {\n light: segmentColors.primary.light[sIdx],\n dark: segmentColors.primary.dark[sIdx],\n },\n };\n config[`compare-${cssIdent(s.key)}`] = {\n label: s.label,\n theme: {\n light: segmentColors.compare.light[sIdx],\n dark: segmentColors.compare.dark[sIdx],\n },\n };\n });\n return config;\n }, [canonicalSeries, segmentColors]);\n\n const activeRow = hoverKey\n ? legendRows.find((r) => r.key === hoverKey) ?? null\n : null;\n const activeDelta = activeRow\n ? formatDelta(activeRow.value, activeRow.prevValue)\n : formatDelta(aggregatedPrimaryTotal, aggregatedCompareTotal);\n\n const windowDays = visibleEnd - visibleStart + 1;\n const startLabel = fmtValue(fullData[visibleStart]!.ts, xFormatKind);\n const endLabel = fmtValue(fullData[visibleEnd]!.ts, xFormatKind);\n\n const outerData = legendRows.map((r) => ({ name: cssIdent(r.key), hoverKey: r.key, value: r.value, fill: r.fill }));\n const innerData = legendRows.map((r) => ({ name: cssIdent(r.key), hoverKey: r.key, value: r.prevValue, fill: r.fillCompare }));\n const activeIdx = hoverKey ? legendRows.findIndex((r) => r.key === hoverKey) : -1;\n\n return (\n <div\n ref={wrapperRef}\n className=\"relative flex h-full min-h-0 w-full min-w-0 flex-1 flex-col select-none\"\n onClick={(e) => e.stopPropagation()}\n >\n {zoomRange && (\n <div className=\"absolute right-2 top-2 z-20\">\n <button\n type=\"button\"\n onClick={onResetZoom}\n className=\"inline-flex items-center gap-1.5 rounded-full bg-blue-500/10 px-2.5 py-1 font-mono text-[10px] font-semibold uppercase tracking-wider text-blue-600 ring-1 ring-blue-500/30 transition-colors duration-150 hover:bg-blue-500/15 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50 dark:text-blue-300 dark:ring-blue-400/30\"\n >\n <MagnifyingGlassMinusIcon weight=\"bold\" className=\"size-3\" aria-hidden=\"true\" />\n <span>{strings.resetZoom}</span>\n </button>\n </div>\n )}\n\n <div className=\"flex h-full min-h-0 w-full flex-1 flex-wrap content-center items-center justify-center gap-x-10 gap-y-6\">\n <div className=\"flex shrink-0 flex-col items-center gap-3\">\n <div className=\"relative\">\n <DesignChartContainer config={chartConfig} className={containerClassName}>\n <PieChart\n role=\"img\"\n aria-label={strings.pieAriaLabel({\n segmentCount: canonicalSeries.length,\n windowDays,\n })}\n >\n {showPrimary && (\n <Pie\n data={outerData}\n dataKey=\"value\"\n nameKey=\"name\"\n cx=\"50%\"\n cy=\"50%\"\n innerRadius={innerRadius}\n outerRadius={outerRadius}\n paddingAngle={1}\n startAngle={90}\n endAngle={-270}\n isAnimationActive={false}\n stroke=\"none\"\n >\n {outerData.map((d, i) => {\n const inactive = activeIdx >= 0 && activeIdx !== i;\n return (\n <Cell\n key={`outer-${d.name}`}\n fill={`var(--color-${d.name})`}\n opacity={inactive ? 0.22 : 1}\n onMouseEnter={() => setHoverKey(d.hoverKey)}\n onMouseLeave={() => setHoverKey(null)}\n />\n );\n })}\n </Pie>\n )}\n {showCompare && (\n <Pie\n data={innerData}\n dataKey=\"value\"\n nameKey=\"name\"\n cx=\"50%\"\n cy=\"50%\"\n innerRadius={compareInnerRadius}\n outerRadius={compareOuterRadius}\n paddingAngle={1}\n startAngle={90}\n endAngle={-270}\n isAnimationActive={false}\n stroke=\"none\"\n >\n {innerData.map((d, i) => {\n const inactive = activeIdx >= 0 && activeIdx !== i;\n return (\n <Cell\n key={`inner-${d.name}`}\n fill={`var(--color-compare-${d.name})`}\n opacity={inactive ? 0.22 : 0.95}\n onMouseEnter={() => setHoverKey(d.hoverKey)}\n onMouseLeave={() => setHoverKey(null)}\n />\n );\n })}\n </Pie>\n )}\n </PieChart>\n </DesignChartContainer>\n\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"block max-w-[68px] truncate font-mono text-[9px] uppercase tracking-wider text-muted-foreground\">\n {activeRow ? activeRow.label : strings.pieTotalCenter}\n </span>\n <span className=\"mt-0.5 block max-w-[72px] truncate font-mono text-xl font-semibold leading-none tabular-nums text-foreground\">\n {fmtValue(activeRow ? activeRow.value : canonicalTotal, yFormatKind)}\n </span>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 text-center\">\n <span className=\"font-mono text-[10px] uppercase tracking-wider text-muted-foreground/80\">\n {startLabel} – {endLabel}\n </span>\n {showCompare && (\n <TrendPill delta={activeDelta} size=\"sm\" label={strings.pieVsPrev} />\n )}\n </div>\n </div>\n\n <ul className=\"flex min-w-[200px] max-w-[300px] flex-col gap-1\">\n {legendRows.map((r) => {\n const isActive = hoverKey === r.key;\n const dimmed = hoverKey != null && !isActive;\n const rowDelta = formatDelta(r.value, r.prevValue);\n return (\n <li key={r.key}>\n <button\n type=\"button\"\n onMouseEnter={() => setHoverKey(r.key)}\n onMouseLeave={() => setHoverKey(null)}\n onFocus={() => setHoverKey(r.key)}\n onBlur={() => setHoverKey(null)}\n className={cn(\n \"flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left transition-[background-color,opacity] duration-150 hover:bg-foreground/[0.04] hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/20\",\n isActive && \"bg-foreground/[0.05]\",\n dimmed && \"opacity-50\",\n )}\n >\n <span\n className=\"size-2.5 shrink-0 rounded-[3px] bg-[var(--c-l)] dark:bg-[var(--c-d)]\"\n style={{ \"--c-l\": r.fill, \"--c-d\": r.fillDark } as CSSProperties}\n />\n <span className=\"min-w-0 flex-1 truncate text-[12px] font-medium text-foreground\">\n {r.label}\n </span>\n <span className=\"shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground\">\n {(r.pct * 100).toFixed(1)}%\n </span>\n <span className=\"shrink-0 min-w-[48px] text-right font-mono text-[11px] font-semibold tabular-nums text-foreground\">\n {fmtValue(r.value, yFormatKind)}\n </span>\n {showCompare && (\n <span className=\"shrink-0\">\n <TrendPill delta={rowDelta} size=\"sm\" />\n </span>\n )}\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -2,10 +2,11 @@ import { DataGridColumnDef } from "./types.js";
2
2
  import { CSSProperties } from "react";
3
3
 
4
4
  //#region src/components/data-grid/data-grid-sizing.d.ts
5
+ declare function getEffectiveMinWidth<TRow>(col: DataGridColumnDef<TRow>): number;
5
6
  declare function getColumnSizingStyle<TRow>(col: DataGridColumnDef<TRow>): CSSProperties;
6
7
  declare function createGridSizingStyle(widths: ReadonlyMap<string, number>, totalWidth: number): Record<string, string>;
7
8
  declare function applyDraggedColumnWidth(el: HTMLElement, columnId: string, width: number, totalWidth: number): void;
8
9
  declare function clampColumnWidth<TRow>(col: DataGridColumnDef<TRow>, width: number): number;
9
10
  //#endregion
10
- export { applyDraggedColumnWidth, clampColumnWidth, createGridSizingStyle, getColumnSizingStyle };
11
+ export { applyDraggedColumnWidth, clampColumnWidth, createGridSizingStyle, getColumnSizingStyle, getEffectiveMinWidth };
11
12
  //# sourceMappingURL=data-grid-sizing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-grid-sizing.d.ts","names":[],"sources":["../../../src/components/data-grid/data-grid-sizing.ts"],"mappings":";;;;iBAWgB,oBAAA,MAAA,CAA2B,GAAA,EAAK,iBAAA,CAAkB,IAAA,IAAQ,aAAA;AAAA,iBAK1D,qBAAA,CACd,MAAA,EAAQ,WAAA,kBACR,UAAA,WACC,MAAA;AAAA,iBAQa,uBAAA,CACd,EAAA,EAAI,WAAA,EACJ,QAAA,UACA,KAAA,UACA,UAAA;AAAA,iBAMc,gBAAA,MAAA,CAAuB,GAAA,EAAK,iBAAA,CAAkB,IAAA,GAAO,KAAA"}
1
+ {"version":3,"file":"data-grid-sizing.d.ts","names":[],"sources":["../../../src/components/data-grid/data-grid-sizing.ts"],"mappings":";;;;iBAgEgB,oBAAA,MAAA,CAA2B,GAAA,EAAK,iBAAA,CAAkB,IAAA;AAAA,iBAWlD,oBAAA,MAAA,CAA2B,GAAA,EAAK,iBAAA,CAAkB,IAAA,IAAQ,aAAA;AAAA,iBAW1D,qBAAA,CACd,MAAA,EAAQ,WAAA,kBACR,UAAA,WACC,MAAA;AAAA,iBAQa,uBAAA,CACd,EAAA,EAAI,WAAA,EACJ,QAAA,UACA,KAAA,UACA,UAAA;AAAA,iBAMc,gBAAA,MAAA,CAAuB,GAAA,EAAK,iBAAA,CAAkB,IAAA,GAAO,KAAA"}
@@ -2,15 +2,41 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  //#region src/components/data-grid/data-grid-sizing.ts
4
4
  function colVar(id) {
5
+ if (process.env.NODE_ENV !== "production" && !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(id)) console.warn(`[DataGrid] column id ${JSON.stringify(id)} contains characters that are not safe in a CSS custom property name. Prefer ascii identifiers.`);
5
6
  return `--col-${id}`;
6
7
  }
8
+ const MIN_COL_WIDTH = 20;
9
+ const MIN_CUSTOM_HEADER_WIDTH = 50;
10
+ const DEFAULT_MAX_COL_WIDTH = 800;
11
+ const HEADER_CHROME_PX = 44;
12
+ let measureContext = null;
13
+ const headerWidthCache = /* @__PURE__ */ new Map();
14
+ function measureHeaderLabelWidth(label) {
15
+ const cached = headerWidthCache.get(label);
16
+ if (cached != null) return cached;
17
+ if (typeof document === "undefined") return 0;
18
+ if (measureContext == null) measureContext = document.createElement("canvas").getContext("2d");
19
+ if (measureContext == null) return 0;
20
+ measureContext.font = "600 12px system-ui, -apple-system, sans-serif";
21
+ const text = label.toUpperCase();
22
+ const width = Math.ceil(measureContext.measureText(text).width + .05 * 12 * text.length);
23
+ headerWidthCache.set(label, width);
24
+ return width;
25
+ }
26
+ function getEffectiveMinWidth(col) {
27
+ if (col.minWidth != null) return col.minWidth;
28
+ const label = typeof col.header === "string" ? col.header : null;
29
+ if (label == null) return typeof col.header === "function" ? MIN_CUSTOM_HEADER_WIDTH : MIN_COL_WIDTH;
30
+ return Math.max(MIN_COL_WIDTH, measureHeaderLabelWidth(label) + HEADER_CHROME_PX);
31
+ }
7
32
  function getColumnSizingStyle(col) {
8
33
  const w = `var(${colVar(col.id)})`;
34
+ const grow = col.flex ?? 0;
9
35
  return {
10
- flex: `0 0 ${w}`,
36
+ flex: `${grow} 0 ${w}`,
11
37
  width: w,
12
- minWidth: col.minWidth ?? 50,
13
- maxWidth: col.maxWidth ?? 800
38
+ minWidth: getEffectiveMinWidth(col),
39
+ maxWidth: grow > 0 ? void 0 : col.maxWidth ?? DEFAULT_MAX_COL_WIDTH
14
40
  };
15
41
  }
16
42
  function createGridSizingStyle(widths, totalWidth) {
@@ -23,7 +49,9 @@ function applyDraggedColumnWidth(el, columnId, width, totalWidth) {
23
49
  el.style.setProperty("--grid-total-w", `${totalWidth}px`);
24
50
  }
25
51
  function clampColumnWidth(col, width) {
26
- return Math.max(col.minWidth ?? 50, Math.min(col.maxWidth ?? 800, width));
52
+ const minWidth = getEffectiveMinWidth(col);
53
+ const maxWidth = col.maxWidth ?? DEFAULT_MAX_COL_WIDTH;
54
+ return Math.max(minWidth, Math.min(maxWidth, width));
27
55
  }
28
56
 
29
57
  //#endregion
@@ -31,4 +59,5 @@ exports.applyDraggedColumnWidth = applyDraggedColumnWidth;
31
59
  exports.clampColumnWidth = clampColumnWidth;
32
60
  exports.createGridSizingStyle = createGridSizingStyle;
33
61
  exports.getColumnSizingStyle = getColumnSizingStyle;
62
+ exports.getEffectiveMinWidth = getEffectiveMinWidth;
34
63
  //# sourceMappingURL=data-grid-sizing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-grid-sizing.js","names":[],"sources":["../../../src/components/data-grid/data-grid-sizing.ts"],"sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { DataGridColumnDef } from \"./types\";\n\n// CSS variable names for column widths set on the grid container.\n// Cells read these via var(...) so a single setProperty during drag\n// resizes every cell in a column with zero React re-renders.\n\nfunction colVar(id: string): `--col-${string}` {\n return `--col-${id}`;\n}\n\nexport function getColumnSizingStyle<TRow>(col: DataGridColumnDef<TRow>): CSSProperties {\n const w = `var(${colVar(col.id)})`;\n return { flex: `0 0 ${w}`, width: w, minWidth: col.minWidth ?? 50, maxWidth: col.maxWidth ?? 800 };\n}\n\nexport function createGridSizingStyle(\n widths: ReadonlyMap<string, number>,\n totalWidth: number,\n): Record<string, string> {\n const style: Record<string, string> = { \"--grid-total-w\": `${totalWidth}px` };\n for (const [id, w] of widths) {\n style[colVar(id)] = `${w}px`;\n }\n return style;\n}\n\nexport function applyDraggedColumnWidth(\n el: HTMLElement,\n columnId: string,\n width: number,\n totalWidth: number,\n) {\n el.style.setProperty(colVar(columnId), `${width}px`);\n el.style.setProperty(\"--grid-total-w\", `${totalWidth}px`);\n}\n\nexport function clampColumnWidth<TRow>(col: DataGridColumnDef<TRow>, width: number): number {\n return Math.max(col.minWidth ?? 50, Math.min(col.maxWidth ?? 800, width));\n}\n"],"mappings":";;;AAOA,SAAS,OAAO,IAA+B;AAC7C,QAAO,SAAS;;AAGlB,SAAgB,qBAA2B,KAA6C;CACtF,MAAM,IAAI,OAAO,OAAO,IAAI,GAAG,CAAC;AAChC,QAAO;EAAE,MAAM,OAAO;EAAK,OAAO;EAAG,UAAU,IAAI,YAAY;EAAI,UAAU,IAAI,YAAY;EAAK;;AAGpG,SAAgB,sBACd,QACA,YACwB;CACxB,MAAM,QAAgC,EAAE,kBAAkB,GAAG,WAAW,KAAK;AAC7E,MAAK,MAAM,CAAC,IAAI,MAAM,OACpB,OAAM,OAAO,GAAG,IAAI,GAAG,EAAE;AAE3B,QAAO;;AAGT,SAAgB,wBACd,IACA,UACA,OACA,YACA;AACA,IAAG,MAAM,YAAY,OAAO,SAAS,EAAE,GAAG,MAAM,IAAI;AACpD,IAAG,MAAM,YAAY,kBAAkB,GAAG,WAAW,IAAI;;AAG3D,SAAgB,iBAAuB,KAA8B,OAAuB;AAC1F,QAAO,KAAK,IAAI,IAAI,YAAY,IAAI,KAAK,IAAI,IAAI,YAAY,KAAK,MAAM,CAAC"}
1
+ {"version":3,"file":"data-grid-sizing.js","names":[],"sources":["../../../src/components/data-grid/data-grid-sizing.ts"],"sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { DataGridColumnDef } from \"./types\";\n\n// CSS variable names for column widths set on the grid container.\n// Cells read these via var(...) so a single setProperty during drag\n// resizes every cell in a column with zero React re-renders.\n\nfunction colVar(id: string): `--col-${string}` {\n // Column ids flow into CSS custom-property names. Non-ident chars would\n // break the cascade silently, so fail loud in dev. Consumers should stick\n // to stable ascii-ident ids for every column.\n if (process.env.NODE_ENV !== \"production\" && !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(id)) {\n // eslint-disable-next-line no-console\n console.warn(\n `[DataGrid] column id ${JSON.stringify(id)} contains characters that are not safe in a CSS custom property name. Prefer ascii identifiers.`\n );\n }\n return `--col-${id}`;\n}\n\n// When col.minWidth is not set, the effective minimum is derived from\n// the header label text width so the label is never clipped on resize.\n// Uses an offscreen canvas for zero-layout measurement; results are\n// cached per unique label string.\n\nconst MIN_COL_WIDTH = 20;\nconst MIN_CUSTOM_HEADER_WIDTH = 50;\n// Default upper bound on column width (both initial sizing + resize clamp).\n// Kept generous because consumers can override per-column via `col.maxWidth`.\nconst DEFAULT_MAX_COL_WIDTH = 800;\n// px-3 both sides + gap-1.5 + sort icon (h-3 w-3) + 2px rounding buffer\nconst HEADER_CHROME_PX = 12 + 12 + 6 + 12 + 2;\n\nlet measureContext: CanvasRenderingContext2D | null = null;\nconst headerWidthCache = new Map<string, number>();\n\nfunction measureHeaderLabelWidth(label: string): number {\n const cached = headerWidthCache.get(label);\n if (cached != null) {\n return cached;\n }\n\n if (typeof document === \"undefined\") {\n return 0;\n }\n if (measureContext == null) {\n measureContext = document.createElement(\"canvas\").getContext(\"2d\");\n }\n if (measureContext == null) {\n return 0;\n }\n\n // Match header cell: text-xs (12px) font-semibold (600) uppercase tracking-wider (0.05em)\n measureContext.font = \"600 12px system-ui, -apple-system, sans-serif\";\n const text = label.toUpperCase();\n const letterSpacingPx = 0.05 * 12;\n const width = Math.ceil(\n measureContext.measureText(text).width + letterSpacingPx * text.length,\n );\n\n headerWidthCache.set(label, width);\n return width;\n}\n\nexport function getEffectiveMinWidth<TRow>(col: DataGridColumnDef<TRow>): number {\n if (col.minWidth != null) {\n return col.minWidth;\n }\n const label = typeof col.header === \"string\" ? col.header : null;\n if (label == null) {\n return typeof col.header === \"function\" ? MIN_CUSTOM_HEADER_WIDTH : MIN_COL_WIDTH;\n }\n return Math.max(MIN_COL_WIDTH, measureHeaderLabelWidth(label) + HEADER_CHROME_PX);\n}\n\nexport function getColumnSizingStyle<TRow>(col: DataGridColumnDef<TRow>): CSSProperties {\n const w = `var(${colVar(col.id)})`;\n const grow = col.flex ?? 0;\n return {\n flex: `${grow} 0 ${w}`,\n width: w,\n minWidth: getEffectiveMinWidth(col),\n maxWidth: grow > 0 ? undefined : (col.maxWidth ?? DEFAULT_MAX_COL_WIDTH),\n };\n}\n\nexport function createGridSizingStyle(\n widths: ReadonlyMap<string, number>,\n totalWidth: number,\n): Record<string, string> {\n const style: Record<string, string> = { \"--grid-total-w\": `${totalWidth}px` };\n for (const [id, w] of widths) {\n style[colVar(id)] = `${w}px`;\n }\n return style;\n}\n\nexport function applyDraggedColumnWidth(\n el: HTMLElement,\n columnId: string,\n width: number,\n totalWidth: number,\n) {\n el.style.setProperty(colVar(columnId), `${width}px`);\n el.style.setProperty(\"--grid-total-w\", `${totalWidth}px`);\n}\n\nexport function clampColumnWidth<TRow>(col: DataGridColumnDef<TRow>, width: number): number {\n const minWidth = getEffectiveMinWidth(col);\n const maxWidth = col.maxWidth ?? DEFAULT_MAX_COL_WIDTH;\n return Math.max(minWidth, Math.min(maxWidth, width));\n}\n"],"mappings":";;;AAOA,SAAS,OAAO,IAA+B;AAI7C,KAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,4BAA4B,KAAK,GAAG,CAEhF,SAAQ,KACN,wBAAwB,KAAK,UAAU,GAAG,CAAC,iGAC5C;AAEH,QAAO,SAAS;;AAQlB,MAAM,gBAAgB;AACtB,MAAM,0BAA0B;AAGhC,MAAM,wBAAwB;AAE9B,MAAM,mBAAmB;AAEzB,IAAI,iBAAkD;AACtD,MAAM,mCAAmB,IAAI,KAAqB;AAElD,SAAS,wBAAwB,OAAuB;CACtD,MAAM,SAAS,iBAAiB,IAAI,MAAM;AAC1C,KAAI,UAAU,KACZ,QAAO;AAGT,KAAI,OAAO,aAAa,YACtB,QAAO;AAET,KAAI,kBAAkB,KACpB,kBAAiB,SAAS,cAAc,SAAS,CAAC,WAAW,KAAK;AAEpE,KAAI,kBAAkB,KACpB,QAAO;AAIT,gBAAe,OAAO;CACtB,MAAM,OAAO,MAAM,aAAa;CAEhC,MAAM,QAAQ,KAAK,KACjB,eAAe,YAAY,KAAK,CAAC,QAFX,MAAO,KAE8B,KAAK,OACjE;AAED,kBAAiB,IAAI,OAAO,MAAM;AAClC,QAAO;;AAGT,SAAgB,qBAA2B,KAAsC;AAC/E,KAAI,IAAI,YAAY,KAClB,QAAO,IAAI;CAEb,MAAM,QAAQ,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS;AAC5D,KAAI,SAAS,KACX,QAAO,OAAO,IAAI,WAAW,aAAa,0BAA0B;AAEtE,QAAO,KAAK,IAAI,eAAe,wBAAwB,MAAM,GAAG,iBAAiB;;AAGnF,SAAgB,qBAA2B,KAA6C;CACtF,MAAM,IAAI,OAAO,OAAO,IAAI,GAAG,CAAC;CAChC,MAAM,OAAO,IAAI,QAAQ;AACzB,QAAO;EACL,MAAM,GAAG,KAAK,KAAK;EACnB,OAAO;EACP,UAAU,qBAAqB,IAAI;EACnC,UAAU,OAAO,IAAI,SAAa,IAAI,YAAY;EACnD;;AAGH,SAAgB,sBACd,QACA,YACwB;CACxB,MAAM,QAAgC,EAAE,kBAAkB,GAAG,WAAW,KAAK;AAC7E,MAAK,MAAM,CAAC,IAAI,MAAM,OACpB,OAAM,OAAO,GAAG,IAAI,GAAG,EAAE;AAE3B,QAAO;;AAGT,SAAgB,wBACd,IACA,UACA,OACA,YACA;AACA,IAAG,MAAM,YAAY,OAAO,SAAS,EAAE,GAAG,MAAM,IAAI;AACpD,IAAG,MAAM,YAAY,kBAAkB,GAAG,WAAW,IAAI;;AAG3D,SAAgB,iBAAuB,KAA8B,OAAuB;CAC1F,MAAM,WAAW,qBAAqB,IAAI;CAC1C,MAAM,WAAW,IAAI,YAAY;AACjC,QAAO,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU,MAAM,CAAC"}
@@ -180,17 +180,21 @@ function DataGridToolbar({ ctx, extra, extraLeading, hideQuickSearch }) {
180
180
  }, [onChange]);
181
181
  const hasDateColumns = (0, react.useMemo)(() => columns.some((c) => c.type === "date" || c.type === "dateTime"), [columns]);
182
182
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
183
- className: "flex items-center gap-2 px-2.5 py-2.5 border-b border-foreground/[0.06]",
184
- children: [
185
- !hideQuickSearch && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickSearch, {
186
- value: state.quickSearch,
187
- onChange: updateQuickSearch,
188
- placeholder: strings.searchPlaceholder
189
- }),
190
- extraLeading,
191
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "flex-1 min-w-0" }),
192
- extra,
193
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
183
+ className: "flex w-full min-w-0 flex-col gap-2 px-2.5 py-2.5 border-b border-foreground/[0.06] sm:flex-row sm:items-center sm:gap-2",
184
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
185
+ className: "flex min-w-0 flex-1 flex-wrap items-center gap-2",
186
+ children: [
187
+ !hideQuickSearch && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuickSearch, {
188
+ value: state.quickSearch,
189
+ onChange: updateQuickSearch,
190
+ placeholder: strings.searchPlaceholder
191
+ }),
192
+ extraLeading,
193
+ extra
194
+ ]
195
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
196
+ className: "flex shrink-0 items-center justify-end gap-2",
197
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
194
198
  className: "relative shrink-0",
195
199
  ref: columnPopover.ref,
196
200
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
@@ -211,13 +215,12 @@ function DataGridToolbar({ ctx, extra, extraLeading, hideQuickSearch }) {
211
215
  hasDateColumns
212
216
  })
213
217
  })]
214
- }),
215
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
218
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
216
219
  onClick: exportCsv,
217
220
  title: strings.export,
218
221
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_phosphor_icons_react.DownloadSimple, { className: "h-3.5 w-3.5" })
219
- })
220
- ]
222
+ })]
223
+ })]
221
224
  });
222
225
  }
223
226
 
@@ -1 +1 @@
1
- {"version":3,"file":"data-grid-toolbar.js","names":["MagnifyingGlass","X","Eye","EyeSlash","Check","DownloadSimple"],"sources":["../../../src/components/data-grid/data-grid-toolbar.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn } from \"@stackframe/stack-ui\";\nimport {\n Check,\n DownloadSimple,\n Eye,\n EyeSlash,\n MagnifyingGlass,\n X,\n} from \"@phosphor-icons/react\";\nimport React, { useCallback, useMemo, useRef, useState } from \"react\";\nimport type {\n DataGridColumnDef,\n DataGridDateDisplay,\n DataGridStrings,\n DataGridToolbarContext,\n} from \"./types\";\n\n// ─── Popover primitive ───────────────────────────────────────────────\n\nfunction usePopover() {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const handler = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [open]);\n\n return { open, setOpen, ref };\n}\n\nfunction PopoverPanel({\n children,\n className,\n popoverRef,\n}: {\n children: React.ReactNode;\n className?: string;\n popoverRef: React.Ref<HTMLDivElement>;\n}) {\n return (\n <div\n ref={popoverRef}\n className={cn(\n \"absolute top-full left-0 mt-1 z-50\",\n \"bg-popover text-popover-foreground rounded-xl shadow-lg\",\n \"ring-1 ring-black/[0.08] dark:ring-white/[0.1]\",\n \"backdrop-blur-xl\",\n className,\n )}\n >\n {children}\n </div>\n );\n}\n\n// ─── Quick search ────────────────────────────────────────────────────\n\nfunction QuickSearch({\n value,\n onChange,\n placeholder,\n}: {\n value: string;\n onChange: (value: string) => void;\n placeholder: string;\n}) {\n return (\n <div className=\"relative flex min-w-0 flex-1 items-center sm:flex-initial\">\n <MagnifyingGlass className=\"absolute left-2.5 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none\" />\n <input\n type=\"text\"\n className={cn(\n \"h-8 w-full sm:w-52 pl-8 pr-7 rounded-xl text-xs\",\n \"bg-background\",\n \"border border-black/[0.08] dark:border-white/[0.08]\",\n \"placeholder:text-muted-foreground/40\",\n \"focus:outline-none focus:ring-1 focus:ring-foreground/[0.1]\",\n \"transition-all duration-150\",\n )}\n placeholder={placeholder}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n />\n {value && (\n <button\n className=\"absolute right-2 text-muted-foreground/40 hover:text-muted-foreground\"\n onClick={() => onChange(\"\")}\n aria-label=\"Clear search\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n )}\n </div>\n );\n}\n\n// ─── Toolbar button ──────────────────────────────────────────────────\n\nfunction ToolbarButton({\n children,\n onClick,\n active,\n title,\n className: extraClassName,\n}: {\n children: React.ReactNode;\n onClick?: () => void;\n active?: boolean;\n title?: string;\n className?: string;\n}) {\n return (\n <button\n className={cn(\n \"relative flex items-center justify-center rounded-lg text-xs font-medium\",\n \"h-7 w-7\",\n \"transition-colors duration-75\",\n active\n ? \"bg-foreground/[0.06] text-foreground\"\n : \"text-muted-foreground hover:text-foreground hover:bg-foreground/[0.04]\",\n extraClassName,\n )}\n onClick={onClick}\n title={title}\n >\n {children}\n </button>\n );\n}\n\n// ─── Column manager ──────────────────────────────────────────────────\n\nfunction ColumnManager<TRow>({\n columns,\n visibility,\n onChange,\n strings,\n dateDisplay,\n onDateDisplayChange,\n hasDateColumns,\n}: {\n columns: readonly DataGridColumnDef<TRow>[];\n visibility: Record<string, boolean>;\n onChange: (visibility: Record<string, boolean>) => void;\n strings: DataGridStrings;\n dateDisplay: DataGridDateDisplay;\n onDateDisplayChange: (mode: DataGridDateDisplay) => void;\n hasDateColumns: boolean;\n}) {\n const hideableColumns = useMemo(\n () => columns.filter((c) => c.hideable !== false),\n [columns],\n );\n\n const toggleColumn = (id: string) => {\n const current = visibility[id] !== false;\n onChange({ ...visibility, [id]: !current });\n };\n\n const showAll = () => {\n const next = { ...visibility };\n for (const col of hideableColumns) next[col.id] = true;\n onChange(next);\n };\n\n const hideAll = () => {\n const next = { ...visibility };\n for (const col of hideableColumns) next[col.id] = false;\n onChange(next);\n };\n\n return (\n <div className=\"p-2 min-w-[240px] max-w-[300px]\">\n <div className=\"max-h-[280px] overflow-y-auto space-y-0.5\">\n {hideableColumns.map((col) => {\n const visible = visibility[col.id] !== false;\n return (\n <button\n key={col.id}\n className={cn(\n \"flex items-center gap-2 w-full px-2.5 py-1.5 rounded-lg text-xs\",\n \"hover:bg-foreground/[0.06] transition-colors duration-75\",\n visible ? \"text-foreground\" : \"text-muted-foreground/50\",\n )}\n onClick={() => toggleColumn(col.id)}\n >\n {visible ? (\n <Eye className=\"h-3.5 w-3.5 flex-shrink-0 text-blue-500\" />\n ) : (\n <EyeSlash className=\"h-3.5 w-3.5 flex-shrink-0\" />\n )}\n <span className=\"truncate text-left\">\n {typeof col.header === \"string\" ? col.header : col.id}\n </span>\n {visible && <Check className=\"h-3 w-3 ml-auto flex-shrink-0 text-blue-500\" />}\n </button>\n );\n })}\n </div>\n <div className=\"flex items-center gap-2 mt-2 pt-2 border-t border-foreground/[0.06]\">\n <button className=\"text-[10px] text-muted-foreground hover:text-foreground font-medium uppercase tracking-wider transition-colors duration-75\" onClick={showAll}>\n {strings.showAll}\n </button>\n <span className=\"text-muted-foreground/20\">|</span>\n <button className=\"text-[10px] text-muted-foreground hover:text-foreground font-medium uppercase tracking-wider transition-colors duration-75\" onClick={hideAll}>\n {strings.hideAll}\n </button>\n </div>\n\n {/* Date format toggle — only rendered when at least one column\n uses `type: \"date\"` or `\"dateTime\"`. Toggling writes to\n `state.dateDisplay` and the grid re-renders every date cell. */}\n {hasDateColumns && (\n <div className=\"mt-2 pt-2 border-t border-foreground/[0.06]\">\n <div className=\"flex items-center justify-between gap-2 px-1\">\n <span className=\"text-[10px] font-medium uppercase tracking-wider text-muted-foreground\">\n {strings.dateFormat}\n </span>\n <div className=\"inline-flex items-center gap-0.5 rounded-lg bg-foreground/[0.04] p-0.5\">\n <button\n className={cn(\n \"px-2 py-0.5 rounded-md text-[11px] font-medium transition-colors duration-75\",\n dateDisplay === \"relative\"\n ? \"bg-background text-foreground shadow-sm ring-1 ring-foreground/[0.06]\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n onClick={() => onDateDisplayChange(\"relative\")}\n >\n {strings.dateFormatRelative}\n </button>\n <button\n className={cn(\n \"px-2 py-0.5 rounded-md text-[11px] font-medium transition-colors duration-75\",\n dateDisplay === \"absolute\"\n ? \"bg-background text-foreground shadow-sm ring-1 ring-foreground/[0.06]\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n onClick={() => onDateDisplayChange(\"absolute\")}\n >\n {strings.dateFormatAbsolute}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n\n// ─── Main toolbar ────────────────────────────────────────────────────\n\nexport function DataGridToolbar<TRow>({\n ctx,\n extra,\n extraLeading,\n hideQuickSearch,\n}: {\n ctx: DataGridToolbarContext<TRow>;\n /** Extra content rendered inside the toolbar row, to the left of the\n * built-in columns / export actions. Use this to add table-specific\n * affordances (refresh, custom toggles, row counts) without giving up\n * the default actions. */\n extra?: React.ReactNode;\n /** Extra content rendered at the START of the toolbar row — occupies\n * the same position as the built-in quick search (after it, if the\n * quick search is visible). Use this together with `hideQuickSearch`\n * to fully replace the quick search with a custom input, e.g. an\n * AI-powered search bar. */\n extraLeading?: React.ReactNode;\n /** Whether to hide the built-in quick-search input. When `true`,\n * callers are expected to provide their own search UI via\n * `extraLeading`. */\n hideQuickSearch?: boolean;\n}) {\n const { state, onChange, columns, strings, exportCsv } = ctx;\n\n const columnPopover = usePopover();\n\n const updateVisibility = useCallback(\n (visibility: Record<string, boolean>) => {\n onChange((s) => ({ ...s, columnVisibility: visibility }));\n },\n [onChange],\n );\n\n const updateDateDisplay = useCallback(\n (mode: DataGridDateDisplay) => {\n onChange((s) => ({ ...s, dateDisplay: mode }));\n },\n [onChange],\n );\n\n const updateQuickSearch = useCallback(\n (value: string) => {\n onChange((s) => ({\n ...s,\n quickSearch: value,\n // Reset to first page whenever the search text changes,\n // otherwise you can end up on a page index that no longer\n // exists in the filtered / refetched result set.\n pagination: { ...s.pagination, pageIndex: 0 },\n }));\n },\n [onChange],\n );\n\n const hasDateColumns = useMemo(\n () => columns.some((c) => c.type === \"date\" || c.type === \"dateTime\"),\n [columns],\n );\n\n return (\n <div className=\"flex items-center gap-2 px-2.5 py-2.5 border-b border-foreground/[0.06]\">\n {!hideQuickSearch && (\n <QuickSearch\n value={state.quickSearch}\n onChange={updateQuickSearch}\n placeholder={strings.searchPlaceholder}\n />\n )}\n {extraLeading}\n <div className=\"flex-1 min-w-0\" />\n\n {extra}\n\n <div className=\"relative shrink-0\" ref={columnPopover.ref}>\n <ToolbarButton\n onClick={() => columnPopover.setOpen(!columnPopover.open)}\n active={columnPopover.open}\n title={strings.columns}\n >\n <Eye className=\"h-3.5 w-3.5\" />\n </ToolbarButton>\n {columnPopover.open && (\n <PopoverPanel popoverRef={columnPopover.ref} className=\"right-0 left-auto\">\n <ColumnManager\n columns={columns}\n visibility={state.columnVisibility}\n onChange={updateVisibility}\n strings={strings}\n dateDisplay={state.dateDisplay}\n onDateDisplayChange={updateDateDisplay}\n hasDateColumns={hasDateColumns}\n />\n </PopoverPanel>\n )}\n </div>\n\n <ToolbarButton onClick={exportCsv} title={strings.export}>\n <DownloadSimple className=\"h-3.5 w-3.5\" />\n </ToolbarButton>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"data-grid-toolbar.js","names":["MagnifyingGlass","X","Eye","EyeSlash","Check","DownloadSimple"],"sources":["../../../src/components/data-grid/data-grid-toolbar.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn } from \"@stackframe/stack-ui\";\nimport {\n Check,\n DownloadSimple,\n Eye,\n EyeSlash,\n MagnifyingGlass,\n X,\n} from \"@phosphor-icons/react\";\nimport React, { useCallback, useMemo, useRef, useState } from \"react\";\nimport type {\n DataGridColumnDef,\n DataGridDateDisplay,\n DataGridStrings,\n DataGridToolbarContext,\n} from \"./types\";\n\n// ─── Popover primitive ───────────────────────────────────────────────\n\nfunction usePopover() {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const handler = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [open]);\n\n return { open, setOpen, ref };\n}\n\nfunction PopoverPanel({\n children,\n className,\n popoverRef,\n}: {\n children: React.ReactNode;\n className?: string;\n popoverRef: React.Ref<HTMLDivElement>;\n}) {\n return (\n <div\n ref={popoverRef}\n className={cn(\n \"absolute top-full left-0 mt-1 z-50\",\n \"bg-popover text-popover-foreground rounded-xl shadow-lg\",\n \"ring-1 ring-black/[0.08] dark:ring-white/[0.1]\",\n \"backdrop-blur-xl\",\n className,\n )}\n >\n {children}\n </div>\n );\n}\n\n// ─── Quick search ────────────────────────────────────────────────────\n\nfunction QuickSearch({\n value,\n onChange,\n placeholder,\n}: {\n value: string;\n onChange: (value: string) => void;\n placeholder: string;\n}) {\n return (\n <div className=\"relative flex min-w-0 flex-1 items-center sm:flex-initial\">\n <MagnifyingGlass className=\"absolute left-2.5 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none\" />\n <input\n type=\"text\"\n className={cn(\n \"h-8 w-full sm:w-52 pl-8 pr-7 rounded-xl text-xs\",\n \"bg-background\",\n \"border border-black/[0.08] dark:border-white/[0.08]\",\n \"placeholder:text-muted-foreground/40\",\n \"focus:outline-none focus:ring-1 focus:ring-foreground/[0.1]\",\n \"transition-all duration-150\",\n )}\n placeholder={placeholder}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n />\n {value && (\n <button\n className=\"absolute right-2 text-muted-foreground/40 hover:text-muted-foreground\"\n onClick={() => onChange(\"\")}\n aria-label=\"Clear search\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n )}\n </div>\n );\n}\n\n// ─── Toolbar button ──────────────────────────────────────────────────\n\nfunction ToolbarButton({\n children,\n onClick,\n active,\n title,\n className: extraClassName,\n}: {\n children: React.ReactNode;\n onClick?: () => void;\n active?: boolean;\n title?: string;\n className?: string;\n}) {\n return (\n <button\n className={cn(\n \"relative flex items-center justify-center rounded-lg text-xs font-medium\",\n \"h-7 w-7\",\n \"transition-colors duration-75\",\n active\n ? \"bg-foreground/[0.06] text-foreground\"\n : \"text-muted-foreground hover:text-foreground hover:bg-foreground/[0.04]\",\n extraClassName,\n )}\n onClick={onClick}\n title={title}\n >\n {children}\n </button>\n );\n}\n\n// ─── Column manager ──────────────────────────────────────────────────\n\nfunction ColumnManager<TRow>({\n columns,\n visibility,\n onChange,\n strings,\n dateDisplay,\n onDateDisplayChange,\n hasDateColumns,\n}: {\n columns: readonly DataGridColumnDef<TRow>[];\n visibility: Record<string, boolean>;\n onChange: (visibility: Record<string, boolean>) => void;\n strings: DataGridStrings;\n dateDisplay: DataGridDateDisplay;\n onDateDisplayChange: (mode: DataGridDateDisplay) => void;\n hasDateColumns: boolean;\n}) {\n const hideableColumns = useMemo(\n () => columns.filter((c) => c.hideable !== false),\n [columns],\n );\n\n const toggleColumn = (id: string) => {\n const current = visibility[id] !== false;\n onChange({ ...visibility, [id]: !current });\n };\n\n const showAll = () => {\n const next = { ...visibility };\n for (const col of hideableColumns) next[col.id] = true;\n onChange(next);\n };\n\n const hideAll = () => {\n const next = { ...visibility };\n for (const col of hideableColumns) next[col.id] = false;\n onChange(next);\n };\n\n return (\n <div className=\"p-2 min-w-[240px] max-w-[300px]\">\n <div className=\"max-h-[280px] overflow-y-auto space-y-0.5\">\n {hideableColumns.map((col) => {\n const visible = visibility[col.id] !== false;\n return (\n <button\n key={col.id}\n className={cn(\n \"flex items-center gap-2 w-full px-2.5 py-1.5 rounded-lg text-xs\",\n \"hover:bg-foreground/[0.06] transition-colors duration-75\",\n visible ? \"text-foreground\" : \"text-muted-foreground/50\",\n )}\n onClick={() => toggleColumn(col.id)}\n >\n {visible ? (\n <Eye className=\"h-3.5 w-3.5 flex-shrink-0 text-blue-500\" />\n ) : (\n <EyeSlash className=\"h-3.5 w-3.5 flex-shrink-0\" />\n )}\n <span className=\"truncate text-left\">\n {typeof col.header === \"string\" ? col.header : col.id}\n </span>\n {visible && <Check className=\"h-3 w-3 ml-auto flex-shrink-0 text-blue-500\" />}\n </button>\n );\n })}\n </div>\n <div className=\"flex items-center gap-2 mt-2 pt-2 border-t border-foreground/[0.06]\">\n <button className=\"text-[10px] text-muted-foreground hover:text-foreground font-medium uppercase tracking-wider transition-colors duration-75\" onClick={showAll}>\n {strings.showAll}\n </button>\n <span className=\"text-muted-foreground/20\">|</span>\n <button className=\"text-[10px] text-muted-foreground hover:text-foreground font-medium uppercase tracking-wider transition-colors duration-75\" onClick={hideAll}>\n {strings.hideAll}\n </button>\n </div>\n\n {/* Date format toggle — only rendered when at least one column\n uses `type: \"date\"` or `\"dateTime\"`. Toggling writes to\n `state.dateDisplay` and the grid re-renders every date cell. */}\n {hasDateColumns && (\n <div className=\"mt-2 pt-2 border-t border-foreground/[0.06]\">\n <div className=\"flex items-center justify-between gap-2 px-1\">\n <span className=\"text-[10px] font-medium uppercase tracking-wider text-muted-foreground\">\n {strings.dateFormat}\n </span>\n <div className=\"inline-flex items-center gap-0.5 rounded-lg bg-foreground/[0.04] p-0.5\">\n <button\n className={cn(\n \"px-2 py-0.5 rounded-md text-[11px] font-medium transition-colors duration-75\",\n dateDisplay === \"relative\"\n ? \"bg-background text-foreground shadow-sm ring-1 ring-foreground/[0.06]\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n onClick={() => onDateDisplayChange(\"relative\")}\n >\n {strings.dateFormatRelative}\n </button>\n <button\n className={cn(\n \"px-2 py-0.5 rounded-md text-[11px] font-medium transition-colors duration-75\",\n dateDisplay === \"absolute\"\n ? \"bg-background text-foreground shadow-sm ring-1 ring-foreground/[0.06]\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n onClick={() => onDateDisplayChange(\"absolute\")}\n >\n {strings.dateFormatAbsolute}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n\n// ─── Main toolbar ────────────────────────────────────────────────────\n\nexport function DataGridToolbar<TRow>({\n ctx,\n extra,\n extraLeading,\n hideQuickSearch,\n}: {\n ctx: DataGridToolbarContext<TRow>;\n /** Extra content rendered inside the toolbar row, to the left of the\n * built-in columns / export actions. Use this to add table-specific\n * affordances (refresh, custom toggles, row counts) without giving up\n * the default actions. */\n extra?: React.ReactNode;\n /** Extra content rendered at the START of the toolbar row — occupies\n * the same position as the built-in quick search (after it, if the\n * quick search is visible). Use this together with `hideQuickSearch`\n * to fully replace the quick search with a custom input, e.g. an\n * AI-powered search bar. */\n extraLeading?: React.ReactNode;\n /** Whether to hide the built-in quick-search input. When `true`,\n * callers are expected to provide their own search UI via\n * `extraLeading`. */\n hideQuickSearch?: boolean;\n}) {\n const { state, onChange, columns, strings, exportCsv } = ctx;\n\n const columnPopover = usePopover();\n\n const updateVisibility = useCallback(\n (visibility: Record<string, boolean>) => {\n onChange((s) => ({ ...s, columnVisibility: visibility }));\n },\n [onChange],\n );\n\n const updateDateDisplay = useCallback(\n (mode: DataGridDateDisplay) => {\n onChange((s) => ({ ...s, dateDisplay: mode }));\n },\n [onChange],\n );\n\n const updateQuickSearch = useCallback(\n (value: string) => {\n onChange((s) => ({\n ...s,\n quickSearch: value,\n // Reset to first page whenever the search text changes,\n // otherwise you can end up on a page index that no longer\n // exists in the filtered / refetched result set.\n pagination: { ...s.pagination, pageIndex: 0 },\n }));\n },\n [onChange],\n );\n\n const hasDateColumns = useMemo(\n () => columns.some((c) => c.type === \"date\" || c.type === \"dateTime\"),\n [columns],\n );\n\n return (\n <div className=\"flex w-full min-w-0 flex-col gap-2 px-2.5 py-2.5 border-b border-foreground/[0.06] sm:flex-row sm:items-center sm:gap-2\">\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\">\n {!hideQuickSearch && (\n <QuickSearch\n value={state.quickSearch}\n onChange={updateQuickSearch}\n placeholder={strings.searchPlaceholder}\n />\n )}\n {extraLeading}\n {extra}\n </div>\n <div className=\"flex shrink-0 items-center justify-end gap-2\">\n <div className=\"relative shrink-0\" ref={columnPopover.ref}>\n <ToolbarButton\n onClick={() => columnPopover.setOpen(!columnPopover.open)}\n active={columnPopover.open}\n title={strings.columns}\n >\n <Eye className=\"h-3.5 w-3.5\" />\n </ToolbarButton>\n {columnPopover.open && (\n <PopoverPanel popoverRef={columnPopover.ref} className=\"right-0 left-auto\">\n <ColumnManager\n columns={columns}\n visibility={state.columnVisibility}\n onChange={updateVisibility}\n strings={strings}\n dateDisplay={state.dateDisplay}\n onDateDisplayChange={updateDateDisplay}\n hasDateColumns={hasDateColumns}\n />\n </PopoverPanel>\n )}\n </div>\n\n <ToolbarButton onClick={exportCsv} title={strings.export}>\n <DownloadSimple className=\"h-3.5 w-3.5\" />\n </ToolbarButton>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -2,6 +2,7 @@ import { DataGridProps } from "./types.js";
2
2
  import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/components/data-grid/data-grid.d.ts
5
+ declare function isDataGridInteractiveRowClickTarget(target: EventTarget | null): boolean;
5
6
  /**
6
7
  * Interactive table with sorting, quick search, pagination, selection,
7
8
  * and virtualization. Handles 10k+ rows smoothly. Pair with
@@ -188,6 +189,39 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
188
189
  * and footer all call it for you. You do not need to wire any of this
189
190
  * manually.
190
191
  *
192
+ * ## Cell overflow and dynamic row heights
193
+ *
194
+ * By default every cell truncates its content with an ellipsis
195
+ * (`cellOverflow: "truncate"`). For columns whose content should wrap
196
+ * — badge lists, multi-line text, permission chips — set
197
+ * `cellOverflow: "wrap"` on the column definition.
198
+ *
199
+ * To let rows grow to fit their tallest cell, set `rowHeight="auto"`
200
+ * on the grid. The virtualizer will measure each row after render and
201
+ * adjust scroll positions accordingly. Pair with `estimatedRowHeight`
202
+ * (default 44) for better scroll-position estimates before measurement.
203
+ *
204
+ * ```tsx
205
+ * // Columns: UUIDs truncate, auth-method badges wrap
206
+ * const columns = [
207
+ * { id: "userId", header: "User ID", width: 130 }, // default truncate
208
+ * { id: "auth", header: "Auth methods", width: 150, cellOverflow: "wrap",
209
+ * renderCell: ({ row }) => (
210
+ * <div className="flex flex-wrap gap-1">
211
+ * {row.authTypes.map((t) => <Badge key={t}>{t}</Badge>)}
212
+ * </div>
213
+ * ),
214
+ * },
215
+ * ];
216
+ *
217
+ * <DataGrid columns={columns} rowHeight="auto" estimatedRowHeight={48} ... />
218
+ * ```
219
+ *
220
+ * With a fixed numeric `rowHeight` (the default), `cellOverflow: "wrap"`
221
+ * still lets content wrap within the row, but anything exceeding the
222
+ * fixed height is clipped. This is useful when you want controlled
223
+ * wrapping without variable row heights.
224
+ *
191
225
  * ## Height and scrolling
192
226
  *
193
227
  * DataGrid is NOT a card. It has no border, rounded corners, or shadow of
@@ -229,5 +263,5 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
229
263
  */
230
264
  declare function DataGrid<TRow>(props: DataGridProps<TRow>): react_jsx_runtime0.JSX.Element;
231
265
  //#endregion
232
- export { DataGrid };
266
+ export { DataGrid, isDataGridInteractiveRowClickTarget };
233
267
  //# sourceMappingURL=data-grid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-grid.d.ts","names":[],"sources":["../../../src/components/data-grid/data-grid.tsx"],"mappings":";;;;;;;AA2wBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,QAAA,MAAA,CAAe,KAAA,EAAO,aAAA,CAAc,IAAA,IAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"data-grid.d.ts","names":[],"sources":["../../../src/components/data-grid/data-grid.tsx"],"mappings":";;;;iBAsKgB,mCAAA,CAAoC,MAAA,EAAQ,WAAA;;;AAA5D;;;;;AA+sBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,QAAA,MAAA,CAAe,KAAA,EAAO,aAAA,CAAc,IAAA,IAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}