From 8014b513d0ead244a6fca68df764cc5532435db4 Mon Sep 17 00:00:00 2001 From: i-subham Date: Wed, 2 Jul 2025 18:44:00 +0530 Subject: [PATCH 1/8] implement customPalette prop across all chart components Add support for custom color palettes in all chart components, allowing users to override default theme colors with brand-specific or custom color schemes. Changes: - Add customPalette?: string[] prop to all chart component interfaces - Update color distribution logic to prioritize customPalette over theme - Add customPalette to argTypes in all story files - Create CustomPalette stories demonstrating usage - Update documentation to mention customPalette feature - Ensure consistent implementation across: - AreaChart - BarChart - LineChart - PieChart - RadarChart - RadialChart The customPalette prop overrides the theme prop when provided, giving users full control over chart colors while maintaining the existing theme system as fallback. Usage: --- .../components/Charts/AreaChart/AreaChart.tsx | 9 +- .../AreaChart/stories/areaChart.stories.tsx | 108 +++++++++- .../components/Charts/BarChart/BarChart.tsx | 9 +- .../BarChart/stories/barChart.stories.tsx | 114 +++++++++- .../components/Charts/LineChart/LineChart.tsx | 9 +- .../LineChart/stories/lineChart.stories.tsx | 116 +++++++++- .../components/Charts/PieChart/PieChart.tsx | 90 ++++---- .../PieChart/components/PieChartRenderers.tsx | 82 ------- .../PieChart/stories/PieChart.stories.tsx | 74 +++++-- .../Charts/RadarChart/RadarChart.tsx | 9 +- .../RadarChart/stories/RadarChart.stories.tsx | 118 ++++++++++- .../Charts/RadialChart/RadialChart.tsx | 91 ++++---- .../components/RadialChartRenderers.tsx | 82 ------- .../stories/RadialChart.stories.tsx | 200 ++++++++++++++---- .../shared/StackedLegend/StackedLegend.tsx | 6 +- .../components/Charts/utils/PalletUtils.ts | 18 +- 16 files changed, 783 insertions(+), 352 deletions(-) delete mode 100644 js/packages/react-ui/src/components/Charts/PieChart/components/PieChartRenderers.tsx delete mode 100644 js/packages/react-ui/src/components/Charts/RadialChart/components/RadialChartRenderers.tsx diff --git a/js/packages/react-ui/src/components/Charts/AreaChart/AreaChart.tsx b/js/packages/react-ui/src/components/Charts/AreaChart/AreaChart.tsx index ff3738c9f..3430361a6 100644 --- a/js/packages/react-ui/src/components/Charts/AreaChart/AreaChart.tsx +++ b/js/packages/react-ui/src/components/Charts/AreaChart/AreaChart.tsx @@ -43,6 +43,7 @@ export interface AreaChartProps { data: T; categoryKey: keyof T[number]; theme?: PaletteName; + customPalette?: string[]; variant?: AreaChartVariant; grid?: boolean; legend?: boolean; @@ -62,6 +63,7 @@ const AreaChartComponent = ({ data, categoryKey, theme = "ocean", + customPalette, variant = "natural", grid = true, icons = {}, @@ -81,9 +83,12 @@ const AreaChartComponent = ({ const transformedKeys = useTransformedKeys(dataKeys); const colors = useMemo(() => { - const palette = getPalette(theme); + const palette = + customPalette && customPalette.length > 0 + ? { name: "custom", colors: customPalette } + : getPalette(theme); return getDistributedColors(palette, dataKeys.length); - }, [theme, dataKeys.length]); + }, [theme, customPalette, dataKeys.length]); const chartConfig: ChartConfig = useMemo(() => { return get2dChartConfig(dataKeys, colors, transformedKeys, undefined, icons); diff --git a/js/packages/react-ui/src/components/Charts/AreaChart/stories/areaChart.stories.tsx b/js/packages/react-ui/src/components/Charts/AreaChart/stories/areaChart.stories.tsx index 283cd5651..b9cf4cecd 100644 --- a/js/packages/react-ui/src/components/Charts/AreaChart/stories/areaChart.stories.tsx +++ b/js/packages/react-ui/src/components/Charts/AreaChart/stories/areaChart.stories.tsx @@ -13,6 +13,19 @@ import { useState } from "react"; import { Card } from "../../../Card"; import { AreaChart, AreaChartProps } from "../AreaChart"; +const customColorPalette = [ + "#0A0E60", + "#14197B", + "#272DA6", + "#383FC9", + "#444CE7", + "#5F67F4", + "#7884FF", + "#97A9FF", + "#B4C6FF", + "#CBD7FF", +]; + // 🔥 COMPREHENSIVE DATA VARIATIONS - Designed to test label collision scenarios const dataVariations = { default: [ @@ -545,7 +558,8 @@ const salesData = [ }, }, theme: { - description: "Specifies the color palette for the chart's areas, tooltips, and legend.", + description: + "Specifies the color palette for the chart's areas, tooltips, and legend. Ignored when customPalette is provided.", control: "select", options: ["ocean", "orchid", "emerald", "sunset", "spectrum", "vivid"], table: { @@ -553,6 +567,15 @@ const salesData = [ category: "🎨 Visual Styling", }, }, + customPalette: { + description: + "Custom array of colors to use instead of the theme palette. Overrides the theme prop when provided.", + control: "object", + table: { + type: { summary: "string[]" }, + category: "🎨 Visual Styling", + }, + }, variant: { description: "Determines the area interpolation method, affecting the shape of the curves.", control: "radio", @@ -674,6 +697,7 @@ export const DataExplorer: Story = { showYAxis: true, xAxisLabel: "Time Period", yAxisLabel: "Values", + customPalette: customColorPalette, // height: 300, }, render: (args: any) => { @@ -1076,6 +1100,88 @@ export const ExpandCollapseMarketingStory: Story = { }, }; +/** + * ## Custom Palette + * + * This story demonstrates how to use the customPalette prop to provide your own color scheme + * for the chart. This is useful when you need to match your brand colors or create + * specific visual themes. + */ + +export const CustomPaletteStory: Story = { + name: "🎨 Custom Palette", + args: { + data: dataVariations.default as any, + categoryKey: "month" as any, + customPalette: customColorPalette, + theme: "ocean", // This will be overridden by customPalette + variant: "natural", + grid: true, + legend: true, + isAnimationActive: true, + showYAxis: true, + xAxisLabel: "Month", + yAxisLabel: "Traffic", + }, + render: (args: any) => ( +
+
+

🎨 Custom Color Palette

+

+ This chart uses a custom color palette instead of the default theme colors. +

+
+ {args.customPalette?.map((color: string, index: number) => ( +
+
+ {color} +
+ ))} +
+
+ + + +
+ ), + parameters: { + docs: { + description: { + story: + 'Demonstrates how to use the `customPalette` prop to provide your own color scheme for the chart. When `customPalette` is provided, it overrides the `theme` prop and uses your specified colors instead of the predefined theme palettes.\n\n**Key Features:**\n- 🎨 **Custom Colors**: Override default theme colors with your own palette\n- 🔄 **Theme Override**: The `theme` prop is ignored when `customPalette` is provided\n- 📊 **Consistent Distribution**: Colors are distributed evenly across data series\n- 🎯 **Brand Matching**: Perfect for matching your application\'s brand colors\n\n**Usage:**\n```tsx\n\n```', + }, + }, + }, +}; + /** * ## Responsive Behavior Demo * diff --git a/js/packages/react-ui/src/components/Charts/BarChart/BarChart.tsx b/js/packages/react-ui/src/components/Charts/BarChart/BarChart.tsx index 3a07e5b9b..a601d0358 100644 --- a/js/packages/react-ui/src/components/Charts/BarChart/BarChart.tsx +++ b/js/packages/react-ui/src/components/Charts/BarChart/BarChart.tsx @@ -45,6 +45,7 @@ export interface BarChartProps { data: T; categoryKey: keyof T[number]; theme?: PaletteName; + customPalette?: string[]; variant?: BarChartVariant; grid?: boolean; radius?: number; @@ -69,6 +70,7 @@ const BarChartComponent = ({ data, categoryKey, theme = "ocean", + customPalette, variant = "grouped", grid = true, icons = {}, @@ -89,9 +91,12 @@ const BarChartComponent = ({ const transformedKeys = useTransformedKeys(dataKeys); const colors = useMemo(() => { - const palette = getPalette(theme); + const palette = + customPalette && customPalette.length > 0 + ? { name: "custom", colors: customPalette } + : getPalette(theme); return getDistributedColors(palette, dataKeys.length); - }, [theme, dataKeys.length]); + }, [theme, customPalette, dataKeys.length]); const chartConfig: ChartConfig = useMemo(() => { return get2dChartConfig(dataKeys, colors, transformedKeys, undefined, icons); diff --git a/js/packages/react-ui/src/components/Charts/BarChart/stories/barChart.stories.tsx b/js/packages/react-ui/src/components/Charts/BarChart/stories/barChart.stories.tsx index 9a5939059..65ed580a6 100644 --- a/js/packages/react-ui/src/components/Charts/BarChart/stories/barChart.stories.tsx +++ b/js/packages/react-ui/src/components/Charts/BarChart/stories/barChart.stories.tsx @@ -522,7 +522,7 @@ const icons = { * - **Responsive Design**: Adjusts gracefully to the size of its container. * * ### Customization - * - **Theming**: Six built-in color palettes. + * - **Theming**: Six built-in color palettes, or use custom colors with `customPalette`. * - **Bar Styling**: Customize the corner radius of the bars. * - **Axis and Grid Control**: Toggle visibility of axes and grid lines. */ @@ -551,6 +551,13 @@ const monthlyData = [ categoryKey="month" theme="ocean" /> + +// With custom colors + \`\`\` ## Data Structure Requirements @@ -599,7 +606,8 @@ const salesData = [ }, }, theme: { - description: "Specifies the color palette for the chart's bars, tooltips, and legend.", + description: + "Specifies the color palette for the chart's bars, tooltips, and legend. Ignored when customPalette is provided.", control: "select", options: ["ocean", "orchid", "emerald", "sunset", "spectrum", "vivid"], table: { @@ -607,6 +615,15 @@ const salesData = [ category: "🎨 Visual Styling", }, }, + customPalette: { + description: + "Custom array of colors to use instead of the theme palette. Overrides the theme prop when provided.", + control: "object", + table: { + type: { summary: "string[]" }, + category: "🎨 Visual Styling", + }, + }, variant: { description: "Defines how multiple data series are displayed: `grouped` (side-by-side) or `stacked` (on top of each other).", @@ -1038,6 +1055,99 @@ export const ExpandCollapseMarketingStory: Story = { }, }; +/** + * ## Custom Palette + * + * This story demonstrates how to use the customPalette prop to provide your own color scheme + * for the chart. This is useful when you need to match your brand colors or create + * specific visual themes. + */ +export const CustomPaletteStory: Story = { + name: "🎨 Custom Palette", + args: { + data: dataVariations.default as any, + categoryKey: "month" as any, + customPalette: [ + "#0A0E60", + "#14197B", + "#272DA6", + "#383FC9", + "#444CE7", + "#5F67F4", + "#7884FF", + "#97A9FF", + "#B4C6FF", + "#CBD7FF", + ], + theme: "ocean", // This will be overridden by customPalette + variant: "grouped", + radius: 4, + grid: true, + legend: true, + isAnimationActive: true, + showYAxis: true, + xAxisLabel: "Month", + yAxisLabel: "Traffic", + }, + render: (args: any) => ( +
+
+

🎨 Custom Color Palette

+

+ This chart uses a custom color palette instead of the default theme colors. +

+
+ {args.customPalette?.map((color: string, index: number) => ( +
+
+ {color} +
+ ))} +
+
+ + + +
+ ), + parameters: { + docs: { + description: { + story: + 'Demonstrates how to use the `customPalette` prop to provide your own color scheme for the chart. When `customPalette` is provided, it overrides the `theme` prop and uses your specified colors instead of the predefined theme palettes.\n\n**Key Features:**\n- 🎨 **Custom Colors**: Override default theme colors with your own palette\n- 🔄 **Theme Override**: The `theme` prop is ignored when `customPalette` is provided\n- 📊 **Consistent Distribution**: Colors are distributed evenly across data series\n- 🎯 **Brand Matching**: Perfect for matching your application\'s brand colors\n\n**Usage:**\n```tsx\n\n```', + }, + }, + }, +}; + /** * ## Responsive Behavior Demo * diff --git a/js/packages/react-ui/src/components/Charts/LineChart/LineChart.tsx b/js/packages/react-ui/src/components/Charts/LineChart/LineChart.tsx index 1affc555e..d2903180c 100644 --- a/js/packages/react-ui/src/components/Charts/LineChart/LineChart.tsx +++ b/js/packages/react-ui/src/components/Charts/LineChart/LineChart.tsx @@ -41,6 +41,7 @@ export interface LineChartProps { data: T; categoryKey: keyof T[number]; theme?: PaletteName; + customPalette?: string[]; variant?: LineChartVariant; grid?: boolean; legend?: boolean; @@ -61,6 +62,7 @@ export const LineChart = ({ data, categoryKey, theme = "ocean", + customPalette, variant = "natural", grid = true, icons = {}, @@ -81,9 +83,12 @@ export const LineChart = ({ const transformedKeys = useTransformedKeys(dataKeys); const colors = useMemo(() => { - const palette = getPalette(theme); + const palette = + customPalette && customPalette.length > 0 + ? { name: "custom", colors: customPalette } + : getPalette(theme); return getDistributedColors(palette, dataKeys.length); - }, [theme, dataKeys.length]); + }, [theme, customPalette, dataKeys.length]); const chartConfig: ChartConfig = useMemo(() => { return get2dChartConfig(dataKeys, colors, transformedKeys, undefined, icons); diff --git a/js/packages/react-ui/src/components/Charts/LineChart/stories/lineChart.stories.tsx b/js/packages/react-ui/src/components/Charts/LineChart/stories/lineChart.stories.tsx index ec03c1a4c..29f0aaa23 100644 --- a/js/packages/react-ui/src/components/Charts/LineChart/stories/lineChart.stories.tsx +++ b/js/packages/react-ui/src/components/Charts/LineChart/stories/lineChart.stories.tsx @@ -13,6 +13,19 @@ import { useState } from "react"; import { Card } from "../../../Card"; import { LineChart, LineChartProps } from "../LineChart"; +const customColorPalette = [ + "#0A0E60", + "#14197B", + "#272DA6", + "#383FC9", + "#444CE7", + "#5F67F4", + "#7884FF", + "#97A9FF", + "#B4C6FF", + "#CBD7FF", +]; + // 🔥 COMPREHENSIVE DATA VARIATIONS - Designed to test label collision scenarios const dataVariations = { default: [ @@ -468,7 +481,7 @@ const icons = { * - **Responsive Design**: Fluidly adjusts to any container size, from small widgets to large dashboards. * * ### Customization - * - **Theming**: Six pre-built color palettes to fit your application's design. + * - **Theming**: Six pre-built color palettes to fit your application's design, or use custom colors with `customPalette`. * - **Line Styles**: Supports `linear`, `natural` (smooth), and `step` variants. * - **Styling Options**: Control stroke width, grid visibility, and more. */ @@ -497,6 +510,13 @@ const timeSeriesData = [ categoryKey="month" theme="ocean" /> + +// With custom colors + \`\`\` ## Data Structure Requirements @@ -549,7 +569,8 @@ const salesData = [ }, }, theme: { - description: "Specifies the color palette for the chart's lines, tooltips, and legend.", + description: + "Specifies the color palette for the chart's lines, tooltips, and legend. Ignored when customPalette is provided.", control: "select", options: ["ocean", "orchid", "emerald", "sunset", "spectrum", "vivid"], table: { @@ -557,6 +578,15 @@ const salesData = [ category: "🎨 Visual Styling", }, }, + customPalette: { + description: + "Custom array of colors to use instead of the theme palette. Overrides the theme prop when provided.", + control: "object", + table: { + type: { summary: "string[]" }, + category: "🎨 Visual Styling", + }, + }, variant: { description: "Determines the line interpolation method, affecting the shape of the lines.", control: "radio", @@ -947,6 +977,88 @@ export const VariantComparisonStory: Story = { }, }; +/** + * ## Custom Palette + * + * This story demonstrates how to use the customPalette prop to provide your own color scheme + * for the chart. This is useful when you need to match your brand colors or create + * specific visual themes. + */ +export const CustomPaletteStory: Story = { + name: "🎨 Custom Palette", + args: { + data: dataVariations.default as any, + categoryKey: "month" as any, + customPalette: customColorPalette, + theme: "ocean", // This will be overridden by customPalette + variant: "natural", + grid: true, + legend: true, + isAnimationActive: true, + showYAxis: true, + strokeWidth: 2, + xAxisLabel: "Month", + yAxisLabel: "Traffic", + }, + render: (args: any) => ( +
+
+

🎨 Custom Color Palette

+

+ This chart uses a custom color palette instead of the default theme colors. +

+
+ {args.customPalette?.map((color: string, index: number) => ( +
+
+ {color} +
+ ))} +
+
+ + + +
+ ), + parameters: { + docs: { + description: { + story: + 'Demonstrates how to use the `customPalette` prop to provide your own color scheme for the chart. When `customPalette` is provided, it overrides the `theme` prop and uses your specified colors instead of the predefined theme palettes.\n\n**Key Features:**\n- 🎨 **Custom Colors**: Override default theme colors with your own palette\n- 🔄 **Theme Override**: The `theme` prop is ignored when `customPalette` is provided\n- 📊 **Consistent Distribution**: Colors are distributed evenly across data series\n- 🎯 **Brand Matching**: Perfect for matching your application\'s brand colors\n\n**Usage:**\n```tsx\n\n```', + }, + }, + }, +}; + /** * ## Legend Expand/Collapse * diff --git a/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx b/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx index b7a55b9e2..2bf266d8f 100644 --- a/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx +++ b/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx @@ -8,11 +8,6 @@ import { StackedLegend } from "../shared/StackedLegend/StackedLegend.js"; import { LegendItem } from "../types/Legend.js"; import { getCategoricalChartConfig } from "../utils/dataUtils.js"; import { getDistributedColors, getPalette, PaletteName } from "../utils/PalletUtils.js"; -import { - createGradientDefinitions, - MAX_CHART_SIZE, - MIN_CHART_SIZE, -} from "./components/PieChartRenderers.js"; import { PieChartData } from "./types/index.js"; import { calculateTwoLevelChartDimensions, @@ -24,16 +19,12 @@ import { useChartHover, } from "./utils/PieChartUtils.js"; -interface GradientColor { - start?: string; - end?: string; -} - export interface PieChartProps { data: T; categoryKey: keyof T[number]; dataKey: keyof T[number]; theme?: PaletteName; + customPalette?: string[]; variant?: "pie" | "donut"; format?: "percentage" | "number"; legend?: boolean; @@ -42,8 +33,6 @@ export interface PieChartProps { appearance?: "circular" | "semiCircular"; cornerRadius?: number; paddingAngle?: number; - useGradients?: boolean; - gradientColors?: GradientColor[]; onMouseEnter?: (data: any, index: number) => void; onMouseLeave?: () => void; onClick?: (data: any, index: number) => void; @@ -51,12 +40,15 @@ export interface PieChartProps { } const STACKED_LEGEND_BREAKPOINT = 400; +const MIN_CHART_SIZE = 150; +const MAX_CHART_SIZE = 500; const PieChartComponent = ({ data, categoryKey, dataKey, theme = "ocean", + customPalette, variant = "pie", format = "number", legend = true, @@ -65,8 +57,6 @@ const PieChartComponent = ({ appearance = "circular", cornerRadius = 0, paddingAngle = 0, - useGradients = false, - gradientColors, onMouseEnter, onMouseLeave, onClick, @@ -85,9 +75,15 @@ const PieChartComponent = ({ // The data that is processed and rendered in the chart const processedData = useMemo(() => data, [data]); + // Sort data by value (highest to lowest) for pie chart rendering + const sortedProcessedData = useMemo( + () => [...processedData].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), + [processedData, dataKey], + ); + const categories = useMemo( - () => processedData.map((item) => String(item[categoryKey])), - [processedData, categoryKey], + () => sortedProcessedData.map((item) => String(item[categoryKey])), + [sortedProcessedData, categoryKey], ); const transformedKeys = useTransformedKeys(categories); @@ -121,13 +117,13 @@ const PieChartComponent = ({ // Memoize expensive data transformations and configurations const transformedData = useMemo( - () => transformDataWithPercentages(processedData, dataKey), - [processedData, dataKey], + () => transformDataWithPercentages(sortedProcessedData as T, dataKey), + [sortedProcessedData, dataKey], ); const chartConfig = useMemo( - () => getCategoricalChartConfig(processedData, categoryKey, theme, transformedKeys), - [processedData, categoryKey, theme, transformedKeys], + () => getCategoricalChartConfig(sortedProcessedData as T, categoryKey, theme, transformedKeys), + [sortedProcessedData, categoryKey, theme, transformedKeys], ); const animationConfig = useMemo( @@ -145,53 +141,46 @@ const PieChartComponent = ({ [cornerRadius, variant, paddingAngle], ); - const palette = useMemo(() => getPalette(theme), [theme]); + const palette = useMemo(() => { + if (customPalette && customPalette.length > 0) { + return { name: "custom", colors: customPalette }; + } + return getPalette(theme); + }, [theme, customPalette]); + const colors = useMemo( - () => getDistributedColors(palette, processedData.length), - [palette, processedData.length], + () => getDistributedColors(palette, sortedProcessedData.length), + [palette, sortedProcessedData.length], ); - const gradientDefinitions = useMemo(() => { - if (!useGradients) return null; - const chartColors = Object.values(chartConfig) - .map((config) => config.color) - .filter((color): color is string => color !== undefined); - return createGradientDefinitions(transformedData, chartColors, gradientColors); - }, [useGradients, chartConfig, transformedData, gradientColors]); - const legendItems = useMemo( () => - processedData.map((item, index) => ({ + sortedProcessedData.map((item, index) => ({ key: String(item[categoryKey]), label: String(item[categoryKey]), value: Number(item[dataKey]), color: colors[index] || "#000000", })), - [processedData, categoryKey, dataKey, colors], + [sortedProcessedData, categoryKey, dataKey, colors], ); const defaultLegendItems = useMemo((): LegendItem[] => { return legendItems.map(({ key, label, color }) => ({ key, label, color })); }, [legendItems]); - const sortedData = useMemo( - () => [...processedData].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), - [processedData, dataKey], - ); - const handleLegendItemHover = useCallback( (index: number | null) => { if (legendVariant !== "stacked") return; if (index !== null) { - const item = sortedData[index]; + const item = sortedProcessedData[index]; if (item) { const categoryValue = String(item[categoryKey]); setHoveredLegendKey(categoryValue); - const processedIndex = processedData.findIndex( - (d) => String(d[categoryKey]) === categoryValue, + const transformedIndex = transformedData.findIndex( + (d) => String((d as any)[categoryKey]) === categoryValue, ); - if (processedIndex !== -1) { - handleMouseEnter(processedData[processedIndex], processedIndex); + if (transformedIndex !== -1) { + handleMouseEnter(transformedData[transformedIndex], transformedIndex); } } } else { @@ -199,7 +188,14 @@ const PieChartComponent = ({ handleMouseLeave(); } }, - [sortedData, categoryKey, processedData, handleMouseEnter, handleMouseLeave, legendVariant], + [ + sortedProcessedData, + categoryKey, + transformedData, + handleMouseEnter, + handleMouseLeave, + legendVariant, + ], ); const handleChartMouseEnter = useCallback( @@ -314,7 +310,7 @@ const PieChartComponent = ({ const transformedKey = transformedKeys[categoryValue] ?? categoryValue; const config = chartConfig[transformedKey]; const hoverStyles = getHoverStyles(index, activeIndex); - const fill = useGradients ? `url(#gradient-${index})` : config?.color || colors[index]; + const fill = config?.color || colors[index]; return ; })} , @@ -332,7 +328,7 @@ const PieChartComponent = ({ const transformedKey = transformedKeys[categoryValue] ?? categoryValue; const config = chartConfig[transformedKey]; const hoverStyles = getHoverStyles(index, activeIndex); - const fill = useGradients ? `url(#gradient-${index})` : config?.color || colors[index]; + const fill = config?.color || colors[index]; return ; })} @@ -345,7 +341,6 @@ const PieChartComponent = ({ categoryKey, chartConfig, activeIndex, - useGradients, colors, transformedKeys, ]); @@ -410,7 +405,6 @@ const PieChartComponent = ({ } /> - {gradientDefinitions && {gradientDefinitions}} {renderPieCharts()} diff --git a/js/packages/react-ui/src/components/Charts/PieChart/components/PieChartRenderers.tsx b/js/packages/react-ui/src/components/Charts/PieChart/components/PieChartRenderers.tsx deleted file mode 100644 index be1ced441..000000000 --- a/js/packages/react-ui/src/components/Charts/PieChart/components/PieChartRenderers.tsx +++ /dev/null @@ -1,82 +0,0 @@ -export const MIN_CHART_SIZE = 150; -export const MAX_CHART_SIZE = 500; - -/** - * Creates gradient definitions for pie chart segments - * @param data - The chart data array - * @param colors - Array of base colors for segments - * @param gradientColors - Optional array of gradient color configurations - * @returns Array of gradient definition elements - */ -export const createGradientDefinitions = ( - data: any[], - colors: string[], - gradientColors?: Array<{ start?: string; end?: string }>, -) => { - return data.map((_, index) => { - const defaultBaseColor = colors[index] || "#000000"; - - // Get the base color from gradientColors or fallback to default - let baseColor = defaultBaseColor; - let secondaryColor = colors[data.length - index - 1] || "#ffffff"; - - if (gradientColors?.[index]) { - // If both colors are provided, use them - if (gradientColors[index].start && gradientColors[index].end) { - baseColor = gradientColors[index].start; - secondaryColor = gradientColors[index].end; - } - // If only start color is provided, create a darker version for end - else if (gradientColors[index].start) { - baseColor = gradientColors[index].start; - secondaryColor = adjustColorBrightness(baseColor, -20); - } - // If only end color is provided, create a lighter version for start - else if (gradientColors[index].end) { - baseColor = adjustColorBrightness(gradientColors[index].end, 20); - secondaryColor = gradientColors[index].end; - } - } - - return ( - - - - - - - ); - }); -}; - -/** - * Adjusts the brightness of a hex color - * @param hex - The hex color to adjust - * @param percent - The percentage to adjust brightness by (-100 to 100) - * @returns The adjusted hex color - */ -export const adjustColorBrightness = (hex: string, percent: number): string => { - if (!hex || !hex.startsWith("#")) { - return hex; - } - try { - const num = parseInt(hex.replace("#", ""), 16); - const amt = Math.round(2.55 * percent); - const R = Math.min(255, Math.max(0, (num >> 16) + amt)); - const G = Math.min(255, Math.max(0, ((num >> 8) & 0x00ff) + amt)); - const B = Math.min(255, Math.max(0, (num & 0x0000ff) + amt)); - return "#" + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); - } catch (error) { - console.warn("Invalid color format:", hex); - console.warn(error); - return hex; - } -}; diff --git a/js/packages/react-ui/src/components/Charts/PieChart/stories/PieChart.stories.tsx b/js/packages/react-ui/src/components/Charts/PieChart/stories/PieChart.stories.tsx index fcd180e9a..adbb04f21 100644 --- a/js/packages/react-ui/src/components/Charts/PieChart/stories/PieChart.stories.tsx +++ b/js/packages/react-ui/src/components/Charts/PieChart/stories/PieChart.stories.tsx @@ -28,14 +28,17 @@ const comprehensiveData = [ { category: "Software", sales: 10500 }, ]; -const gradientPalette = [ - { start: "#FF6B6B", end: "#FF8E8E" }, - { start: "#4ECDC4", end: "#6ED7D0" }, - { start: "#45B7D1", end: "#6BC5DB" }, - { start: "#96CEB4", end: "#B4DCC9" }, - { start: "#FFEEAD", end: "#FFF4C4" }, - { start: "#D4A5A5", end: "#E5BDBD" }, - { start: "#9B59B6", end: "#B07CC7" }, +const customColorPalette = [ + "#0A0E60", + "#14197B", + "#272DA6", + "#383FC9", + "#444CE7", + "#5F67F4", + "#7884FF", + "#97A9FF", + "#B4C6FF", + "#CBD7FF", ]; /** @@ -70,7 +73,7 @@ const gradientPalette = [ * ### Customization * - **Theming**: Six pre-built color palettes to match your application's design * - **Styling Options**: Control corner radius, padding between slices, and more - * - **Gradient Fills**: Apply beautiful gradients for an enhanced visual appeal + */ const meta: Meta> = { @@ -164,6 +167,15 @@ const salesData = [ category: "🎨 Visual Styling", }, }, + customPalette: { + description: + "An array of color strings to use as a custom palette for the chart. This overrides the `theme` prop.", + control: "object", + table: { + type: { summary: "string[]" }, + category: "🎨 Visual Styling", + }, + }, appearance: { description: "The overall shape of the chart: a full circle or a semicircle.", control: "radio", @@ -237,15 +249,6 @@ const salesData = [ category: "🎨 Visual Styling", }, }, - useGradients: { - description: "Applies gradient fills to the pie slices instead of solid colors.", - control: "boolean", - table: { - type: { summary: "boolean" }, - defaultValue: { summary: "false" }, - category: "🎨 Visual Styling", - }, - }, }, } satisfies Meta; @@ -279,8 +282,7 @@ export const DefaultConfiguration: Story = { appearance: "circular", cornerRadius: 0, paddingAngle: 0, - useGradients: false, - gradientColors: gradientPalette, + customPalette: customColorPalette, }, render: (args: any) => ( @@ -345,6 +347,37 @@ export const LayoutAndVariantOptions: Story = { ), }; +/** + * ## Custom Palette + * + * This example demonstrates how to use a custom color palette. + * + * **Feature Highlight:** + * - The `customPalette` prop allows you to define your own array of colors. + */ +export const CustomPalette: Story = { + name: "🎨 Custom Palette", + args: { + data: monthlySalesData, + categoryKey: "month", + dataKey: "value", + variant: "donut", + legend: true, + legendVariant: "stacked", + cornerRadius: 4, + paddingAngle: 2, + customPalette: customColorPalette, + }, + render: (args: any) => ( + +

+ Chart with Custom Palette +

+ +
+ ), +}; + /** * ## Large Dataset with Carousel * @@ -387,7 +420,6 @@ export const ResponsiveDemo: Story = { legendVariant: "stacked", isAnimationActive: false, cornerRadius: 8, - useGradients: false, }, render: (args: any) => { const [dimensions, setDimensions] = useState<{ width: number; height: number | string }>({ diff --git a/js/packages/react-ui/src/components/Charts/RadarChart/RadarChart.tsx b/js/packages/react-ui/src/components/Charts/RadarChart/RadarChart.tsx index c94b7c1ed..74c8cc416 100644 --- a/js/packages/react-ui/src/components/Charts/RadarChart/RadarChart.tsx +++ b/js/packages/react-ui/src/components/Charts/RadarChart/RadarChart.tsx @@ -18,6 +18,7 @@ export interface RadarChartProps { data: T; categoryKey: keyof T[number]; theme?: "ocean" | "orchid" | "emerald" | "sunset" | "spectrum" | "vivid"; + customPalette?: string[]; variant?: "line" | "area"; grid?: boolean; legend?: boolean; @@ -31,6 +32,7 @@ const RadarChartComponent = ({ data, categoryKey, theme = "ocean", + customPalette, variant = "line", grid = true, legend = true, @@ -46,9 +48,12 @@ const RadarChartComponent = ({ const transformedKeys = useTransformedKeys(dataKeys); const colors = useMemo(() => { - const palette = getPalette(theme); + const palette = + customPalette && customPalette.length > 0 + ? { name: "custom", colors: customPalette } + : getPalette(theme); return getDistributedColors(palette, dataKeys.length); - }, [theme, dataKeys.length]); + }, [theme, customPalette, dataKeys.length]); // Create Config const chartConfig: ChartConfig = useMemo(() => { diff --git a/js/packages/react-ui/src/components/Charts/RadarChart/stories/RadarChart.stories.tsx b/js/packages/react-ui/src/components/Charts/RadarChart/stories/RadarChart.stories.tsx index 18759c702..446b1977c 100644 --- a/js/packages/react-ui/src/components/Charts/RadarChart/stories/RadarChart.stories.tsx +++ b/js/packages/react-ui/src/components/Charts/RadarChart/stories/RadarChart.stories.tsx @@ -104,6 +104,14 @@ import { RadarChart } from '@crayon-ui/react-ui/Charts/RadarChart'; theme="ocean" variant="line" /> + +// With custom colors + \`\`\` ## Data Structure Requirements @@ -125,6 +133,7 @@ const exampleData = [ - **Multiple Data Series**: Compare several datasets on the same chart. - **Two Visual Variants**: Choose between 'line' and 'area' styles. - **Customizable Appearance**: Control colors, stroke width, and area opacity. +- **Custom Color Palettes**: Use predefined themes or provide your own custom colors. - **Interactive Legend**: Toggle visibility of data series. - **Icon Support**: Add custom icons to legend items for better visual identification. - **Animation**: Smooth animations for loading and data transitions. @@ -167,7 +176,7 @@ const exampleData = [ }, theme: { description: ` -**Color Theme Selection.** Choose from professionally designed color palettes: +**Color Theme Selection.** Choose from professionally designed color palettes. Ignored when customPalette is provided. - **ocean**: Cool blues and teals (professional, corporate) - **orchid**: Purple and pink tones (creative, modern) @@ -183,6 +192,15 @@ const exampleData = [ category: "🎨 Visual Styling", }, }, + customPalette: { + description: + "Custom array of colors to use instead of the theme palette. Overrides the theme prop when provided.", + control: "object", + table: { + type: { summary: "string[]" }, + category: "🎨 Visual Styling", + }, + }, variant: { description: ` **Chart Style Variant:** @@ -584,3 +602,101 @@ export const ThemeShowcase: Story = { }, }, }; + +/** + * ## Custom Palette + * + * This story demonstrates how to use the customPalette prop to provide your own color scheme + * for the chart. This is useful when you need to match your brand colors or create + * specific visual themes. + */ +export const CustomPaletteStory: Story = { + name: "🎨 Custom Palette", + args: { + data: dataVariations.productFeatures, + categoryKey: "metric", + customPalette: [ + "#0A0E60", + "#14197B", + "#272DA6", + "#383FC9", + "#444CE7", + "#5F67F4", + "#7884FF", + "#97A9FF", + "#B4C6FF", + "#CBD7FF", + ], + theme: "ocean", // This will be overridden by customPalette + variant: "area", + grid: true, + legend: true, + strokeWidth: 3, + isAnimationActive: true, + }, + render: (args: any) => ( +
+
+

🎨 Custom Color Palette

+

+ This radar chart uses a custom color palette instead of the default theme colors. +

+
+ {args.customPalette?.map((color: string, index: number) => ( +
+
+ {color} +
+ ))} +
+
+ +
+

+ Team Performance with Custom Colors +

+

+ Using custom brand colors to match your application's design system. +

+
+ +
+
+ ), + parameters: { + docs: { + description: { + story: + 'Demonstrates how to use the `customPalette` prop to provide your own color scheme for the radar chart. When `customPalette` is provided, it overrides the `theme` prop and uses your specified colors instead of the predefined theme palettes.\n\n**Key Features:**\n- 🎨 **Custom Colors**: Override default theme colors with your own palette\n- 🔄 **Theme Override**: The `theme` prop is ignored when `customPalette` is provided\n- 📊 **Consistent Distribution**: Colors are distributed evenly across data series\n- 🎯 **Brand Matching**: Perfect for matching your application\'s brand colors\n\n**Usage:**\n```tsx\n\n```', + }, + }, + }, +}; diff --git a/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx b/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx index 91ea4a737..e6916b949 100644 --- a/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx +++ b/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx @@ -8,11 +8,6 @@ import { StackedLegend } from "../shared/StackedLegend/StackedLegend"; import { LegendItem } from "../types/Legend"; import { getCategoricalChartConfig } from "../utils/dataUtils"; import { getDistributedColors, getPalette, PaletteName } from "../utils/PalletUtils"; -import { - createRadialGradientDefinitions, - MAX_CHART_SIZE, - MIN_CHART_SIZE, -} from "./components/RadialChartRenderers"; import { RadialChartData } from "./types"; import { calculateRadialChartDimensions, @@ -23,16 +18,12 @@ import { useRadialChartHover, } from "./utils/RadialChartUtils"; -interface GradientColor { - start?: string; - end?: string; -} - export interface RadialChartProps { data: T; categoryKey: keyof T[number]; dataKey: keyof T[number]; theme?: PaletteName; + customPalette?: string[]; variant?: "semicircle" | "circular"; format?: "percentage" | "number"; legend?: boolean; @@ -40,8 +31,6 @@ export interface RadialChartProps { grid?: boolean; isAnimationActive?: boolean; cornerRadius?: number; - useGradients?: boolean; - gradientColors?: GradientColor[]; onMouseEnter?: (data: any, index: number) => void; onMouseLeave?: () => void; onClick?: (data: any, index: number) => void; @@ -49,12 +38,15 @@ export interface RadialChartProps { } const STACKED_LEGEND_BREAKPOINT = 400; +const MIN_CHART_SIZE = 150; +const MAX_CHART_SIZE = 500; export const RadialChart = ({ data, categoryKey, dataKey, theme = "ocean", + customPalette, variant = "circular", format = "number", legend = true, @@ -62,8 +54,6 @@ export const RadialChart = ({ grid = false, isAnimationActive = false, cornerRadius = 10, - useGradients = false, - gradientColors, onMouseEnter, onMouseLeave, onClick, @@ -82,9 +72,15 @@ export const RadialChart = ({ // The data that is processed and rendered in the chart const processedData = useMemo(() => data, [data]); + // Sort data by value (highest to lowest) for radial chart rendering + const sortedProcessedData = useMemo( + () => [...processedData].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), + [processedData, dataKey], + ); + const categories = useMemo( - () => processedData.map((item) => String(item[categoryKey])), - [processedData, categoryKey], + () => sortedProcessedData.map((item) => String(item[categoryKey])), + [sortedProcessedData, categoryKey], ); const transformedKeys = useTransformedKeys(categories); @@ -138,13 +134,13 @@ export const RadialChart = ({ // Memoize expensive data transformations and configurations const transformedData = useMemo( - () => transformRadialDataWithPercentages(processedData, dataKey, theme), - [processedData, dataKey, theme], + () => transformRadialDataWithPercentages(sortedProcessedData as T, dataKey, theme), + [sortedProcessedData, dataKey, theme], ); const chartConfig = useMemo( - () => getCategoricalChartConfig(processedData, categoryKey, theme, transformedKeys), - [processedData, categoryKey, theme, transformedKeys], + () => getCategoricalChartConfig(sortedProcessedData as T, categoryKey, theme, transformedKeys), + [sortedProcessedData, categoryKey, theme, transformedKeys], ); const animationConfig = useMemo( @@ -158,57 +154,48 @@ export const RadialChart = ({ ); // Get color palette and distribute colors - const palette = useMemo(() => getPalette(theme), [theme]); + const palette = useMemo(() => { + if (customPalette && customPalette.length > 0) { + return { name: "custom", colors: customPalette }; + } + return getPalette(theme); + }, [theme, customPalette]); const colors = useMemo( - () => getDistributedColors(palette, processedData.length), - [palette, processedData.length], + () => getDistributedColors(palette, sortedProcessedData.length), + [palette, sortedProcessedData.length], ); - // Memoize gradient definitions - const gradientDefinitions = useMemo(() => { - if (!useGradients) return null; - const chartColors = Object.values(chartConfig) - .map((config) => config.color) - .filter((color): color is string => color !== undefined); - return createRadialGradientDefinitions(transformedData, chartColors, gradientColors); - }, [useGradients, chartConfig, transformedData, gradientColors]); - // Create legend items for both variants const legendItems = useMemo( () => - processedData.map((item, index) => ({ + sortedProcessedData.map((item, index) => ({ key: String(item[categoryKey]), label: String(item[categoryKey]), value: Number(item[dataKey]), color: colors[index] || "#000000", })), - [processedData, categoryKey, dataKey, colors], + [sortedProcessedData, categoryKey, dataKey, colors], ); const defaultLegendItems = useMemo((): LegendItem[] => { return legendItems.map(({ key, label, color }) => ({ key, label, color })); }, [legendItems]); - // Memoize sorted data for legend hover handling - const sortedData = useMemo( - () => [...processedData].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), - [processedData, dataKey], - ); - // Handle legend item hover to highlight radial bar const handleLegendItemHover = useCallback( (index: number | null) => { if (legendVariant !== "stacked") return; if (index !== null) { - const item = sortedData[index]; + const item = sortedProcessedData[index]; if (item) { const categoryValue = String(item[categoryKey]); setHoveredLegendKey(categoryValue); - const originalIndex = processedData.findIndex( - (d) => String(d[categoryKey]) === categoryValue, + // Find the index in the transformed data (which is also sorted) + const transformedIndex = transformedData.findIndex( + (d) => String((d as any)[categoryKey]) === categoryValue, ); - if (originalIndex !== -1) { - handleMouseEnter(processedData[originalIndex], originalIndex); + if (transformedIndex !== -1) { + handleMouseEnter(transformedData[transformedIndex], transformedIndex); } } } else { @@ -216,7 +203,14 @@ export const RadialChart = ({ handleMouseLeave(); } }, - [sortedData, categoryKey, processedData, handleMouseEnter, handleMouseLeave, legendVariant], + [ + sortedProcessedData, + categoryKey, + transformedData, + handleMouseEnter, + handleMouseLeave, + legendVariant, + ], ); // Enhanced chart hover handlers @@ -331,7 +325,6 @@ export const RadialChart = ({ /> } /> - {gradientDefinitions && {gradientDefinitions}} ({ const categoryValue = String(entry[categoryKey as keyof typeof entry] || ""); const config = chartConfig[categoryValue]; const hoverStyles = getRadialHoverStyles(index, activeIndex); - const fill = useGradients - ? `url(#radial-gradient-${index})` - : config?.color || colors[index]; + const fill = config?.color || colors[index]; return ( ); diff --git a/js/packages/react-ui/src/components/Charts/RadialChart/components/RadialChartRenderers.tsx b/js/packages/react-ui/src/components/Charts/RadialChart/components/RadialChartRenderers.tsx deleted file mode 100644 index 8f79cd5b3..000000000 --- a/js/packages/react-ui/src/components/Charts/RadialChart/components/RadialChartRenderers.tsx +++ /dev/null @@ -1,82 +0,0 @@ -export const MIN_CHART_SIZE = 150; -export const MAX_CHART_SIZE = 500; - -/** - * Creates gradient definitions for radial chart segments - * @param data - The chart data array - * @param colors - Array of base colors for segments - * @param gradientColors - Optional array of gradient color configurations - * @returns Array of gradient definition elements - */ -export const createRadialGradientDefinitions = ( - data: any[], - colors: string[], - gradientColors?: Array<{ start?: string; end?: string }>, -) => { - return data.map((_, index) => { - const defaultBaseColor = colors[index] || "#000000"; - - // Get the base color from gradientColors or fallback to default - let baseColor = defaultBaseColor; - let secondaryColor = colors[data.length - index - 1] || "#ffffff"; - - if (gradientColors?.[index]) { - // If both colors are provided, use them - if (gradientColors[index].start && gradientColors[index].end) { - baseColor = gradientColors[index].start; - secondaryColor = gradientColors[index].end; - } - // If only start color is provided, create a darker version for end - else if (gradientColors[index].start) { - baseColor = gradientColors[index].start; - secondaryColor = adjustColorBrightness(baseColor, -20); - } - // If only end color is provided, create a lighter version for start - else if (gradientColors[index].end) { - baseColor = adjustColorBrightness(gradientColors[index].end, 20); - secondaryColor = gradientColors[index].end; - } - } - - return ( - - - - - - - ); - }); -}; - -/** - * Adjusts the brightness of a hex color - * @param hex - The hex color to adjust - * @param percent - The percentage to adjust brightness by (-100 to 100) - * @returns The adjusted hex color - */ -export const adjustColorBrightness = (hex: string, percent: number): string => { - if (!hex || !hex.startsWith("#")) { - return hex; - } - try { - const num = parseInt(hex.replace("#", ""), 16); - const amt = Math.round(2.55 * percent); - const R = Math.min(255, Math.max(0, (num >> 16) + amt)); - const G = Math.min(255, Math.max(0, ((num >> 8) & 0x00ff) + amt)); - const B = Math.min(255, Math.max(0, (num & 0x0000ff) + amt)); - return "#" + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); - } catch (error) { - console.warn("Invalid color format:", hex); - console.warn(error); - return hex; - } -}; diff --git a/js/packages/react-ui/src/components/Charts/RadialChart/stories/RadialChart.stories.tsx b/js/packages/react-ui/src/components/Charts/RadialChart/stories/RadialChart.stories.tsx index c0fc1994b..1ce33cfcf 100644 --- a/js/packages/react-ui/src/components/Charts/RadialChart/stories/RadialChart.stories.tsx +++ b/js/packages/react-ui/src/components/Charts/RadialChart/stories/RadialChart.stories.tsx @@ -43,15 +43,19 @@ const comprehensiveFinancialData = [ { category: "Meal Vouchers", amount: 1200 }, ]; -// Custom gradient definitions for enhanced visual appeal -const customGradientPalette = [ - { start: "#FF6B6B", end: "#FF8E8E", name: "Coral Red" }, - { start: "#4ECDC4", end: "#6ED7D0", name: "Turquoise" }, - { start: "#45B7D1", end: "#6BC5DB", name: "Sky Blue" }, - { start: "#96CEB4", end: "#B4DCC9", name: "Mint Green" }, - { start: "#FFEEAD", end: "#FFF4C4", name: "Light Yellow" }, - { start: "#D4A5A5", end: "#E5BDBD", name: "Rose" }, - { start: "#9B59B6", end: "#B07CC7", name: "Purple" }, +// Custom color palette for demonstration + +const customColorPalette = [ + "#0A0E60", + "#14197B", + "#272DA6", + "#383FC9", + "#444CE7", + "#5F67F4", + "#7884FF", + "#97A9FF", + "#B4C6FF", + "#CBD7FF", ]; /** @@ -82,7 +86,7 @@ const customGradientPalette = [ * * ### Customization Options * - **Theme System**: Pre-built color palettes (ocean, orchid, emerald, sunset, spectrum, vivid) - * - **Gradient Support**: Custom gradient definitions for enhanced visual appeal + * - **Layout Flexibility**: Responsive design that adapts to container dimensions */ @@ -201,6 +205,26 @@ const exampleData = [ category: "🎨 Visual Styling", }, }, + customPalette: { + description: ` +**Custom Color Palette.** Override the theme colors with your own color array. + +**Usage:** +- Provide an array of hex color strings +- Colors will be applied in order to chart segments +- Takes precedence over theme colors when provided +- Useful for brand-specific color requirements + +**Example:** +\`["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4"]\` + `, + control: false, + table: { + type: { summary: "string[]" }, + defaultValue: { summary: "undefined" }, + category: "🎨 Visual Styling", + }, + }, variant: { description: ` **Chart Layout Style:** @@ -304,22 +328,6 @@ const exampleData = [ category: "🎨 Visual Styling", }, }, - useGradients: { - description: ` -**Gradient Enhancement.** Applies gradient effects to radial bars. - -**Visual impact:** -- Enhanced depth and dimension -- Modern, polished appearance -- Works best with 3-8 data points - `, - control: "boolean", - table: { - type: { summary: "boolean" }, - defaultValue: { summary: "false" }, - category: "🎨 Visual Styling", - }, - }, }, } satisfies Meta; @@ -353,8 +361,6 @@ export const DefaultConfiguration: Story = { grid: false, isAnimationActive: true, cornerRadius: 10, - useGradients: false, - gradientColors: customGradientPalette, }, render: (args: any) => ( @@ -408,7 +414,6 @@ export const ThemeShowcase: Story = { grid: false, isAnimationActive: true, cornerRadius: 10, - useGradients: false, }, render: (args: any) => ( @@ -446,11 +451,10 @@ export const ThemeShowcase: Story = { /** * ## Advanced Visual Enhancement * - * This example showcases the gradient feature combined with percentage formatting - * to create visually striking and informative charts. + * This example showcases percentage formatting to create visually striking and informative charts. */ -export const GradientEnhancement: Story = { - name: "✨ Gradient Enhancement", +export const VisualEnhancement: Story = { + name: "✨ Visual Enhancement", args: { data: monthlyRevenueData.slice(0, 6), categoryKey: "month", @@ -463,8 +467,6 @@ export const GradientEnhancement: Story = { grid: false, isAnimationActive: true, cornerRadius: 15, - useGradients: true, - gradientColors: customGradientPalette, }, render: (args: any) => ( @@ -473,7 +475,7 @@ export const GradientEnhancement: Story = { Revenue Distribution

- Enhanced with gradients and percentage display + Enhanced with percentage display

@@ -483,21 +485,127 @@ export const GradientEnhancement: Story = { docs: { description: { story: ` -**Gradient Enhancement Features:** +**Visual Enhancement Features:** -- **Visual Depth**: Creates three-dimensional appearance +- **Percentage Display**: Shows data as percentages for better comparison - **Modern Aesthetic**: Aligns with contemporary design trends -- **Brand Flexibility**: Custom gradient definitions for brand alignment -- **Performance Optimized**: Efficiently rendered using SVG gradients +- **Theme Integration**: Uses theme colors for consistent visual design +- **Performance Optimized**: Efficiently rendered for smooth interactions -**When to Use Gradients:** +**When to Use Percentage Format:** - Executive dashboards and presentations - Marketing materials and public-facing reports -- When visual impact is prioritized -- With 3-8 data points for optimal effect +- When relative comparisons are important +- With 3-8 data points for optimal readability -**Technical Note:** Gradients are defined as start/end color pairs and applied -automatically to maintain visual consistency across the chart. +**Technical Note:** Percentages are automatically calculated from the data values, +providing clear relative comparisons across all chart segments. + `, + }, + }, + }, +}; + +/** + * ## Custom Color Palette + * + * This example demonstrates how to use a custom color palette to override + * the default theme colors with brand-specific or custom color schemes. + */ +export const CustomPalette: Story = { + name: "🎨 Custom Color Palette", + args: { + data: monthlyRevenueData.slice(0, 8), + categoryKey: "month", + dataKey: "value", + customPalette: customColorPalette, + variant: "circular", + format: "number", + legend: true, + legendVariant: "stacked", + grid: false, + isAnimationActive: true, + cornerRadius: 12, + }, + render: (args: any) => ( + +
+

+ Brand-Specific Color Scheme +

+

+ Using custom colors that align with your brand identity +

+
+ +
+ 🎨 Custom Palette:{" "} + {customColorPalette.slice(0, 8).map((color, index) => ( + + ))} +
+
+ ), + parameters: { + docs: { + description: { + story: ` +**Custom Palette Features:** + +- **Brand Alignment**: Use your exact brand colors for consistent visual identity +- **Override Themes**: Takes precedence over theme-based color selection +- **Flexible Array**: Provide any number of colors - they'll cycle automatically for larger datasets +- **Hex Color Support**: Standard hex color format (#RRGGBB) + +**Implementation Example:** +\`\`\`tsx +const brandColors = [ + "#FF6B6B", // Primary brand color + "#4ECDC4", // Secondary brand color + "#45B7D1", // Accent color 1 + "#96CEB4", // Accent color 2 + // ... more colors as needed +]; + + +\`\`\` + +**Best Practices:** +- Ensure sufficient contrast between adjacent colors +- Test color accessibility for users with color vision differences +- Provide at least as many colors as data points for optimal display +- Consider color psychology and brand associations when selecting colors + +**Technical Notes:** +- Colors are applied in array order to data segments +- If fewer colors than data points, colors will cycle automatically `, }, }, @@ -524,7 +632,6 @@ export const LargeDatasetDemo: Story = { grid: false, isAnimationActive: true, cornerRadius: 8, - useGradients: false, }, render: (args: any) => ( @@ -606,7 +713,6 @@ export const ResponsiveDemo: Story = { legendVariant: "stacked", isAnimationActive: false, cornerRadius: 8, - useGradients: false, }, render: (args: any) => { const [dimensions, setDimensions] = useState<{ width: number; height: number | string }>({ diff --git a/js/packages/react-ui/src/components/Charts/shared/StackedLegend/StackedLegend.tsx b/js/packages/react-ui/src/components/Charts/shared/StackedLegend/StackedLegend.tsx index 9605934d5..7607fd4cf 100644 --- a/js/packages/react-ui/src/components/Charts/shared/StackedLegend/StackedLegend.tsx +++ b/js/packages/react-ui/src/components/Charts/shared/StackedLegend/StackedLegend.tsx @@ -110,10 +110,8 @@ export const StackedLegend = ({ // Calculate total for percentage const total = items.reduce((sum, item) => sum + item.value, 0); - // Sort items by value in descending order (higher to lower) - const sortedItems = [...items].sort((a, b) => b.value - a.value); - - const itemsToDisplay = isShowMoreLayout && !showAll ? sortedItems.slice(0, 6) : sortedItems; + // Items are already sorted by the parent component, so we use them as-is + const itemsToDisplay = isShowMoreLayout && !showAll ? items.slice(0, 6) : items; return (
= colors.length) { + // Wrap around from the beginning + actualIndex = index % colors.length; + } else { + actualIndex = index; + } + + result.push(colors[actualIndex]!); } return result; From e746a2d250de5a062720fb06e5c92278bc9431f9 Mon Sep 17 00:00:00 2001 From: i-subham Date: Wed, 2 Jul 2025 19:31:23 +0530 Subject: [PATCH 2/8] remove superfluous code --- .../react-ui/src/components/Charts/PieChart/PieChart.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx b/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx index 2bf266d8f..482874eba 100644 --- a/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx +++ b/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx @@ -72,13 +72,10 @@ const PieChartComponent = ({ const isRowLayout = legend && legendVariant === "stacked" && wrapperRect.width >= STACKED_LEGEND_BREAKPOINT; - // The data that is processed and rendered in the chart - const processedData = useMemo(() => data, [data]); - // Sort data by value (highest to lowest) for pie chart rendering const sortedProcessedData = useMemo( - () => [...processedData].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), - [processedData, dataKey], + () => [...data].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), + [data, dataKey], ); const categories = useMemo( From 871ca62fae9cf9695467601ec1635081c7fbd573 Mon Sep 17 00:00:00 2001 From: i-subham Date: Wed, 2 Jul 2025 22:57:22 +0530 Subject: [PATCH 3/8] small changes --- .../src/components/Charts/RadialChart/RadialChart.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx b/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx index e6916b949..c8e4b56d2 100644 --- a/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx +++ b/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx @@ -69,13 +69,11 @@ export const RadialChart = ({ const isRowLayout = legend && legendVariant === "stacked" && wrapperRect.width >= STACKED_LEGEND_BREAKPOINT; - // The data that is processed and rendered in the chart - const processedData = useMemo(() => data, [data]); // Sort data by value (highest to lowest) for radial chart rendering const sortedProcessedData = useMemo( - () => [...processedData].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), - [processedData, dataKey], + () => [...data].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), + [data, dataKey], ); const categories = useMemo( From 48992fe3cf4b68395b8e2b66972047c8e64983e7 Mon Sep 17 00:00:00 2001 From: abhishek Date: Thu, 3 Jul 2025 15:09:36 +0530 Subject: [PATCH 4/8] fmt --- .../react-ui/src/components/Charts/RadialChart/RadialChart.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx b/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx index c8e4b56d2..697c57278 100644 --- a/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx +++ b/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx @@ -69,7 +69,6 @@ export const RadialChart = ({ const isRowLayout = legend && legendVariant === "stacked" && wrapperRect.width >= STACKED_LEGEND_BREAKPOINT; - // Sort data by value (highest to lowest) for radial chart rendering const sortedProcessedData = useMemo( () => [...data].sort((a, b) => Number(b[dataKey]) - Number(a[dataKey])), From 26ea1dfaaca72d038cd1515be45d267ec28f316a Mon Sep 17 00:00:00 2001 From: abhishek Date: Thu, 3 Jul 2025 16:01:48 +0530 Subject: [PATCH 5/8] add chart palette in themeprovider --- .../components/Charts/AreaChart/AreaChart.tsx | 15 +++++------ .../AreaChart/stories/areaChart.stories.tsx | 15 ----------- .../components/Charts/BarChart/BarChart.tsx | 15 +++++------ .../components/Charts/LineChart/LineChart.tsx | 15 +++++------ .../components/Charts/PieChart/PieChart.tsx | 19 +++++-------- .../PieChart/stories/PieChart.stories.tsx | 15 ----------- .../Charts/RadarChart/RadarChart.tsx | 15 +++++------ .../Charts/RadialChart/RadialChart.tsx | 18 +++++-------- .../components/Charts/utils/PalletUtils.ts | 27 +++++++++++++++++-- .../src/components/ThemeProvider/types.ts | 11 +++++++- 10 files changed, 77 insertions(+), 88 deletions(-) diff --git a/js/packages/react-ui/src/components/Charts/AreaChart/AreaChart.tsx b/js/packages/react-ui/src/components/Charts/AreaChart/AreaChart.tsx index 3430361a6..6861723ae 100644 --- a/js/packages/react-ui/src/components/Charts/AreaChart/AreaChart.tsx +++ b/js/packages/react-ui/src/components/Charts/AreaChart/AreaChart.tsx @@ -24,7 +24,7 @@ import { getWidthOfData, getXAxisTickPositionData, } from "../utils/AreaAndLine/AreaAndLineUtils"; -import { getDistributedColors, getPalette, PaletteName } from "../utils/PalletUtils"; +import { PaletteName, useChartPalette } from "../utils/PalletUtils"; import { get2dChartConfig, getColorForDataKey, @@ -82,13 +82,12 @@ const AreaChartComponent = ({ const transformedKeys = useTransformedKeys(dataKeys); - const colors = useMemo(() => { - const palette = - customPalette && customPalette.length > 0 - ? { name: "custom", colors: customPalette } - : getPalette(theme); - return getDistributedColors(palette, dataKeys.length); - }, [theme, customPalette, dataKeys.length]); + const colors = useChartPalette({ + chartThemeName: theme, + customPalette, + themePaletteName: "areaChartPalette", + dataLength: dataKeys.length, + }); const chartConfig: ChartConfig = useMemo(() => { return get2dChartConfig(dataKeys, colors, transformedKeys, undefined, icons); diff --git a/js/packages/react-ui/src/components/Charts/AreaChart/stories/areaChart.stories.tsx b/js/packages/react-ui/src/components/Charts/AreaChart/stories/areaChart.stories.tsx index b9cf4cecd..f6d7ee0ef 100644 --- a/js/packages/react-ui/src/components/Charts/AreaChart/stories/areaChart.stories.tsx +++ b/js/packages/react-ui/src/components/Charts/AreaChart/stories/areaChart.stories.tsx @@ -13,19 +13,6 @@ import { useState } from "react"; import { Card } from "../../../Card"; import { AreaChart, AreaChartProps } from "../AreaChart"; -const customColorPalette = [ - "#0A0E60", - "#14197B", - "#272DA6", - "#383FC9", - "#444CE7", - "#5F67F4", - "#7884FF", - "#97A9FF", - "#B4C6FF", - "#CBD7FF", -]; - // 🔥 COMPREHENSIVE DATA VARIATIONS - Designed to test label collision scenarios const dataVariations = { default: [ @@ -697,7 +684,6 @@ export const DataExplorer: Story = { showYAxis: true, xAxisLabel: "Time Period", yAxisLabel: "Values", - customPalette: customColorPalette, // height: 300, }, render: (args: any) => { @@ -1113,7 +1099,6 @@ export const CustomPaletteStory: Story = { args: { data: dataVariations.default as any, categoryKey: "month" as any, - customPalette: customColorPalette, theme: "ocean", // This will be overridden by customPalette variant: "natural", grid: true, diff --git a/js/packages/react-ui/src/components/Charts/BarChart/BarChart.tsx b/js/packages/react-ui/src/components/Charts/BarChart/BarChart.tsx index a601d0358..c9e81dc6e 100644 --- a/js/packages/react-ui/src/components/Charts/BarChart/BarChart.tsx +++ b/js/packages/react-ui/src/components/Charts/BarChart/BarChart.tsx @@ -17,7 +17,7 @@ import { YAxisTick, } from "../shared"; import { type LegendItem } from "../types"; -import { getDistributedColors, getPalette, type PaletteName } from "../utils/PalletUtils"; +import { useChartPalette, type PaletteName } from "../utils/PalletUtils"; import { get2dChartConfig, getColorForDataKey, @@ -90,13 +90,12 @@ const BarChartComponent = ({ const transformedKeys = useTransformedKeys(dataKeys); - const colors = useMemo(() => { - const palette = - customPalette && customPalette.length > 0 - ? { name: "custom", colors: customPalette } - : getPalette(theme); - return getDistributedColors(palette, dataKeys.length); - }, [theme, customPalette, dataKeys.length]); + const colors = useChartPalette({ + chartThemeName: theme, + customPalette, + themePaletteName: "barChartPalette", + dataLength: dataKeys.length, + }); const chartConfig: ChartConfig = useMemo(() => { return get2dChartConfig(dataKeys, colors, transformedKeys, undefined, icons); diff --git a/js/packages/react-ui/src/components/Charts/LineChart/LineChart.tsx b/js/packages/react-ui/src/components/Charts/LineChart/LineChart.tsx index d2903180c..7d79a5bd6 100644 --- a/js/packages/react-ui/src/components/Charts/LineChart/LineChart.tsx +++ b/js/packages/react-ui/src/components/Charts/LineChart/LineChart.tsx @@ -24,7 +24,7 @@ import { getWidthOfData, getXAxisTickPositionData, } from "../utils/AreaAndLine/AreaAndLineUtils"; -import { getDistributedColors, getPalette, PaletteName } from "../utils/PalletUtils"; +import { PaletteName, useChartPalette } from "../utils/PalletUtils"; import { get2dChartConfig, getColorForDataKey, @@ -82,13 +82,12 @@ export const LineChart = ({ const transformedKeys = useTransformedKeys(dataKeys); - const colors = useMemo(() => { - const palette = - customPalette && customPalette.length > 0 - ? { name: "custom", colors: customPalette } - : getPalette(theme); - return getDistributedColors(palette, dataKeys.length); - }, [theme, customPalette, dataKeys.length]); + const colors = useChartPalette({ + chartThemeName: theme, + customPalette, + themePaletteName: "lineChartPalette", + dataLength: dataKeys.length, + }); const chartConfig: ChartConfig = useMemo(() => { return get2dChartConfig(dataKeys, colors, transformedKeys, undefined, icons); diff --git a/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx b/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx index 482874eba..659679e66 100644 --- a/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx +++ b/js/packages/react-ui/src/components/Charts/PieChart/PieChart.tsx @@ -7,7 +7,7 @@ import { DefaultLegend } from "../shared/DefaultLegend/DefaultLegend.js"; import { StackedLegend } from "../shared/StackedLegend/StackedLegend.js"; import { LegendItem } from "../types/Legend.js"; import { getCategoricalChartConfig } from "../utils/dataUtils.js"; -import { getDistributedColors, getPalette, PaletteName } from "../utils/PalletUtils.js"; +import { PaletteName, useChartPalette } from "../utils/PalletUtils.js"; import { PieChartData } from "./types/index.js"; import { calculateTwoLevelChartDimensions, @@ -138,17 +138,12 @@ const PieChartComponent = ({ [cornerRadius, variant, paddingAngle], ); - const palette = useMemo(() => { - if (customPalette && customPalette.length > 0) { - return { name: "custom", colors: customPalette }; - } - return getPalette(theme); - }, [theme, customPalette]); - - const colors = useMemo( - () => getDistributedColors(palette, sortedProcessedData.length), - [palette, sortedProcessedData.length], - ); + const colors = useChartPalette({ + chartThemeName: theme, + customPalette, + themePaletteName: "pieChartPalette", + dataLength: sortedProcessedData.length, + }); const legendItems = useMemo( () => diff --git a/js/packages/react-ui/src/components/Charts/PieChart/stories/PieChart.stories.tsx b/js/packages/react-ui/src/components/Charts/PieChart/stories/PieChart.stories.tsx index adbb04f21..beb115e3e 100644 --- a/js/packages/react-ui/src/components/Charts/PieChart/stories/PieChart.stories.tsx +++ b/js/packages/react-ui/src/components/Charts/PieChart/stories/PieChart.stories.tsx @@ -28,19 +28,6 @@ const comprehensiveData = [ { category: "Software", sales: 10500 }, ]; -const customColorPalette = [ - "#0A0E60", - "#14197B", - "#272DA6", - "#383FC9", - "#444CE7", - "#5F67F4", - "#7884FF", - "#97A9FF", - "#B4C6FF", - "#CBD7FF", -]; - /** * # PieChart Component Documentation * @@ -282,7 +269,6 @@ export const DefaultConfiguration: Story = { appearance: "circular", cornerRadius: 0, paddingAngle: 0, - customPalette: customColorPalette, }, render: (args: any) => ( @@ -366,7 +352,6 @@ export const CustomPalette: Story = { legendVariant: "stacked", cornerRadius: 4, paddingAngle: 2, - customPalette: customColorPalette, }, render: (args: any) => ( diff --git a/js/packages/react-ui/src/components/Charts/RadarChart/RadarChart.tsx b/js/packages/react-ui/src/components/Charts/RadarChart/RadarChart.tsx index 74c8cc416..108fdf099 100644 --- a/js/packages/react-ui/src/components/Charts/RadarChart/RadarChart.tsx +++ b/js/packages/react-ui/src/components/Charts/RadarChart/RadarChart.tsx @@ -6,7 +6,7 @@ import { SideBarTooltipProvider } from "../context/SideBarTooltipContext"; import { useTransformedKeys } from "../hooks/useTransformKey"; import { ActiveDot, CustomTooltipContent, DefaultLegend } from "../shared"; import { LegendItem } from "../types"; -import { getDistributedColors, getPalette } from "../utils/PalletUtils"; +import { useChartPalette } from "../utils/PalletUtils"; import { get2dChartConfig, getDataKeys, getLegendItems } from "../utils/dataUtils"; import { AxisLabel } from "./components/AxisLabel"; import { RadarChartData } from "./types"; @@ -47,13 +47,12 @@ const RadarChartComponent = ({ const transformedKeys = useTransformedKeys(dataKeys); - const colors = useMemo(() => { - const palette = - customPalette && customPalette.length > 0 - ? { name: "custom", colors: customPalette } - : getPalette(theme); - return getDistributedColors(palette, dataKeys.length); - }, [theme, customPalette, dataKeys.length]); + const colors = useChartPalette({ + chartThemeName: theme, + customPalette, + themePaletteName: "radarChartPalette", + dataLength: dataKeys.length, + }); // Create Config const chartConfig: ChartConfig = useMemo(() => { diff --git a/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx b/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx index 697c57278..e94afd937 100644 --- a/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx +++ b/js/packages/react-ui/src/components/Charts/RadialChart/RadialChart.tsx @@ -7,7 +7,7 @@ import { DefaultLegend } from "../shared/DefaultLegend/DefaultLegend"; import { StackedLegend } from "../shared/StackedLegend/StackedLegend"; import { LegendItem } from "../types/Legend"; import { getCategoricalChartConfig } from "../utils/dataUtils"; -import { getDistributedColors, getPalette, PaletteName } from "../utils/PalletUtils"; +import { PaletteName, useChartPalette } from "../utils/PalletUtils"; import { RadialChartData } from "./types"; import { calculateRadialChartDimensions, @@ -151,16 +151,12 @@ export const RadialChart = ({ ); // Get color palette and distribute colors - const palette = useMemo(() => { - if (customPalette && customPalette.length > 0) { - return { name: "custom", colors: customPalette }; - } - return getPalette(theme); - }, [theme, customPalette]); - const colors = useMemo( - () => getDistributedColors(palette, sortedProcessedData.length), - [palette, sortedProcessedData.length], - ); + const colors = useChartPalette({ + chartThemeName: theme, + customPalette, + themePaletteName: "radialChartPalette", + dataLength: sortedProcessedData.length, + }); // Create legend items for both variants const legendItems = useMemo( diff --git a/js/packages/react-ui/src/components/Charts/utils/PalletUtils.ts b/js/packages/react-ui/src/components/Charts/utils/PalletUtils.ts index d2e20f484..973bdd723 100644 --- a/js/packages/react-ui/src/components/Charts/utils/PalletUtils.ts +++ b/js/packages/react-ui/src/components/Charts/utils/PalletUtils.ts @@ -1,4 +1,6 @@ +import { useMemo } from "react"; import invariant from "tiny-invariant"; +import { ChartColorPalette, useTheme } from "../../ThemeProvider"; export type ColorPalette = { name: string; @@ -133,8 +135,7 @@ export const getPaletteMap = (): PaletteMap => { return colorPalettes; }; -export const getDistributedColors = (palette: ColorPalette, dataLength: number): string[] => { - const colors = palette.colors; +export const getDistributedColors = (colors: string[], dataLength: number): string[] => { const midIndex = Math.floor(colors.length / 2); if (dataLength === 1) { @@ -168,3 +169,25 @@ export const getDistributedColors = (palette: ColorPalette, dataLength: number): return result; }; + +export const useChartPalette = ({ + chartThemeName, + customPalette, + themePaletteName, + dataLength, +}: { + chartThemeName: PaletteName; + customPalette?: string[]; + themePaletteName: keyof ChartColorPalette; + dataLength: number; +}) => { + const { theme } = useTheme(); + const paletteFromTheme = theme[themePaletteName] || theme.defaultChartPalette; + const paletteFromChartTheme = getPalette(chartThemeName); + + const palette = customPalette || paletteFromTheme || paletteFromChartTheme.colors; + + return useMemo(() => { + return getDistributedColors(palette, dataLength); + }, [palette, dataLength]); +}; diff --git a/js/packages/react-ui/src/components/ThemeProvider/types.ts b/js/packages/react-ui/src/components/ThemeProvider/types.ts index 06c399f48..23ddd13ab 100644 --- a/js/packages/react-ui/src/components/ThemeProvider/types.ts +++ b/js/packages/react-ui/src/components/ThemeProvider/types.ts @@ -2,7 +2,7 @@ export type ThemeMode = "light" | "dark"; // Color-related theme properties -export interface ColorTheme { +export interface ColorTheme extends ChartColorPalette { // Background colors backgroundFills?: string; /** @@ -114,6 +114,15 @@ export interface ColorTheme { chatUserResponseText?: string; } +export interface ChartColorPalette { + defaultChartPalette?: string[]; + barChartPalette?: string[]; + lineChartPalette?: string[]; + areaChartPalette?: string[]; + pieChartPalette?: string[]; + radarChartPalette?: string[]; + radialChartPalette?: string[]; +} // Layout-related theme properties export interface LayoutTheme { // Spacing From 71d2e5863ddffa0abec234de489f8aa876f1fd1f Mon Sep 17 00:00:00 2001 From: abhishek Date: Thu, 3 Jul 2025 16:24:27 +0530 Subject: [PATCH 6/8] optimise theme provider --- .../src/components/ThemeProvider/ThemeProvider.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/js/packages/react-ui/src/components/ThemeProvider/ThemeProvider.tsx b/js/packages/react-ui/src/components/ThemeProvider/ThemeProvider.tsx index fdf85bfc5..1ffffb485 100644 --- a/js/packages/react-ui/src/components/ThemeProvider/ThemeProvider.tsx +++ b/js/packages/react-ui/src/components/ThemeProvider/ThemeProvider.tsx @@ -1,4 +1,5 @@ -import React, { createContext, useContext } from "react"; +import React, { createContext, useContext, useMemo } from "react"; +import { useShallow } from "zustand/react/shallow"; import { ColorTheme, EffectTheme, LayoutTheme, Theme, ThemeMode, TypographyTheme } from "./types"; export type ThemeProps = { @@ -322,12 +323,16 @@ export const ThemeProvider = ({ darkTheme: userDarkTheme, }: ThemeProps) => { const baseTheme = themes[mode]; - const lightTheme = { ...baseTheme, ...userTheme }; - const darkTheme = { ...baseTheme, ...(userDarkTheme || userTheme) }; + const lightTheme = useShallow(() => ({ ...baseTheme, ...userTheme }))(undefined); + const darkTheme = useShallow(() => ({ ...baseTheme, ...(userDarkTheme || userTheme) }))( + undefined, + ); + const theme = mode === "light" ? lightTheme : darkTheme; + const contextValue = useMemo(() => ({ theme, mode }), [theme, mode]); return ( - +