Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add tailwind to wave, support shadcn + donut chart as a POC #1775

Merged
merged 25 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .storybook/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ body {
background-color: orange;
}
}

@import "../frontend/tailwindsetup.css";
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,8 @@
},
"[md]": {
"editor.wordWrap": "on"
},
"files.associations": {
"*.css": "tailwindcss"
}
}
11 changes: 10 additions & 1 deletion electron.vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "electron-vite";
import flow from "rollup-plugin-flow";
Expand Down Expand Up @@ -63,15 +64,23 @@ export default defineConfig({
server: {
open: false,
},
css: {
preprocessorOptions: {
scss: {
silenceDeprecations: ["mixed-decls"],
},
},
},
plugins: [
ViteImageOptimizer(),
tsconfigPaths(),
flow(),
svgr({
svgrOptions: { exportType: "default", ref: true, svgo: false, titleProp: true },
include: "**/*.svg",
}),
react({}),
flow(),
tailwindcss(),
viteStaticCopy({
targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }],
}),
Expand Down
3 changes: 3 additions & 0 deletions frontend/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import { NotificationBubbles } from "./notification/notificationbubbles";

import "./app.scss";

// this should come after app.scss (don't remove the newline above otherwise prettier will reorder these imports)
import "../tailwindsetup.css";

const dlog = debug("wave:app");
const focusLog = debug("wave:focus");

Expand Down
73 changes: 73 additions & 0 deletions frontend/app/element/donutchart.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { Meta, StoryObj } from "@storybook/react";
import DonutChart from "./donutchart";

const meta = {
title: "Components/DonutChart",
component: DonutChart,
parameters: {
layout: "centered",
docs: {
description: {
component:
"The `DonutChart` component displays data in a donut-style chart with customizable colors, labels, and tooltip. Useful for visualizing proportions or percentages.",
},
},
},
argTypes: {
data: {
description:
"The data for the chart, where each item includes `label`, `value`, and optional `displayvalue`.",
control: { type: "object" },
},
config: {
description: "config for the chart",
control: { type: "object" },
},
innerLabel: {
description: "The label displayed inside the donut chart (e.g., percentages).",
control: { type: "text" },
},
},
decorators: [
(Story) => (
<div
style={{
width: "200px",
height: "200px",
display: "flex",
justifyContent: "center",
alignItems: "center",
border: "1px solid #ddd",
}}
>
<Story />
</div>
),
],
} satisfies Meta<typeof DonutChart>;

export default meta;
type Story = StoryObj<typeof DonutChart>;

export const Default: Story = {
args: {
config: {
chrome: { label: "Chrome", color: "#8884d8" },
safari: { label: "Safari", color: "#82ca9d" },
firefox: { label: "Firefox", color: "#ffc658" },
edge: { label: "Edge", color: "#ff8042" },
other: { label: "Other", color: "#8dd1e1" },
},
data: [
{ label: "chrome", value: 275, fill: "#8884d8" }, // Purple
{ label: "safari", value: 200, fill: "#82ca9d" }, // Green
{ label: "firefox", value: 287, fill: "#ffc658" }, // Yellow
{ label: "edge", value: 173, fill: "#ff8042" }, // Orange
{ label: "other", value: 190, fill: "#8dd1e1" }, // Light Blue
],
innerLabel: "50%",
innerSubLabel: "50/100",
dataKey: "value",
nameKey: "label",
},
};
92 changes: 92 additions & 0 deletions frontend/app/element/donutchart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/app/shadcn/chart";
import { isBlank } from "@/util/util";
import { Label, Pie, PieChart } from "recharts";
import { ViewBox } from "recharts/types/util/types";

const DEFAULT_COLORS = [
"#3498db", // blue
"#2ecc71", // green
"#e74c3c", // red
"#f1c40f", // yellow
"#9b59b6", // purple
"#1abc9c", // turquoise
"#e67e22", // orange
"#34495e", // dark blue
];

const NO_DATA_COLOR = "#E0E0E0";

const PieInnerLabel = ({
innerLabel,
innerSubLabel,
viewBox,
}: {
innerLabel: string;
innerSubLabel: string;
viewBox: ViewBox;
}) => {
if (isBlank(innerLabel)) {
return null;
}
if (!viewBox || !("cx" in viewBox) || !("cy" in viewBox)) {
return null;
}
return (
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle">
<tspan x={viewBox.cx} y={viewBox.cy} fill="white" className="fill-foreground text-2xl font-bold">
{innerLabel}
</tspan>
{innerSubLabel && (
<tspan x={viewBox.cx} y={(viewBox.cy || 0) + 24} className="fill-muted-foreground">
{innerSubLabel}
</tspan>
)}
</text>
);
};

const DonutChart = ({
data,
config,
innerLabel,
innerSubLabel,
dataKey,
nameKey,
}: {
data: any[];
config: ChartConfig;
innerLabel?: string;
innerSubLabel?: string;
dataKey: string;
nameKey: string;
}) => {
Comment on lines +56 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding proper TypeScript types for data prop.

The data prop is typed as any[] which loses type safety. Consider creating a proper interface for the data structure.

+interface DonutChartData {
+    [key: string]: string | number;
+}
+
 const DonutChart = ({
     data,
     config,
     innerLabel,
     innerSubLabel,
     dataKey,
     nameKey,
 }: {
-    data: any[];
+    data: DonutChartData[];
     config: ChartConfig;
     innerLabel?: string;
     innerSubLabel?: string;
     dataKey: string;
     nameKey: string;
 }) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data: any[];
config: ChartConfig;
innerLabel?: string;
innerSubLabel?: string;
dataKey: string;
nameKey: string;
}) => {
interface DonutChartData {
[key: string]: string | number;
}
const DonutChart = ({
data,
config,
innerLabel,
innerSubLabel,
dataKey,
nameKey,
}: {
data: DonutChartData[];
config: ChartConfig;
innerLabel?: string;
innerSubLabel?: string;
dataKey: string;
nameKey: string;
}) => {
// ... implementation details
}

return (
<div className="flex flex-col items-center w-full h-full">
<ChartContainer config={config} className="mx-auto w-full h-full aspect-square max-h-[250px]">
<PieChart>
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
<Pie
data={data}
dataKey={dataKey}
nameKey={nameKey}
innerRadius={60}
strokeWidth={5}
isAnimationActive={false}
>
<Label
content={({ viewBox }) => (
<PieInnerLabel
innerLabel={innerLabel}
innerSubLabel={innerSubLabel}
viewBox={viewBox}
/>
)}
/>
</Pie>
</PieChart>
</ChartContainer>
</div>
);
};
Comment on lines +48 to +90
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and customization options.

