Skip to content

Commit 6e2ff6c

Browse files
authored
add tailwind to wave, support shadcn + donut chart as a POC (#1775)
1 parent 3c5ff51 commit 6e2ff6c

28 files changed

+1288
-230
lines changed

.storybook/global.css

+2
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ body {
1616
background-color: orange;
1717
}
1818
}
19+
20+
@import "../frontend/tailwindsetup.css";

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,8 @@
4545
},
4646
"[md]": {
4747
"editor.wordWrap": "on"
48+
},
49+
"files.associations": {
50+
"*.css": "tailwindcss"
4851
}
4952
}

electron.vite.config.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2025, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import tailwindcss from "@tailwindcss/vite";
45
import react from "@vitejs/plugin-react-swc";
56
import { defineConfig } from "electron-vite";
67
import flow from "rollup-plugin-flow";
@@ -63,15 +64,23 @@ export default defineConfig({
6364
server: {
6465
open: false,
6566
},
67+
css: {
68+
preprocessorOptions: {
69+
scss: {
70+
silenceDeprecations: ["mixed-decls"],
71+
},
72+
},
73+
},
6674
plugins: [
6775
ViteImageOptimizer(),
6876
tsconfigPaths(),
77+
flow(),
6978
svgr({
7079
svgrOptions: { exportType: "default", ref: true, svgo: false, titleProp: true },
7180
include: "**/*.svg",
7281
}),
7382
react({}),
74-
flow(),
83+
tailwindcss(),
7584
viteStaticCopy({
7685
targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }],
7786
}),

frontend/app/app.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import { NotificationBubbles } from "./notification/notificationbubbles";
2929

3030
import "./app.scss";
3131

32+
// this should come after app.scss (don't remove the newline above otherwise prettier will reorder these imports)
33+
import "../tailwindsetup.css";
34+
3235
const dlog = debug("wave:app");
3336
const focusLog = debug("wave:focus");
3437

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import DonutChart from "./donutchart";
3+
4+
const meta = {
5+
title: "Components/DonutChart",
6+
component: DonutChart,
7+
parameters: {
8+
layout: "centered",
9+
docs: {
10+
description: {
11+
component:
12+
"The `DonutChart` component displays data in a donut-style chart with customizable colors, labels, and tooltip. Useful for visualizing proportions or percentages.",
13+
},
14+
},
15+
},
16+
argTypes: {
17+
data: {
18+
description:
19+
"The data for the chart, where each item includes `label`, `value`, and optional `displayvalue`.",
20+
control: { type: "object" },
21+
},
22+
config: {
23+
description: "config for the chart",
24+
control: { type: "object" },
25+
},
26+
innerLabel: {
27+
description: "The label displayed inside the donut chart (e.g., percentages).",
28+
control: { type: "text" },
29+
},
30+
},
31+
decorators: [
32+
(Story) => (
33+
<div
34+
style={{
35+
width: "200px",
36+
height: "200px",
37+
display: "flex",
38+
justifyContent: "center",
39+
alignItems: "center",
40+
border: "1px solid #ddd",
41+
}}
42+
>
43+
<Story />
44+
</div>
45+
),
46+
],
47+
} satisfies Meta<typeof DonutChart>;
48+
49+
export default meta;
50+
type Story = StoryObj<typeof DonutChart>;
51+
52+
export const Default: Story = {
53+
args: {
54+
config: {
55+
chrome: { label: "Chrome", color: "#8884d8" },
56+
safari: { label: "Safari", color: "#82ca9d" },
57+
firefox: { label: "Firefox", color: "#ffc658" },
58+
edge: { label: "Edge", color: "#ff8042" },
59+
other: { label: "Other", color: "#8dd1e1" },
60+
},
61+
data: [
62+
{ label: "chrome", value: 275, fill: "#8884d8" }, // Purple
63+
{ label: "safari", value: 200, fill: "#82ca9d" }, // Green
64+
{ label: "firefox", value: 287, fill: "#ffc658" }, // Yellow
65+
{ label: "edge", value: 173, fill: "#ff8042" }, // Orange
66+
{ label: "other", value: 190, fill: "#8dd1e1" }, // Light Blue
67+
],
68+
innerLabel: "50%",
69+
innerSubLabel: "50/100",
70+
dataKey: "value",
71+
nameKey: "label",
72+
},
73+
};

frontend/app/element/donutchart.tsx

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/app/shadcn/chart";
2+
import { isBlank } from "@/util/util";
3+
import { Label, Pie, PieChart } from "recharts";
4+
import { ViewBox } from "recharts/types/util/types";
5+
6+
const DEFAULT_COLORS = [
7+
"#3498db", // blue
8+
"#2ecc71", // green
9+
"#e74c3c", // red
10+
"#f1c40f", // yellow
11+
"#9b59b6", // purple
12+
"#1abc9c", // turquoise
13+
"#e67e22", // orange
14+
"#34495e", // dark blue
15+
];
16+
17+
const NO_DATA_COLOR = "#E0E0E0";
18+
19+
const PieInnerLabel = ({
20+
innerLabel,
21+
innerSubLabel,
22+
viewBox,
23+
}: {
24+
innerLabel: string;
25+
innerSubLabel: string;
26+
viewBox: ViewBox;
27+
}) => {
28+
if (isBlank(innerLabel)) {
29+
return null;
30+
}
31+
if (!viewBox || !("cx" in viewBox) || !("cy" in viewBox)) {
32+
return null;
33+
}
34+
return (
35+
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle">
36+
<tspan x={viewBox.cx} y={viewBox.cy} fill="white" className="fill-foreground text-2xl font-bold">
37+
{innerLabel}
38+
</tspan>
39+
{innerSubLabel && (
40+
<tspan x={viewBox.cx} y={(viewBox.cy || 0) + 24} className="fill-muted-foreground">
41+
{innerSubLabel}
42+
</tspan>
43+
)}
44+
</text>
45+
);
46+
};
47+
48+
const DonutChart = ({
49+
data,
50+
config,
51+
innerLabel,
52+
innerSubLabel,
53+
dataKey,
54+
nameKey,
55+
}: {
56+
data: any[];
57+
config: ChartConfig;
58+
innerLabel?: string;
59+
innerSubLabel?: string;
60+
dataKey: string;
61+
nameKey: string;
62+
}) => {
63+
return (
64+
<div className="flex flex-col items-center w-full h-full">
65+
<ChartContainer config={config} className="mx-auto w-full h-full aspect-square max-h-[250px]">
66+
<PieChart>
67+
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
68+
<Pie
69+
data={data}
70+
dataKey={dataKey}
71+
nameKey={nameKey}
72+
innerRadius={60}
73+
strokeWidth={5}
74+
isAnimationActive={false}
75+
>
76+
<Label
77+
content={({ viewBox }) => (
78+
<PieInnerLabel
79+
innerLabel={innerLabel}
80+
innerSubLabel={innerSubLabel}
81+
viewBox={viewBox}
82+
/>
83+
)}
84+
/>
85+
</Pie>
86+
</PieChart>
87+
</ChartContainer>
88+
</div>
89+
);
90+
};
91+
92+
export default DonutChart;

frontend/app/element/markdown.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@
9393
ul {
9494
list-style-type: disc;
9595
list-style-position: outside;
96-
margin-left: 1.143em;
96+
margin-left: 1em;
9797
}
9898

9999
ol {
100100
list-style-position: outside;
101-
margin-left: 1.357em;
101+
margin-left: 1.2em;
102102
}
103103

104104
blockquote {

0 commit comments

Comments
 (0)