[WEB-3781] Analytics page enhancements (#7005)
* chore: analytics endpoint * added anlytics v2 * updated status icons * added area chart in workitems and en translations * active projects * chore: created analytics chart * chore: validation errors * improved radar-chart , added empty states , added projects summary * chore: added a new graph in advance analytics * integrated priority chart * chore: added csv exporter * added priority dropdown * integrated created vs resolved chart * custom x and y axis label in bar and area chart * added wrapper styles to legends * added filter components * fixed temp data imports * integrated filters in priority charts * added label to priority chart and updated duration filter * refactor * reverted to void onchange * fixed some contant exports * fixed type issues * fixed some type and build issues * chore: updated the filtering logic for analytics * updated default value to last_30_days * percentage value whole number and added some rules for axis options * fixed some translations * added - custom tick for radar, calc of insight cards, filter labels * chore: opitmised the analytics endpoint * replace old analytics path with new , updated labels of insight card, done some store fixes * chore: updated the export request * Enhanced ProjectSelect to support multi-select, improved state management, and optimized data fetching and component structure. * fix: round completion percentage calculation in ActiveProjectItem * added empty states in project insights * Added loader and empty state in created/resolved chart * added loaders * added icons in filters * added custom colors in customised charts * cleaned up some code * added some responsiveness * updated translations * updated serrchbar for the table * added work item modal in project analytics * fixed some of the layput issues in the peek view * chore: updated the base function for viewsets * synced tab to url * code cleanup * chore: updated the export logic * fixed project_ids filter * added icon in projectdropdown * updated export button position * export csv and emptystates icons * refactor * code refactor * updated loaders, moved color pallete to contants, added nullish collasece operator in neccessary places * removed uneccessary cn * fixed formatting issues * fixed empty project_ids in payload * improved null checks * optimized charts * modified relevant variables to observable.ref * fixed the duration type * optimized some code * updated query key in project-insight * updated query key in project-insight * updated formatting * chore: replaced analytics route with new one and done some optimizations * removed the old analytics --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
0d5c7c6653
commit
75d81f9e95
103 changed files with 3919 additions and 162 deletions
|
|
@ -29,13 +29,21 @@ export const AreaChart = React.memo(<K extends string, T extends string>(props:
|
|||
// states
|
||||
const [activeArea, setActiveArea] = useState<string | null>(null);
|
||||
const [activeLegend, setActiveLegend] = useState<string | null>(null);
|
||||
|
||||
// derived values
|
||||
const itemKeys = useMemo(() => areas.map((area) => area.key), [areas]);
|
||||
const itemLabels: Record<string, string> = useMemo(
|
||||
() => areas.reduce((acc, area) => ({ ...acc, [area.key]: area.label }), {}),
|
||||
[areas]
|
||||
);
|
||||
const itemDotColors = useMemo(() => areas.reduce((acc, area) => ({ ...acc, [area.key]: area.fill }), {}), [areas]);
|
||||
const { itemKeys, itemLabels, itemDotColors } = useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
const labels: Record<string, string> = {};
|
||||
const colors: Record<string, string> = {};
|
||||
|
||||
for (const area of areas) {
|
||||
keys.push(area.key);
|
||||
labels[area.key] = area.label;
|
||||
colors[area.key] = area.fill;
|
||||
}
|
||||
|
||||
return { itemKeys: keys, itemLabels: labels, itemDotColors: colors };
|
||||
}, [areas]);
|
||||
|
||||
const renderAreas = useMemo(
|
||||
() =>
|
||||
|
|
@ -77,7 +85,7 @@ export const AreaChart = React.memo(<K extends string, T extends string>(props:
|
|||
// get the last data point
|
||||
const lastPoint = data[data.length - 1];
|
||||
// for the y-value in the last point, use its yAxis key value
|
||||
const lastYValue = lastPoint[yAxis.key] || 0;
|
||||
const lastYValue = lastPoint[yAxis.key] ?? 0;
|
||||
// create data for a straight line that has points at each x-axis position
|
||||
return data.map((item, index) => {
|
||||
// calculate the y value for this point on the straight line
|
||||
|
|
@ -91,7 +99,6 @@ export const AreaChart = React.memo(<K extends string, T extends string>(props:
|
|||
};
|
||||
});
|
||||
}, [data, xAxis.key]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
|
|
@ -128,8 +135,8 @@ export const AreaChart = React.memo(<K extends string, T extends string>(props:
|
|||
value: yAxis.label,
|
||||
angle: -90,
|
||||
position: "bottom",
|
||||
offset: -24,
|
||||
dx: -16,
|
||||
offset: yAxis.offset ?? -24,
|
||||
dx: yAxis.dx ?? -16,
|
||||
className: AXIS_LABEL_CLASSNAME,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,22 @@ export const BarChart = React.memo(<K extends string, T extends string>(props: T
|
|||
// states
|
||||
const [activeBar, setActiveBar] = useState<string | null>(null);
|
||||
const [activeLegend, setActiveLegend] = useState<string | null>(null);
|
||||
|
||||
// derived values
|
||||
const stackKeys = useMemo(() => bars.map((bar) => bar.key), [bars]);
|
||||
const stackLabels: Record<string, string> = useMemo(
|
||||
() => bars.reduce((acc, bar) => ({ ...acc, [bar.key]: bar.label }), {}),
|
||||
[bars]
|
||||
);
|
||||
const stackDotColors = useMemo(() => bars.reduce((acc, bar) => ({ ...acc, [bar.key]: bar.fill }), {}), [bars]);
|
||||
const { stackKeys, stackLabels, stackDotColors } = useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
const labels: Record<string, string> = {};
|
||||
const colors: Record<string, string> = {};
|
||||
|
||||
for (const bar of bars) {
|
||||
keys.push(bar.key);
|
||||
labels[bar.key] = bar.label;
|
||||
// For tooltip, we need a string color. If fill is a function, use a default color
|
||||
colors[bar.key] = typeof bar.fill === "function" ? "#000000" : bar.fill;
|
||||
}
|
||||
|
||||
return { stackKeys: keys, stackLabels: labels, stackDotColors: colors };
|
||||
}, [bars]);
|
||||
|
||||
const renderBars = useMemo(
|
||||
() =>
|
||||
|
|
@ -102,7 +111,7 @@ export const BarChart = React.memo(<K extends string, T extends string>(props: T
|
|||
axisLine={false}
|
||||
label={{
|
||||
value: xAxis.label,
|
||||
dy: 28,
|
||||
dy: xAxis.dy ?? 28,
|
||||
className: AXIS_LABEL_CLASSNAME,
|
||||
}}
|
||||
tickCount={tickCount.x}
|
||||
|
|
|
|||
|
|
@ -15,16 +15,17 @@ export const getLegendProps = (args: TChartLegend): LegendProps => {
|
|||
overflow: "hidden",
|
||||
...(layout === "vertical"
|
||||
? {
|
||||
top: 0,
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
}
|
||||
top: 0,
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
}
|
||||
: {
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}),
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}),
|
||||
...args.wrapperStyles,
|
||||
},
|
||||
content: <CustomLegend {...args} />,
|
||||
};
|
||||
|
|
@ -33,8 +34,8 @@ export const getLegendProps = (args: TChartLegend): LegendProps => {
|
|||
const CustomLegend = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> &
|
||||
Pick<LegendProps, "payload" | "formatter" | "onClick" | "onMouseEnter" | "onMouseLeave"> &
|
||||
TChartLegend
|
||||
Pick<LegendProps, "payload" | "formatter" | "onClick" | "onMouseEnter" | "onMouseLeave"> &
|
||||
TChartLegend
|
||||
>((props, ref) => {
|
||||
const { formatter, layout, onClick, onMouseEnter, onMouseLeave, payload } = props;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import React from "react";
|
|||
// Common classnames
|
||||
const AXIS_TICK_CLASSNAME = "fill-custom-text-300 text-sm";
|
||||
|
||||
export const CustomXAxisTick = React.memo<any>(({ x, y, payload }: any) => (
|
||||
export const CustomXAxisTick = React.memo<any>(({ x, y, payload, getLabel }: any) => (
|
||||
<g transform={`translate(${x},${y})`}>
|
||||
<text y={0} dy={16} textAnchor="middle" className={AXIS_TICK_CLASSNAME}>
|
||||
{payload.value}
|
||||
{getLabel ? getLabel(payload.value) : payload.value}
|
||||
</text>
|
||||
</g>
|
||||
));
|
||||
|
|
@ -20,4 +20,28 @@ export const CustomYAxisTick = React.memo<any>(({ x, y, payload }: any) => (
|
|||
</text>
|
||||
</g>
|
||||
));
|
||||
|
||||
CustomYAxisTick.displayName = "CustomYAxisTick";
|
||||
|
||||
export const CustomRadarAxisTick = React.memo<any>(
|
||||
({ x, y, payload, getLabel, cx, cy, offset = 16 }: any) => {
|
||||
// Calculate direction vector from center to tick
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
// Normalize and apply offset
|
||||
const length = Math.sqrt(dx * dx + dy * dy);
|
||||
const normX = dx / length;
|
||||
const normY = dy / length;
|
||||
const labelX = x + normX * offset;
|
||||
const labelY = y + normY * offset;
|
||||
|
||||
return (
|
||||
<g transform={`translate(${labelX},${labelY})`}>
|
||||
<text y={0} textAnchor="middle" className={AXIS_TICK_CLASSNAME}>
|
||||
{getLabel ? getLabel(payload.value) : payload.value}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
);
|
||||
CustomRadarAxisTick.displayName = "CustomRadarAxisTick";
|
||||
|
|
|
|||
|
|
@ -38,13 +38,21 @@ export const LineChart = React.memo(<K extends string, T extends string>(props:
|
|||
// states
|
||||
const [activeLine, setActiveLine] = useState<string | null>(null);
|
||||
const [activeLegend, setActiveLegend] = useState<string | null>(null);
|
||||
|
||||
// derived values
|
||||
const itemKeys = useMemo(() => lines.map((line) => line.key), [lines]);
|
||||
const itemLabels: Record<string, string> = useMemo(
|
||||
() => lines.reduce((acc, line) => ({ ...acc, [line.key]: line.label }), {}),
|
||||
[lines]
|
||||
);
|
||||
const itemDotColors = useMemo(() => lines.reduce((acc, line) => ({ ...acc, [line.key]: line.stroke }), {}), [lines]);
|
||||
const { itemKeys, itemLabels, itemDotColors } = useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
const labels: Record<string, string> = {};
|
||||
const colors: Record<string, string> = {};
|
||||
|
||||
for (const line of lines) {
|
||||
keys.push(line.key);
|
||||
labels[line.key] = line.label;
|
||||
colors[line.key] = line.stroke;
|
||||
}
|
||||
|
||||
return { itemKeys: keys, itemLabels: labels, itemDotColors: colors };
|
||||
}, [lines]);
|
||||
|
||||
const renderLines = useMemo(
|
||||
() =>
|
||||
|
|
|
|||
1
packages/propel/src/charts/radar-chart/index.ts
Normal file
1
packages/propel/src/charts/radar-chart/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./root";
|
||||
95
packages/propel/src/charts/radar-chart/root.tsx
Normal file
95
packages/propel/src/charts/radar-chart/root.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
PolarGrid,
|
||||
Radar,
|
||||
RadarChart as CoreRadarChart,
|
||||
ResponsiveContainer,
|
||||
PolarAngleAxis,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "recharts";
|
||||
import { TRadarChartProps } from "@plane/types";
|
||||
import { getLegendProps } from "../components/legend";
|
||||
import { CustomRadarAxisTick } from "../components/tick";
|
||||
import { CustomTooltip } from "../components/tooltip";
|
||||
|
||||
const RadarChart = <T extends string, K extends string>(props: TRadarChartProps<T, K>) => {
|
||||
const { data, radars, margin, showTooltip, legend, className, angleAxis } = props;
|
||||
|
||||
// states
|
||||
const [, setActiveIndex] = useState<number | null>(null);
|
||||
const [activeLegend, setActiveLegend] = useState<string | null>(null);
|
||||
|
||||
const { itemKeys, itemLabels, itemDotColors } = useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
const labels: Record<string, string> = {};
|
||||
const colors: Record<string, string> = {};
|
||||
|
||||
for (const radar of radars) {
|
||||
keys.push(radar.key);
|
||||
labels[radar.key] = radar.name;
|
||||
colors[radar.key] = radar.stroke ?? radar.fill ?? "#000000";
|
||||
}
|
||||
return { itemKeys: keys, itemLabels: labels, itemDotColors: colors };
|
||||
}, [radars]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<CoreRadarChart cx="50%" cy="50%" outerRadius="80%" data={data} margin={margin}>
|
||||
<PolarGrid stroke="rgba(var(--color-border-100), 0.9)" />
|
||||
<PolarAngleAxis dataKey={angleAxis.key} tick={(props) => <CustomRadarAxisTick {...props} />} />
|
||||
{showTooltip && (
|
||||
<Tooltip
|
||||
cursor={{
|
||||
stroke: "rgba(var(--color-text-300))",
|
||||
strokeDasharray: "4 4",
|
||||
}}
|
||||
wrapperStyle={{
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
content={({ active, label, payload }) => (
|
||||
<CustomTooltip
|
||||
active={active}
|
||||
activeKey={activeLegend}
|
||||
label={label}
|
||||
payload={payload}
|
||||
itemKeys={itemKeys}
|
||||
itemLabels={itemLabels}
|
||||
itemDotColors={itemDotColors}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{legend && (
|
||||
// @ts-expect-error recharts types are not up to date
|
||||
<Legend
|
||||
onMouseEnter={(payload) => {
|
||||
// @ts-expect-error recharts types are not up to date
|
||||
const key: string | undefined = payload.payload?.key;
|
||||
if (!key) return;
|
||||
setActiveLegend(key);
|
||||
setActiveIndex(null);
|
||||
}}
|
||||
onMouseLeave={() => setActiveLegend(null)}
|
||||
{...getLegendProps(legend)}
|
||||
/>
|
||||
)}
|
||||
{radars.map((radar) => (
|
||||
<Radar
|
||||
key={radar.key}
|
||||
name={radar.name}
|
||||
dataKey={radar.key}
|
||||
stroke={radar.stroke}
|
||||
fill={radar.fill}
|
||||
fillOpacity={radar.fillOpacity}
|
||||
dot={radar.dot}
|
||||
/>
|
||||
))}
|
||||
</CoreRadarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { RadarChart };
|
||||
1
packages/propel/src/charts/scatter-chart/index.ts
Normal file
1
packages/propel/src/charts/scatter-chart/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./root";
|
||||
155
packages/propel/src/charts/scatter-chart/root.tsx
Normal file
155
packages/propel/src/charts/scatter-chart/root.tsx
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
"use client";
|
||||
|
||||
import React, { useMemo, useState } from "react";
|
||||
import {
|
||||
CartesianGrid,
|
||||
ScatterChart as CoreScatterChart,
|
||||
Legend,
|
||||
Scatter,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
// plane imports
|
||||
import { AXIS_LABEL_CLASSNAME } from "@plane/constants";
|
||||
import { TScatterChartProps } from "@plane/types";
|
||||
// local components
|
||||
import { getLegendProps } from "../components/legend";
|
||||
import { CustomXAxisTick, CustomYAxisTick } from "../components/tick";
|
||||
import { CustomTooltip } from "../components/tooltip";
|
||||
|
||||
export const ScatterChart = React.memo(<K extends string, T extends string>(props: TScatterChartProps<K, T>) => {
|
||||
const {
|
||||
data,
|
||||
scatterPoints,
|
||||
margin,
|
||||
xAxis,
|
||||
yAxis,
|
||||
|
||||
className,
|
||||
tickCount = {
|
||||
x: undefined,
|
||||
y: 10,
|
||||
},
|
||||
legend,
|
||||
showTooltip = true,
|
||||
} = props;
|
||||
// states
|
||||
const [activePoint, setActivePoint] = useState<string | null>(null);
|
||||
const [activeLegend, setActiveLegend] = useState<string | null>(null);
|
||||
|
||||
//derived values
|
||||
const { itemKeys, itemLabels, itemDotColors } = useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
const labels: Record<string, string> = {};
|
||||
const colors: Record<string, string> = {};
|
||||
|
||||
for (const point of scatterPoints) {
|
||||
keys.push(point.key);
|
||||
labels[point.key] = point.label;
|
||||
colors[point.key] = point.fill;
|
||||
}
|
||||
return { itemKeys: keys, itemLabels: labels, itemDotColors: colors };
|
||||
}, [scatterPoints]);
|
||||
|
||||
const renderPoints = useMemo(
|
||||
() =>
|
||||
scatterPoints.map((point) => (
|
||||
<Scatter
|
||||
key={point.key}
|
||||
dataKey={point.key}
|
||||
fill={point.fill}
|
||||
stroke={point.stroke}
|
||||
opacity={!!activeLegend && activeLegend !== point.key ? 0.1 : 1}
|
||||
onMouseEnter={() => setActivePoint(point.key)}
|
||||
onMouseLeave={() => setActivePoint(null)}
|
||||
/>
|
||||
)),
|
||||
[activeLegend, scatterPoints]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<CoreScatterChart
|
||||
data={data}
|
||||
margin={{
|
||||
top: margin?.top === undefined ? 5 : margin.top,
|
||||
right: margin?.right === undefined ? 30 : margin.right,
|
||||
bottom: margin?.bottom === undefined ? 5 : margin.bottom,
|
||||
left: margin?.left === undefined ? 20 : margin.left,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid stroke="rgba(var(--color-border-100), 0.8)" vertical={false} />
|
||||
<XAxis
|
||||
dataKey={xAxis.key}
|
||||
tick={(props) => <CustomXAxisTick {...props} />}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
label={
|
||||
xAxis.label && {
|
||||
value: xAxis.label,
|
||||
dy: 28,
|
||||
className: AXIS_LABEL_CLASSNAME,
|
||||
}
|
||||
}
|
||||
tickCount={tickCount.x}
|
||||
/>
|
||||
<YAxis
|
||||
domain={yAxis.domain}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
label={
|
||||
yAxis.label && {
|
||||
value: yAxis.label,
|
||||
angle: -90,
|
||||
position: "bottom",
|
||||
offset: -24,
|
||||
dx: -16,
|
||||
className: AXIS_LABEL_CLASSNAME,
|
||||
}
|
||||
}
|
||||
tick={(props) => <CustomYAxisTick {...props} />}
|
||||
tickCount={tickCount.y}
|
||||
allowDecimals={!!yAxis.allowDecimals}
|
||||
/>
|
||||
{legend && (
|
||||
// @ts-expect-error recharts types are not up to date
|
||||
<Legend
|
||||
onMouseEnter={(payload) => setActiveLegend(payload.value)}
|
||||
onMouseLeave={() => setActiveLegend(null)}
|
||||
formatter={(value) => itemLabels[value]}
|
||||
{...getLegendProps(legend)}
|
||||
/>
|
||||
)}
|
||||
{showTooltip && (
|
||||
<Tooltip
|
||||
cursor={{
|
||||
stroke: "rgba(var(--color-text-300))",
|
||||
strokeDasharray: "4 4",
|
||||
}}
|
||||
wrapperStyle={{
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
content={({ active, label, payload }) => (
|
||||
<CustomTooltip
|
||||
active={active}
|
||||
activeKey={activePoint}
|
||||
label={label}
|
||||
payload={payload}
|
||||
itemKeys={itemKeys}
|
||||
itemLabels={itemLabels}
|
||||
itemDotColors={itemDotColors}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{renderPoints}
|
||||
</CoreScatterChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
ScatterChart.displayName = "ScatterChart";
|
||||
120
packages/propel/src/table/core.tsx
Normal file
120
packages/propel/src/table/core.tsx
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@plane/utils"
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
Table.displayName = "Table"
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("bg-custom-background-80 py-4 border-y border-custom-border-200", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-custom-background-300 font-medium",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"transition-colors data-[state=selected]:bg-custom-background-100",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableRow.displayName = "TableRow"
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableHeaderCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableHeaderCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-10 px-2 text-left align-middle font-medium text-custom-text-300 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableDataCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableDataCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableDataCellElement,
|
||||
React.HTMLAttributes<HTMLTableDataCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-custom-text-300", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
1
packages/propel/src/table/index.ts
Normal file
1
packages/propel/src/table/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./core";
|
||||
Loading…
Add table
Add a link
Reference in a new issue