Consider the following improvements:

  1. Add error handling for empty data
  2. Allow color customization
  3. Add proper documentation
+/**
+ * A donut chart component that displays data in a circular format with an optional inner label.
+ * @param data - Array of data points to display
+ * @param config - Chart configuration
+ * @param innerLabel - Optional text to display in the center
+ * @param innerSubLabel - Optional secondary text below the inner label
+ * @param dataKey - Key to use for data values
+ * @param nameKey - Key to use for segment names
+ * @param colors - Optional array of colors to override defaults
+ */
 const DonutChart = ({
     data,
     config,
     innerLabel,
     innerSubLabel,
     dataKey,
     nameKey,
+    colors = DEFAULT_COLORS,
 }: {
     data: any[];
     config: ChartConfig;
     innerLabel?: string;
     innerSubLabel?: string;
     dataKey: string;
     nameKey: string;
+    colors?: string[];
 }) => {
+    if (!data?.length) {
+        return (
+            <div className="flex items-center justify-center w-full h-full text-muted-foreground">
+                No data available
+            </div>
+        );
+    }
+
     return (
         <div className="flex flex-col items-center w-full h-full">
             <ChartContainer config={config} className="mx-auto w-full h-full aspect-square max-h-[250px]">
                 <PieChart>
                     <ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
                     <Pie
                         data={data}
                         dataKey={dataKey}
                         nameKey={nameKey}
                         innerRadius={60}
                         strokeWidth={5}
                         isAnimationActive={false}
+                        fill={NO_DATA_COLOR}
+                    >
+                        {data.map((_, index) => (
+                            <Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
+                        ))}
-                    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const DonutChart = ({
data,
config,
innerLabel,
innerSubLabel,
dataKey,
nameKey,
}: {
data: any[];
config: ChartConfig;
innerLabel?: string;
innerSubLabel?: string;
dataKey: string;
nameKey: string;
}) => {
return (
<div className="flex flex-col items-center w-full h-full">
<ChartContainer config={config} className="mx-auto w-full h-full aspect-square max-h-[250px]">
<PieChart>
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
<Pie
data={data}
dataKey={dataKey}
nameKey={nameKey}
innerRadius={60}
strokeWidth={5}
isAnimationActive={false}
>
<Label
content={({ viewBox }) => (
<PieInnerLabel
innerLabel={innerLabel}
innerSubLabel={innerSubLabel}
viewBox={viewBox}
/>
)}
/>
</Pie>
</PieChart>
</ChartContainer>
</div>
);
};
/**
* A donut chart component that displays data in a circular format with an optional inner label.
* @param data - Array of data points to display
* @param config - Chart configuration
* @param innerLabel - Optional text to display in the center
* @param innerSubLabel - Optional secondary text below the inner label
* @param dataKey - Key to use for data values
* @param nameKey - Key to use for segment names
* @param colors - Optional array of colors to override defaults
*/
const DonutChart = ({
data,
config,
innerLabel,
innerSubLabel,
dataKey,
nameKey,
colors = DEFAULT_COLORS,
}: {
data: any[];
config: ChartConfig;
innerLabel?: string;
innerSubLabel?: string;
dataKey: string;
nameKey: string;
colors?: string[];
}) => {
if (!data?.length) {
return (
<div className="flex items-center justify-center w-full h-full text-muted-foreground">
No data available
</div>
);
}
return (
<div className="flex flex-col items-center w-full h-full">
<ChartContainer config={config} className="mx-auto w-full h-full aspect-square max-h-[250px]">
<PieChart>
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
<Pie
data={data}
dataKey={dataKey}
nameKey={nameKey}
innerRadius={60}
strokeWidth={5}
isAnimationActive={false}
fill={NO_DATA_COLOR}
>
{data.map((_, index) => (
<Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
))}
<Label
content={({ viewBox }) => (
<PieInnerLabel
innerLabel={innerLabel}
innerSubLabel={innerSubLabel}
viewBox={viewBox}
/>
)}
/>
</Pie>
</PieChart>
</ChartContainer>
</div>
);
};


export default DonutChart;
4 changes: 2 additions & 2 deletions frontend/app/element/markdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@
ul {
list-style-type: disc;
list-style-position: outside;
margin-left: 1.143em;
margin-left: 1em;
}

ol {
list-style-position: outside;
margin-left: 1.357em;
margin-left: 1.2em;
}

blockquote {
Expand Down
Loading
Loading