@alpic-ai/ui 0.0.0-staging.g4a0bec2 → 0.0.0-staging.g4d42ca0
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.
- package/dist/components/bar-chart.d.mts +2 -0
- package/dist/components/bar-chart.mjs +39 -5
- package/dist/components/heatmap-chart.d.mts +6 -0
- package/dist/components/heatmap-chart.mjs +146 -116
- package/dist/components/stat.mjs +2 -2
- package/dist/components/table.d.mts +5 -1
- package/dist/components/table.mjs +2 -2
- package/dist/lib/chart-palette.mjs +4 -1
- package/package.json +26 -26
- package/src/components/bar-chart.tsx +36 -0
- package/src/components/heatmap-chart.tsx +49 -5
- package/src/components/stat.tsx +2 -2
- package/src/components/table.tsx +11 -2
- package/src/lib/chart-palette.ts +4 -1
|
@@ -22,6 +22,7 @@ interface BarChartProps {
|
|
|
22
22
|
};
|
|
23
23
|
markers?: ChartMarker[];
|
|
24
24
|
texture?: boolean;
|
|
25
|
+
partialLastBar?: boolean;
|
|
25
26
|
loading?: boolean;
|
|
26
27
|
valueFormatter?: (value: number) => string;
|
|
27
28
|
labelFormatter?: (label: string | number) => string;
|
|
@@ -41,6 +42,7 @@ declare function BarChart({
|
|
|
41
42
|
referenceLine,
|
|
42
43
|
markers,
|
|
43
44
|
texture,
|
|
45
|
+
partialLastBar,
|
|
44
46
|
loading,
|
|
45
47
|
valueFormatter,
|
|
46
48
|
labelFormatter,
|
|
@@ -7,11 +7,11 @@ import { ChartTooltipContent } from "./chart-tooltip.mjs";
|
|
|
7
7
|
import { Skeleton } from "./skeleton.mjs";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import * as React$1 from "react";
|
|
10
|
-
import { Bar, BarChart as BarChart$1, CartesianGrid, LabelList, ReferenceArea, ReferenceDot, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
|
10
|
+
import { Bar, BarChart as BarChart$1, CartesianGrid, Cell, LabelList, ReferenceArea, ReferenceDot, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
|
11
11
|
//#region src/components/bar-chart.tsx
|
|
12
12
|
const BAR_RADIUS = 4;
|
|
13
13
|
const MAX_BAR_SIZE = 48;
|
|
14
|
-
function BarChart({ data, index, series, variant = "stacked", legend = false, legendAlign = "left", valueLabels = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, texture = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
14
|
+
function BarChart({ data, index, series, variant = "stacked", legend = false, legendAlign = "left", valueLabels = false, height = 200, yAxisWidth = 48, palette, referenceLine, markers, texture = false, partialLastBar = false, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
|
|
15
15
|
const { palette: paletteColors, theme } = useChartContext(palette);
|
|
16
16
|
const reactId = React$1.useId().replace(/:/g, "");
|
|
17
17
|
const resolved = resolveSeries(series, paletteColors, theme);
|
|
@@ -38,6 +38,20 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
38
38
|
series,
|
|
39
39
|
stacked
|
|
40
40
|
]);
|
|
41
|
+
const topSlotByRow = React$1.useMemo(() => {
|
|
42
|
+
if (!stacked) return [];
|
|
43
|
+
return data.map((row) => {
|
|
44
|
+
for (let slot = rendered.length - 1; slot >= 0; slot -= 1) {
|
|
45
|
+
const value = Number(row[rendered[slot]?.key ?? ""]);
|
|
46
|
+
if (Number.isFinite(value) && value > 0) return slot;
|
|
47
|
+
}
|
|
48
|
+
return -1;
|
|
49
|
+
});
|
|
50
|
+
}, [
|
|
51
|
+
data,
|
|
52
|
+
rendered,
|
|
53
|
+
stacked
|
|
54
|
+
]);
|
|
41
55
|
const margin = {
|
|
42
56
|
top: markers?.length || valueLabels ? 18 : 8,
|
|
43
57
|
right: 8,
|
|
@@ -207,7 +221,7 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
207
221
|
position: "insideBottomRight"
|
|
208
222
|
} : void 0
|
|
209
223
|
}),
|
|
210
|
-
rendered.map((entry, slot) => /* @__PURE__ */
|
|
224
|
+
rendered.map((entry, slot) => /* @__PURE__ */ jsxs(Bar, {
|
|
211
225
|
dataKey: entry.key,
|
|
212
226
|
name: entry.name,
|
|
213
227
|
stackId: stacked ? "stack" : void 0,
|
|
@@ -220,10 +234,30 @@ function BarChart({ data, index, series, variant = "stacked", legend = false, le
|
|
|
220
234
|
isAnimationActive: false,
|
|
221
235
|
animationDuration: 650,
|
|
222
236
|
animationEasing: "ease-out",
|
|
223
|
-
children:
|
|
237
|
+
children: [(stacked || partialLastBar) && data.map((row, rowIndex) => {
|
|
238
|
+
const roundedTop = stacked ? topSlotByRow[rowIndex] === slot : true;
|
|
239
|
+
const isPartial = partialLastBar && rowIndex === data.length - 1;
|
|
240
|
+
return /* @__PURE__ */ jsx(Cell, {
|
|
241
|
+
radius: roundedTop ? [
|
|
242
|
+
BAR_RADIUS,
|
|
243
|
+
BAR_RADIUS,
|
|
244
|
+
0,
|
|
245
|
+
0
|
|
246
|
+
] : [
|
|
247
|
+
0,
|
|
248
|
+
0,
|
|
249
|
+
0,
|
|
250
|
+
0
|
|
251
|
+
],
|
|
252
|
+
fillOpacity: isPartial ? .15 : 1,
|
|
253
|
+
stroke: isPartial ? entry.color : void 0,
|
|
254
|
+
strokeWidth: isPartial ? 1.25 : 0,
|
|
255
|
+
strokeDasharray: isPartial ? "3 2" : void 0
|
|
256
|
+
}, `${entry.key}-${row[index] ?? rowIndex}`);
|
|
257
|
+
}), valueLabels && (!stacked || rendered.length === 1) && /* @__PURE__ */ jsx(LabelList, {
|
|
224
258
|
dataKey: entry.key,
|
|
225
259
|
content: renderValueLabel(entry.color)
|
|
226
|
-
})
|
|
260
|
+
})]
|
|
227
261
|
}, entry.key)),
|
|
228
262
|
markers?.map((marker) => /* @__PURE__ */ jsx(ReferenceDot, {
|
|
229
263
|
x: marker.x,
|
|
@@ -22,6 +22,9 @@ interface HeatmapChartProps {
|
|
|
22
22
|
label: string;
|
|
23
23
|
format?: (value: number) => string;
|
|
24
24
|
}>;
|
|
25
|
+
tooltipTitleKey?: string;
|
|
26
|
+
colorScale?: "linear" | "rank";
|
|
27
|
+
showLegend?: boolean;
|
|
25
28
|
ariaLabel?: string;
|
|
26
29
|
className?: string;
|
|
27
30
|
}
|
|
@@ -41,6 +44,9 @@ declare function HeatmapChart({
|
|
|
41
44
|
xTickFormatter,
|
|
42
45
|
yTickFormatter,
|
|
43
46
|
tooltipMetrics,
|
|
47
|
+
tooltipTitleKey,
|
|
48
|
+
colorScale,
|
|
49
|
+
showLegend,
|
|
44
50
|
ariaLabel,
|
|
45
51
|
className
|
|
46
52
|
}: HeatmapChartProps): React$1.JSX.Element;
|
|
@@ -31,7 +31,7 @@ const uniqueInOrder = (values) => {
|
|
|
31
31
|
}
|
|
32
32
|
return ordered;
|
|
33
33
|
};
|
|
34
|
-
function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square", palette, xLabels, yLabels, showAllXLabels = false, highlightPeak = true, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), xTickFormatter, yTickFormatter, tooltipMetrics, ariaLabel = "Activity heatmap", className }) {
|
|
34
|
+
function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square", palette, xLabels, yLabels, showAllXLabels = false, highlightPeak = true, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), xTickFormatter, yTickFormatter, tooltipMetrics, tooltipTitleKey, colorScale = "linear", showLegend = false, ariaLabel = "Activity heatmap", className }) {
|
|
35
35
|
const { paletteName, theme } = useChartContext(palette);
|
|
36
36
|
const [hovered, setHovered] = React$1.useState(null);
|
|
37
37
|
const [mounted, setMounted] = React$1.useState(false);
|
|
@@ -81,6 +81,19 @@ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square",
|
|
|
81
81
|
yLabels
|
|
82
82
|
]);
|
|
83
83
|
const isEmpty = columns.length === 0 || rows.length === 0 || maxValue <= 0;
|
|
84
|
+
const colorFractionOf = React$1.useMemo(() => {
|
|
85
|
+
if (colorScale === "rank") {
|
|
86
|
+
const sorted = [...new Set(cells.map((cell) => cell.value).filter((value) => value > 0))].sort((lower, upper) => lower - upper);
|
|
87
|
+
const RANK_FLOOR = .18;
|
|
88
|
+
const fractionByValue = new Map(sorted.map((value, index) => [value, sorted.length > 1 ? RANK_FLOOR + (1 - RANK_FLOOR) * (index / (sorted.length - 1)) : 1]));
|
|
89
|
+
return (value) => value > 0 ? fractionByValue.get(value) ?? 0 : 0;
|
|
90
|
+
}
|
|
91
|
+
return (value) => maxValue > 0 ? value / maxValue : 0;
|
|
92
|
+
}, [
|
|
93
|
+
cells,
|
|
94
|
+
colorScale,
|
|
95
|
+
maxValue
|
|
96
|
+
]);
|
|
84
97
|
if (loading) return /* @__PURE__ */ jsxs("div", {
|
|
85
98
|
className: cn("flex items-center justify-center gap-2.5 font-mono text-quaternary-foreground text-text-xs", className),
|
|
86
99
|
style: { minHeight: PLACEHOLDER_HEIGHT },
|
|
@@ -91,7 +104,7 @@ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square",
|
|
|
91
104
|
style: { minHeight: PLACEHOLDER_HEIGHT },
|
|
92
105
|
children: "no data in range"
|
|
93
106
|
});
|
|
94
|
-
const ramp = heatRamp(paletteName, theme.isDark ? HEAT_EMPTY.dark : HEAT_EMPTY.light);
|
|
107
|
+
const ramp = heatRamp(paletteName, theme.isDark ? HEAT_EMPTY.dark : HEAT_EMPTY.light, theme.isDark);
|
|
95
108
|
const totalWidth = PAD.left + columns.length * CELL + (columns.length - 1) * GAP + PAD.right;
|
|
96
109
|
const totalHeight = PAD.top + rows.length * CELL + (rows.length - 1) * GAP + PAD.bottom;
|
|
97
110
|
const xStride = showAllXLabels ? 1 : Math.ceil(columns.length / MAX_X_TICKS);
|
|
@@ -104,125 +117,142 @@ function HeatmapChart({ data, xKey, yKey, dataKey = "value", variant = "square",
|
|
|
104
117
|
return /* @__PURE__ */ jsxs("div", {
|
|
105
118
|
"data-slot": "heatmap-chart",
|
|
106
119
|
className: cn("w-full", className),
|
|
107
|
-
children: [
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ jsx("div", {
|
|
122
|
+
className: "overflow-x-auto",
|
|
123
|
+
children: /* @__PURE__ */ jsxs("svg", {
|
|
124
|
+
viewBox: `0 0 ${totalWidth} ${totalHeight}`,
|
|
125
|
+
className: "block min-w-[480px]",
|
|
126
|
+
style: {
|
|
127
|
+
width: "100%",
|
|
128
|
+
height: "auto"
|
|
129
|
+
},
|
|
130
|
+
role: "img",
|
|
131
|
+
"aria-label": ariaLabel,
|
|
132
|
+
onMouseLeave: () => setHovered(null),
|
|
133
|
+
children: [
|
|
134
|
+
columns.map((label, col) => col % xStride === 0 ? /* @__PURE__ */ jsx("text", {
|
|
135
|
+
x: cellX(col) + CELL / 2,
|
|
136
|
+
y: PAD.top - 7,
|
|
137
|
+
textAnchor: "middle",
|
|
138
|
+
className: "fill-quaternary-foreground font-mono text-[8.5px]",
|
|
139
|
+
children: formatX(label)
|
|
140
|
+
}, `x-${label}`) : null),
|
|
141
|
+
rows.map((label, row) => /* @__PURE__ */ jsx("text", {
|
|
142
|
+
x: PAD.left - 8,
|
|
143
|
+
y: cellY(row) + CELL / 2 + 3,
|
|
144
|
+
textAnchor: "end",
|
|
145
|
+
className: "fill-quaternary-foreground font-mono text-[8.5px]",
|
|
146
|
+
children: formatY(label)
|
|
147
|
+
}, `y-${label}`)),
|
|
148
|
+
cells.map((cell) => {
|
|
149
|
+
const fraction = colorFractionOf(cell.value);
|
|
150
|
+
const fill = heatColor(ramp, fraction);
|
|
151
|
+
const key = pairKey(cell.rowLabel, cell.colLabel);
|
|
152
|
+
const isPeak = highlightPeak && key === peakKey;
|
|
153
|
+
if (variant === "dot") return /* @__PURE__ */ jsx("circle", {
|
|
154
|
+
cx: cellX(cell.colIndex) + CELL / 2,
|
|
155
|
+
cy: cellY(cell.rowIndex) + CELL / 2,
|
|
156
|
+
r: CELL / 2 * (DOT_MIN + DOT_RANGE * fraction),
|
|
157
|
+
fill,
|
|
158
|
+
stroke: isPeak ? theme.foreground : void 0,
|
|
159
|
+
strokeWidth: isPeak ? 1.4 : void 0
|
|
160
|
+
}, key);
|
|
161
|
+
return /* @__PURE__ */ jsx("rect", {
|
|
162
|
+
x: cellX(cell.colIndex),
|
|
163
|
+
y: cellY(cell.rowIndex),
|
|
164
|
+
width: CELL,
|
|
165
|
+
height: CELL,
|
|
166
|
+
rx: 2.5,
|
|
167
|
+
fill,
|
|
168
|
+
stroke: isPeak ? theme.foreground : void 0,
|
|
169
|
+
strokeWidth: isPeak ? 1.4 : void 0
|
|
170
|
+
}, key);
|
|
171
|
+
}),
|
|
172
|
+
cells.map((cell) => /* @__PURE__ */ jsx("rect", {
|
|
148
173
|
x: cellX(cell.colIndex),
|
|
149
174
|
y: cellY(cell.rowIndex),
|
|
150
|
-
width:
|
|
151
|
-
height:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
fill: "transparent",
|
|
164
|
-
onMouseMove: (event) => setHovered({
|
|
165
|
-
x: event.clientX,
|
|
166
|
-
y: event.clientY,
|
|
167
|
-
rowLabel: cell.rowLabel,
|
|
168
|
-
colLabel: cell.colLabel,
|
|
169
|
-
value: cell.value,
|
|
170
|
-
record: cell.record
|
|
171
|
-
})
|
|
172
|
-
}, `hit-${pairKey(cell.rowLabel, cell.colLabel)}`))
|
|
173
|
-
]
|
|
174
|
-
})
|
|
175
|
-
}), mounted && hovered && createPortal(/* @__PURE__ */ jsxs("div", {
|
|
176
|
-
className: "pointer-events-none fixed z-50 min-w-[130px] rounded-lg border border-border bg-popover px-3 py-2.5 text-popover-foreground shadow-lg",
|
|
177
|
-
style: {
|
|
178
|
-
left: tooltipLeft,
|
|
179
|
-
top: tooltipTop
|
|
180
|
-
},
|
|
181
|
-
children: [/* @__PURE__ */ jsxs("p", {
|
|
182
|
-
className: "mb-1.5 font-mono text-[10px] text-quaternary-foreground uppercase tracking-wider",
|
|
183
|
-
children: [
|
|
184
|
-
formatY(hovered.rowLabel),
|
|
185
|
-
" · ",
|
|
186
|
-
formatX(hovered.colLabel)
|
|
187
|
-
]
|
|
188
|
-
}), tooltipMetrics && tooltipMetrics.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
189
|
-
className: "flex flex-col gap-1",
|
|
190
|
-
children: tooltipMetrics.map((metric) => {
|
|
191
|
-
const raw = hovered.record ? Number(hovered.record[metric.key]) || 0 : 0;
|
|
192
|
-
const isActive = metric.key === dataKey;
|
|
193
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
194
|
-
className: "flex items-center justify-between gap-4 text-text-xs",
|
|
195
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
196
|
-
className: "inline-flex items-center gap-2 text-muted-foreground",
|
|
197
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
198
|
-
"aria-hidden": true,
|
|
199
|
-
className: "size-2 shrink-0 rounded-[2px]",
|
|
200
|
-
style: {
|
|
201
|
-
background: isActive ? heatColor(ramp, hovered.value / maxValue) : theme.mutedForeground,
|
|
202
|
-
opacity: isActive ? 1 : .35
|
|
203
|
-
}
|
|
204
|
-
}), metric.label]
|
|
205
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
206
|
-
className: cn("font-mono tabular-nums", isActive ? "font-semibold text-foreground" : "text-muted-foreground"),
|
|
207
|
-
children: (metric.format ?? valueFormatter)(raw)
|
|
208
|
-
})]
|
|
209
|
-
}, metric.key);
|
|
175
|
+
width: 25,
|
|
176
|
+
height: 25,
|
|
177
|
+
fill: "transparent",
|
|
178
|
+
onMouseMove: (event) => setHovered({
|
|
179
|
+
x: event.clientX,
|
|
180
|
+
y: event.clientY,
|
|
181
|
+
rowLabel: cell.rowLabel,
|
|
182
|
+
colLabel: cell.colLabel,
|
|
183
|
+
value: cell.value,
|
|
184
|
+
record: cell.record
|
|
185
|
+
})
|
|
186
|
+
}, `hit-${pairKey(cell.rowLabel, cell.colLabel)}`))
|
|
187
|
+
]
|
|
210
188
|
})
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
189
|
+
}),
|
|
190
|
+
showLegend && /* @__PURE__ */ jsxs("div", {
|
|
191
|
+
className: "mt-3 flex items-center justify-end gap-2",
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ jsx("span", {
|
|
194
|
+
className: "font-mono text-[9px] text-quaternary-foreground uppercase tracking-[0.14em]",
|
|
195
|
+
children: "Less"
|
|
196
|
+
}),
|
|
197
|
+
/* @__PURE__ */ jsx("span", {
|
|
216
198
|
"aria-hidden": true,
|
|
217
|
-
className: "
|
|
218
|
-
style: { background:
|
|
219
|
-
}),
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
199
|
+
className: "h-2 w-24 rounded-full border border-border",
|
|
200
|
+
style: { background: `linear-gradient(90deg, ${ramp.join(", ")})` }
|
|
201
|
+
}),
|
|
202
|
+
/* @__PURE__ */ jsx("span", {
|
|
203
|
+
className: "font-mono text-[9px] text-quaternary-foreground uppercase tracking-[0.14em]",
|
|
204
|
+
children: "More"
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
}),
|
|
208
|
+
mounted && hovered && createPortal(/* @__PURE__ */ jsxs("div", {
|
|
209
|
+
className: "pointer-events-none fixed z-50 min-w-[130px] rounded-lg border border-border bg-popover px-3 py-2.5 text-popover-foreground shadow-lg",
|
|
210
|
+
style: {
|
|
211
|
+
left: tooltipLeft,
|
|
212
|
+
top: tooltipTop
|
|
213
|
+
},
|
|
214
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
215
|
+
className: "mb-1.5 font-mono text-[10px] text-quaternary-foreground uppercase tracking-wider",
|
|
216
|
+
children: tooltipTitleKey && typeof hovered.record?.[tooltipTitleKey] === "string" ? hovered.record[tooltipTitleKey] : `${formatY(hovered.rowLabel)} · ${formatX(hovered.colLabel)}`
|
|
217
|
+
}), tooltipMetrics && tooltipMetrics.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
218
|
+
className: "flex flex-col gap-1",
|
|
219
|
+
children: tooltipMetrics.map((metric) => {
|
|
220
|
+
const raw = hovered.record ? Number(hovered.record[metric.key]) || 0 : 0;
|
|
221
|
+
const isActive = metric.key === dataKey;
|
|
222
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
223
|
+
className: "flex items-center justify-between gap-4 text-text-xs",
|
|
224
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
225
|
+
className: "inline-flex items-center gap-2 text-muted-foreground",
|
|
226
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
227
|
+
"aria-hidden": true,
|
|
228
|
+
className: "size-2 shrink-0 rounded-[2px]",
|
|
229
|
+
style: {
|
|
230
|
+
background: isActive ? heatColor(ramp, colorFractionOf(hovered.value)) : theme.mutedForeground,
|
|
231
|
+
opacity: isActive ? 1 : .35
|
|
232
|
+
}
|
|
233
|
+
}), metric.label]
|
|
234
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
235
|
+
className: cn("font-mono tabular-nums", isActive ? "font-semibold text-foreground" : "text-muted-foreground"),
|
|
236
|
+
children: (metric.format ?? valueFormatter)(raw)
|
|
237
|
+
})]
|
|
238
|
+
}, metric.key);
|
|
239
|
+
})
|
|
240
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
241
|
+
className: "flex items-center justify-between gap-4 text-text-xs",
|
|
242
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
243
|
+
className: "inline-flex items-center gap-2 text-muted-foreground",
|
|
244
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
245
|
+
"aria-hidden": true,
|
|
246
|
+
className: "size-2 shrink-0 rounded-[2px]",
|
|
247
|
+
style: { background: heatColor(ramp, colorFractionOf(hovered.value)) }
|
|
248
|
+
}), "activity"]
|
|
249
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
250
|
+
className: "font-mono font-semibold tabular-nums text-foreground",
|
|
251
|
+
children: valueFormatter(hovered.value)
|
|
252
|
+
})]
|
|
223
253
|
})]
|
|
224
|
-
})
|
|
225
|
-
|
|
254
|
+
}), document.body)
|
|
255
|
+
]
|
|
226
256
|
});
|
|
227
257
|
}
|
|
228
258
|
//#endregion
|
package/dist/components/stat.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { cva } from "class-variance-authority";
|
|
|
7
7
|
import * as React$1 from "react";
|
|
8
8
|
import { Area, AreaChart, ResponsiveContainer } from "recharts";
|
|
9
9
|
//#region src/components/stat.tsx
|
|
10
|
-
const statDeltaVariants = cva("inline-flex items-center gap-0.5 rounded-md px-
|
|
10
|
+
const statDeltaVariants = cva("inline-flex items-center gap-0.5 rounded-md px-2 py-1 font-mono text-xs font-medium leading-none", {
|
|
11
11
|
variants: { sentiment: {
|
|
12
12
|
positive: "text-success bg-success/12",
|
|
13
13
|
negative: "text-destructive bg-destructive/12"
|
|
@@ -51,7 +51,7 @@ function Stat({ value, unit, delta, sparkline, semantic, className, ...props })
|
|
|
51
51
|
delta && /* @__PURE__ */ jsxs(DeltaPill, {
|
|
52
52
|
sentiment,
|
|
53
53
|
className: "mb-0.5",
|
|
54
|
-
children: [delta.direction === "up" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "size-3" }), delta.label ?? `${delta.value}%`]
|
|
54
|
+
children: [delta.direction === "up" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "size-3.5" }), delta.label ?? `${delta.value}%`]
|
|
55
55
|
})
|
|
56
56
|
]
|
|
57
57
|
}), hasSpark && /* @__PURE__ */ jsx("div", {
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import * as React$1 from "react";
|
|
2
2
|
|
|
3
3
|
//#region src/components/table.d.ts
|
|
4
|
+
interface TableProps extends React$1.ComponentProps<"table"> {
|
|
5
|
+
containerClassName?: string;
|
|
6
|
+
}
|
|
4
7
|
declare function Table({
|
|
5
8
|
className,
|
|
9
|
+
containerClassName,
|
|
6
10
|
...props
|
|
7
|
-
}:
|
|
11
|
+
}: TableProps): React$1.JSX.Element;
|
|
8
12
|
declare function TableHeader({
|
|
9
13
|
className,
|
|
10
14
|
...props
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { cn } from "../lib/cn.mjs";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/components/table.tsx
|
|
5
|
-
function Table({ className, ...props }) {
|
|
5
|
+
function Table({ className, containerClassName, ...props }) {
|
|
6
6
|
return /* @__PURE__ */ jsx("div", {
|
|
7
|
-
className: "relative w-full overflow-auto rounded-xl border border-border-secondary bg-background",
|
|
7
|
+
className: cn("relative w-full overflow-auto rounded-xl border border-border-secondary bg-background", containerClassName),
|
|
8
8
|
children: /* @__PURE__ */ jsx("table", {
|
|
9
9
|
"data-slot": "table",
|
|
10
10
|
className: cn("w-full caption-bottom type-text-sm", className),
|
|
@@ -42,7 +42,10 @@ const HEAT_RAMPS = {
|
|
|
42
42
|
magenta: heatRampMagenta,
|
|
43
43
|
cyan: heatRampMint
|
|
44
44
|
};
|
|
45
|
-
const heatRamp = (palette, empty) =>
|
|
45
|
+
const heatRamp = (palette, empty, isDark = true) => {
|
|
46
|
+
const [, ...colored] = HEAT_RAMPS[palette](empty);
|
|
47
|
+
return [empty, ...isDark ? colored : colored.reverse()];
|
|
48
|
+
};
|
|
46
49
|
const hexToRgb = (hex) => {
|
|
47
50
|
const value = Number.parseInt(hex.slice(1), 16);
|
|
48
51
|
return [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpic-ai/ui",
|
|
3
|
-
"version": "0.0.0-staging.
|
|
3
|
+
"version": "0.0.0-staging.g4d42ca0",
|
|
4
4
|
"description": "Alpic design system — shared UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -23,48 +23,48 @@
|
|
|
23
23
|
"src"
|
|
24
24
|
],
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"lucide-react": "^1.
|
|
26
|
+
"lucide-react": "^1.23.0",
|
|
27
27
|
"react": "^19.2.7",
|
|
28
28
|
"react-dom": "^19.2.7",
|
|
29
|
-
"react-hook-form": "^7.
|
|
29
|
+
"react-hook-form": "^7.81.0",
|
|
30
30
|
"sonner": "^2.0.7",
|
|
31
|
-
"tailwindcss": "^4.3.
|
|
31
|
+
"tailwindcss": "^4.3.2",
|
|
32
32
|
"tw-animate-css": "^1.4.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@radix-ui/react-accordion": "^1.2.
|
|
36
|
-
"@radix-ui/react-avatar": "^1.2.
|
|
37
|
-
"@radix-ui/react-checkbox": "^1.3.
|
|
38
|
-
"@radix-ui/react-collapsible": "^1.1.
|
|
39
|
-
"@radix-ui/react-dialog": "^1.1.
|
|
40
|
-
"@radix-ui/react-dropdown-menu": "^2.1.
|
|
41
|
-
"@radix-ui/react-label": "^2.1.
|
|
42
|
-
"@radix-ui/react-popover": "^1.1.
|
|
43
|
-
"@radix-ui/react-radio-group": "^1.4.
|
|
44
|
-
"@radix-ui/react-scroll-area": "^1.2.
|
|
45
|
-
"@radix-ui/react-select": "^2.3.
|
|
46
|
-
"@radix-ui/react-separator": "^1.1.
|
|
35
|
+
"@radix-ui/react-accordion": "^1.2.15",
|
|
36
|
+
"@radix-ui/react-avatar": "^1.2.1",
|
|
37
|
+
"@radix-ui/react-checkbox": "^1.3.6",
|
|
38
|
+
"@radix-ui/react-collapsible": "^1.1.15",
|
|
39
|
+
"@radix-ui/react-dialog": "^1.1.18",
|
|
40
|
+
"@radix-ui/react-dropdown-menu": "^2.1.19",
|
|
41
|
+
"@radix-ui/react-label": "^2.1.11",
|
|
42
|
+
"@radix-ui/react-popover": "^1.1.18",
|
|
43
|
+
"@radix-ui/react-radio-group": "^1.4.2",
|
|
44
|
+
"@radix-ui/react-scroll-area": "^1.2.13",
|
|
45
|
+
"@radix-ui/react-select": "^2.3.2",
|
|
46
|
+
"@radix-ui/react-separator": "^1.1.11",
|
|
47
47
|
"@radix-ui/react-slot": "^1.3.0",
|
|
48
|
-
"@radix-ui/react-switch": "^1.3.
|
|
49
|
-
"@radix-ui/react-tabs": "^1.1.
|
|
50
|
-
"@radix-ui/react-toggle-group": "^1.1.
|
|
51
|
-
"@radix-ui/react-tooltip": "^1.2.
|
|
48
|
+
"@radix-ui/react-switch": "^1.3.2",
|
|
49
|
+
"@radix-ui/react-tabs": "^1.1.16",
|
|
50
|
+
"@radix-ui/react-toggle-group": "^1.1.14",
|
|
51
|
+
"@radix-ui/react-tooltip": "^1.2.11",
|
|
52
52
|
"class-variance-authority": "^0.7.1",
|
|
53
53
|
"clsx": "^2.1.1",
|
|
54
54
|
"cmdk": "^1.1.1",
|
|
55
|
-
"recharts": "^3.9.
|
|
55
|
+
"recharts": "^3.9.2",
|
|
56
56
|
"tailwind-merge": "^3.6.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@ladle/react": "^5.1.1",
|
|
60
|
-
"@tailwindcss/postcss": "^4.3.
|
|
60
|
+
"@tailwindcss/postcss": "^4.3.2",
|
|
61
61
|
"@types/react": "19.2.17",
|
|
62
62
|
"@types/react-dom": "19.2.3",
|
|
63
|
-
"lucide-react": "^1.
|
|
64
|
-
"react-hook-form": "^7.
|
|
63
|
+
"lucide-react": "^1.23.0",
|
|
64
|
+
"react-hook-form": "^7.81.0",
|
|
65
65
|
"shx": "^0.4.0",
|
|
66
66
|
"sonner": "^2.0.7",
|
|
67
|
-
"tailwindcss": "^4.3.
|
|
67
|
+
"tailwindcss": "^4.3.2",
|
|
68
68
|
"tsdown": "^0.22.3",
|
|
69
69
|
"tw-animate-css": "^1.4.0",
|
|
70
70
|
"typescript": "^6.0.3"
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build": "shx rm -rf dist && tsdown",
|
|
74
74
|
"format": "biome check --write --error-on-warnings .",
|
|
75
|
-
"test:type": "
|
|
75
|
+
"test:type": "tsgo --noEmit",
|
|
76
76
|
"test:format": "biome check --error-on-warnings .",
|
|
77
77
|
"dev:ui": "ladle serve",
|
|
78
78
|
"publish:npm": "pnpm publish --tag \"${NPM_TAG}\" --access public --no-git-checks"
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import {
|
|
5
5
|
Bar,
|
|
6
6
|
CartesianGrid,
|
|
7
|
+
Cell,
|
|
7
8
|
LabelList,
|
|
8
9
|
BarChart as RechartsBarChart,
|
|
9
10
|
ReferenceArea,
|
|
@@ -41,6 +42,7 @@ export interface BarChartProps {
|
|
|
41
42
|
referenceLine?: { y: number; label?: string; band?: boolean };
|
|
42
43
|
markers?: ChartMarker[];
|
|
43
44
|
texture?: boolean;
|
|
45
|
+
partialLastBar?: boolean;
|
|
44
46
|
loading?: boolean;
|
|
45
47
|
valueFormatter?: (value: number) => string;
|
|
46
48
|
labelFormatter?: (label: string | number) => string;
|
|
@@ -61,6 +63,7 @@ function BarChart({
|
|
|
61
63
|
referenceLine,
|
|
62
64
|
markers,
|
|
63
65
|
texture = false,
|
|
66
|
+
partialLastBar = false,
|
|
64
67
|
loading = false,
|
|
65
68
|
valueFormatter = (value) => value.toLocaleString("en-US"),
|
|
66
69
|
labelFormatter,
|
|
@@ -97,6 +100,21 @@ function BarChart({
|
|
|
97
100
|
return max;
|
|
98
101
|
}, [data, series, stacked]);
|
|
99
102
|
|
|
103
|
+
const topSlotByRow = React.useMemo(() => {
|
|
104
|
+
if (!stacked) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return data.map((row) => {
|
|
108
|
+
for (let slot = rendered.length - 1; slot >= 0; slot -= 1) {
|
|
109
|
+
const value = Number(row[rendered[slot]?.key ?? ""]);
|
|
110
|
+
if (Number.isFinite(value) && value > 0) {
|
|
111
|
+
return slot;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return -1;
|
|
115
|
+
});
|
|
116
|
+
}, [data, rendered, stacked]);
|
|
117
|
+
|
|
100
118
|
const margin = { top: markers?.length || valueLabels ? 18 : 8, right: 8, bottom: 2, left: 0 };
|
|
101
119
|
|
|
102
120
|
const axis = {
|
|
@@ -278,6 +296,24 @@ function BarChart({
|
|
|
278
296
|
animationDuration={650}
|
|
279
297
|
animationEasing="ease-out"
|
|
280
298
|
>
|
|
299
|
+
{(stacked || partialLastBar) &&
|
|
300
|
+
data.map((row, rowIndex) => {
|
|
301
|
+
const roundedTop = stacked ? topSlotByRow[rowIndex] === slot : true;
|
|
302
|
+
const isPartial = partialLastBar && rowIndex === data.length - 1;
|
|
303
|
+
const cellRadius = (roundedTop
|
|
304
|
+
? [BAR_RADIUS, BAR_RADIUS, 0, 0]
|
|
305
|
+
: [0, 0, 0, 0]) as unknown as number;
|
|
306
|
+
return (
|
|
307
|
+
<Cell
|
|
308
|
+
key={`${entry.key}-${row[index] ?? rowIndex}`}
|
|
309
|
+
radius={cellRadius}
|
|
310
|
+
fillOpacity={isPartial ? 0.15 : 1}
|
|
311
|
+
stroke={isPartial ? entry.color : undefined}
|
|
312
|
+
strokeWidth={isPartial ? 1.25 : 0}
|
|
313
|
+
strokeDasharray={isPartial ? "3 2" : undefined}
|
|
314
|
+
/>
|
|
315
|
+
);
|
|
316
|
+
})}
|
|
281
317
|
{valueLabels && (!stacked || rendered.length === 1) && (
|
|
282
318
|
<LabelList dataKey={entry.key} content={renderValueLabel(entry.color)} />
|
|
283
319
|
)}
|
|
@@ -24,6 +24,9 @@ export interface HeatmapChartProps {
|
|
|
24
24
|
xTickFormatter?: (label: string) => string;
|
|
25
25
|
yTickFormatter?: (label: string) => string;
|
|
26
26
|
tooltipMetrics?: ReadonlyArray<{ key: string; label: string; format?: (value: number) => string }>;
|
|
27
|
+
tooltipTitleKey?: string;
|
|
28
|
+
colorScale?: "linear" | "rank";
|
|
29
|
+
showLegend?: boolean;
|
|
27
30
|
ariaLabel?: string;
|
|
28
31
|
className?: string;
|
|
29
32
|
}
|
|
@@ -83,6 +86,9 @@ function HeatmapChart({
|
|
|
83
86
|
xTickFormatter,
|
|
84
87
|
yTickFormatter,
|
|
85
88
|
tooltipMetrics,
|
|
89
|
+
tooltipTitleKey,
|
|
90
|
+
colorScale = "linear",
|
|
91
|
+
showLegend = false,
|
|
86
92
|
ariaLabel = "Activity heatmap",
|
|
87
93
|
className,
|
|
88
94
|
}: HeatmapChartProps) {
|
|
@@ -123,6 +129,28 @@ function HeatmapChart({
|
|
|
123
129
|
const { columns, rows, cells, maxValue, peakKey } = layout;
|
|
124
130
|
const isEmpty = columns.length === 0 || rows.length === 0 || maxValue <= 0;
|
|
125
131
|
|
|
132
|
+
// "rank" spreads clustered values across the whole ramp by colouring each cell
|
|
133
|
+
// by its position among the distinct non-zero values rather than its raw ratio
|
|
134
|
+
// to the max — the right read for skewed activity counts (à la GitHub).
|
|
135
|
+
const colorFractionOf = React.useMemo(() => {
|
|
136
|
+
if (colorScale === "rank") {
|
|
137
|
+
const sorted = [...new Set(cells.map((cell) => cell.value).filter((value) => value > 0))].sort(
|
|
138
|
+
(lower, upper) => lower - upper,
|
|
139
|
+
);
|
|
140
|
+
// Floor non-zero ranks above the empty colour so the quietest active day
|
|
141
|
+
// still reads as coloured, never mistaken for no data.
|
|
142
|
+
const RANK_FLOOR = 0.18;
|
|
143
|
+
const fractionByValue = new Map(
|
|
144
|
+
sorted.map((value, index) => [
|
|
145
|
+
value,
|
|
146
|
+
sorted.length > 1 ? RANK_FLOOR + (1 - RANK_FLOOR) * (index / (sorted.length - 1)) : 1,
|
|
147
|
+
]),
|
|
148
|
+
);
|
|
149
|
+
return (value: number) => (value > 0 ? (fractionByValue.get(value) ?? 0) : 0);
|
|
150
|
+
}
|
|
151
|
+
return (value: number) => (maxValue > 0 ? value / maxValue : 0);
|
|
152
|
+
}, [cells, colorScale, maxValue]);
|
|
153
|
+
|
|
126
154
|
if (loading) {
|
|
127
155
|
return (
|
|
128
156
|
<div
|
|
@@ -152,7 +180,7 @@ function HeatmapChart({
|
|
|
152
180
|
);
|
|
153
181
|
}
|
|
154
182
|
|
|
155
|
-
const ramp = heatRamp(paletteName, theme.isDark ? HEAT_EMPTY.dark : HEAT_EMPTY.light);
|
|
183
|
+
const ramp = heatRamp(paletteName, theme.isDark ? HEAT_EMPTY.dark : HEAT_EMPTY.light, theme.isDark);
|
|
156
184
|
const totalWidth = PAD.left + columns.length * CELL + (columns.length - 1) * GAP + PAD.right;
|
|
157
185
|
const totalHeight = PAD.top + rows.length * CELL + (rows.length - 1) * GAP + PAD.bottom;
|
|
158
186
|
const xStride = showAllXLabels ? 1 : Math.ceil(columns.length / MAX_X_TICKS);
|
|
@@ -209,7 +237,7 @@ function HeatmapChart({
|
|
|
209
237
|
</text>
|
|
210
238
|
))}
|
|
211
239
|
{cells.map((cell) => {
|
|
212
|
-
const fraction = cell.value
|
|
240
|
+
const fraction = colorFractionOf(cell.value);
|
|
213
241
|
const fill = heatColor(ramp, fraction);
|
|
214
242
|
const key = pairKey(cell.rowLabel, cell.colLabel);
|
|
215
243
|
const isPeak = highlightPeak && key === peakKey;
|
|
@@ -266,6 +294,18 @@ function HeatmapChart({
|
|
|
266
294
|
</svg>
|
|
267
295
|
</div>
|
|
268
296
|
|
|
297
|
+
{showLegend && (
|
|
298
|
+
<div className="mt-3 flex items-center justify-end gap-2">
|
|
299
|
+
<span className="font-mono text-[9px] text-quaternary-foreground uppercase tracking-[0.14em]">Less</span>
|
|
300
|
+
<span
|
|
301
|
+
aria-hidden
|
|
302
|
+
className="h-2 w-24 rounded-full border border-border"
|
|
303
|
+
style={{ background: `linear-gradient(90deg, ${ramp.join(", ")})` }}
|
|
304
|
+
/>
|
|
305
|
+
<span className="font-mono text-[9px] text-quaternary-foreground uppercase tracking-[0.14em]">More</span>
|
|
306
|
+
</div>
|
|
307
|
+
)}
|
|
308
|
+
|
|
269
309
|
{mounted &&
|
|
270
310
|
hovered &&
|
|
271
311
|
createPortal(
|
|
@@ -274,7 +314,9 @@ function HeatmapChart({
|
|
|
274
314
|
style={{ left: tooltipLeft, top: tooltipTop }}
|
|
275
315
|
>
|
|
276
316
|
<p className="mb-1.5 font-mono text-[10px] text-quaternary-foreground uppercase tracking-wider">
|
|
277
|
-
{
|
|
317
|
+
{tooltipTitleKey && typeof hovered.record?.[tooltipTitleKey] === "string"
|
|
318
|
+
? hovered.record[tooltipTitleKey]
|
|
319
|
+
: `${formatY(hovered.rowLabel)} · ${formatX(hovered.colLabel)}`}
|
|
278
320
|
</p>
|
|
279
321
|
{tooltipMetrics && tooltipMetrics.length > 0 ? (
|
|
280
322
|
<div className="flex flex-col gap-1">
|
|
@@ -288,7 +330,9 @@ function HeatmapChart({
|
|
|
288
330
|
aria-hidden
|
|
289
331
|
className="size-2 shrink-0 rounded-[2px]"
|
|
290
332
|
style={{
|
|
291
|
-
background: isActive
|
|
333
|
+
background: isActive
|
|
334
|
+
? heatColor(ramp, colorFractionOf(hovered.value))
|
|
335
|
+
: theme.mutedForeground,
|
|
292
336
|
opacity: isActive ? 1 : 0.35,
|
|
293
337
|
}}
|
|
294
338
|
/>
|
|
@@ -312,7 +356,7 @@ function HeatmapChart({
|
|
|
312
356
|
<span
|
|
313
357
|
aria-hidden
|
|
314
358
|
className="size-2 shrink-0 rounded-[2px]"
|
|
315
|
-
style={{ background: heatColor(ramp, hovered.value
|
|
359
|
+
style={{ background: heatColor(ramp, colorFractionOf(hovered.value)) }}
|
|
316
360
|
/>
|
|
317
361
|
activity
|
|
318
362
|
</span>
|
package/src/components/stat.tsx
CHANGED
|
@@ -9,7 +9,7 @@ import { cn } from "../lib/cn";
|
|
|
9
9
|
import { useChartContext } from "./chart-container";
|
|
10
10
|
|
|
11
11
|
const statDeltaVariants = cva(
|
|
12
|
-
"inline-flex items-center gap-0.5 rounded-md px-
|
|
12
|
+
"inline-flex items-center gap-0.5 rounded-md px-2 py-1 font-mono text-xs font-medium leading-none",
|
|
13
13
|
{
|
|
14
14
|
variants: {
|
|
15
15
|
sentiment: {
|
|
@@ -63,7 +63,7 @@ function Stat({ value, unit, delta, sparkline, semantic, className, ...props }:
|
|
|
63
63
|
{unit && <span className="font-mono text-[11px] leading-none text-quaternary-foreground mb-0.5">{unit}</span>}
|
|
64
64
|
{delta && (
|
|
65
65
|
<DeltaPill sentiment={sentiment} className="mb-0.5">
|
|
66
|
-
{delta.direction === "up" ? <ArrowUp className="size-3" /> : <ArrowDown className="size-3" />}
|
|
66
|
+
{delta.direction === "up" ? <ArrowUp className="size-3.5" /> : <ArrowDown className="size-3.5" />}
|
|
67
67
|
{delta.label ?? `${delta.value}%`}
|
|
68
68
|
</DeltaPill>
|
|
69
69
|
)}
|
package/src/components/table.tsx
CHANGED
|
@@ -4,9 +4,18 @@ import type * as React from "react";
|
|
|
4
4
|
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
interface TableProps extends React.ComponentProps<"table"> {
|
|
8
|
+
containerClassName?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function Table({ className, containerClassName, ...props }: TableProps) {
|
|
8
12
|
return (
|
|
9
|
-
<div
|
|
13
|
+
<div
|
|
14
|
+
className={cn(
|
|
15
|
+
"relative w-full overflow-auto rounded-xl border border-border-secondary bg-background",
|
|
16
|
+
containerClassName,
|
|
17
|
+
)}
|
|
18
|
+
>
|
|
10
19
|
<table data-slot="table" className={cn("w-full caption-bottom type-text-sm", className)} {...props} />
|
|
11
20
|
</div>
|
|
12
21
|
);
|
package/src/lib/chart-palette.ts
CHANGED
|
@@ -55,7 +55,10 @@ const HEAT_RAMPS: Record<ChartPaletteName, (empty: string) => string[]> = {
|
|
|
55
55
|
cyan: heatRampMint,
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
export const heatRamp = (palette: ChartPaletteName, empty: string) =>
|
|
58
|
+
export const heatRamp = (palette: ChartPaletteName, empty: string, isDark = true) => {
|
|
59
|
+
const [, ...colored] = HEAT_RAMPS[palette](empty);
|
|
60
|
+
return [empty, ...(isDark ? colored : colored.reverse())];
|
|
61
|
+
};
|
|
59
62
|
|
|
60
63
|
const hexToRgb = (hex: string) => {
|
|
61
64
|
const value = Number.parseInt(hex.slice(1), 16);
|