diff --git a/.changeset/fluffy-impalas-raise.md b/.changeset/fluffy-impalas-raise.md new file mode 100644 index 00000000000..b8963c358bc --- /dev/null +++ b/.changeset/fluffy-impalas-raise.md @@ -0,0 +1,7 @@ +--- +'@razorpay/blade': minor +--- + +feat(blade): add BarChart component + +[Docs Link](https://blade.razorpay.com/?path=/docs/components-charts-barchart--docs) \ No newline at end of file diff --git a/.changeset/yellow-lamps-sneeze.md b/.changeset/yellow-lamps-sneeze.md new file mode 100644 index 00000000000..ea700a5c299 --- /dev/null +++ b/.changeset/yellow-lamps-sneeze.md @@ -0,0 +1,5 @@ +--- +'@razorpay/blade-mcp': minor +--- + +feat(blade-mcp): update knowledgebase with BarChart diff --git a/packages/blade-mcp/knowledgebase/components/AreaChart.md b/packages/blade-mcp/knowledgebase/components/AreaChart.md index dc62bc8cfb9..cfdb7076c4c 100644 --- a/packages/blade-mcp/knowledgebase/components/AreaChart.md +++ b/packages/blade-mcp/knowledgebase/components/AreaChart.md @@ -43,7 +43,7 @@ type ChartAreaWrapperProps = { children?: React.ReactNode; colorTheme?: colorTheme; data: data[]; -}; +} & BoxProps; type ChartReferenceLineProps = { /** @@ -136,4 +136,4 @@ function BasicAreaChart() { ); } -``` \ No newline at end of file +``` diff --git a/packages/blade-mcp/knowledgebase/components/BarChart.md b/packages/blade-mcp/knowledgebase/components/BarChart.md new file mode 100644 index 00000000000..a65e73d1f3d --- /dev/null +++ b/packages/blade-mcp/knowledgebase/components/BarChart.md @@ -0,0 +1,178 @@ +# BarChart + +## Component Name + +BarChart + +## Description + +BarChart is a comprehensive data visualization component that renders interactive bar charts with support for grouped, stacked, and vertical layouts. It provides customizable colors, animations, and interactive features like hover states and tooltips. The component is built on top of Recharts and integrates seamlessly with Blade's design system, offering consistent styling and accessibility features. BarChart supports multiple data series, custom color themes, and various chart configurations for displaying categorical data effectively. + +## Important Constraints + +- `ChartBarWrapper` component only accepts `ChartBar`, `ChartXAxis`, `ChartYAxis`, `ChartCartesianGrid`, `ChartTooltip`, `ChartLegend`, and `ChartReferenceLine` components as children. +- `data` prop is required and must be an array of objects with consistent data structure +- `dataKey` prop is required for each `ChartBar` component and must correspond to a property in the data array +- `stackId` must be consistent across all bars that should be stacked together +- `layout="vertical"` requires `ChartXAxis` to have `type="number"` and `ChartYAxis` to have `type="category"` +- Color tokens must follow the exact format: `chart.background.categorical.{color}.{emphasis}` or `chart.background.sequential.{color}.{number}` + +## TypeScript Types + +These types define the props that the BarChart component and its subcomponents accept: + +```typescript +type ChartBarProps = Omit & { + /** + * The data key of the bar chart. + */ + dataKey: RechartsBarProps['dataKey']; + /** + * The name of the bar chart. + */ + name?: RechartsBarProps['name']; + /** + * The color of the bar chart. + */ + color?: ChartsCategoricalColorToken | ChartSequentialColorToken; + /** + * The stack id of the bar chart. + */ + stackId?: RechartsBarProps['stackId']; + /** + * The active bar of the bar chart. + */ + activeBar?: RechartsBarProps['activeBar']; + /** + * The label of the bar chart. + */ + label?: RechartsBarProps['label']; + /** + * The show legend of the bar chart. + */ + showLegend?: boolean; +}; + +type data = { + [key: string]: unknown; +}; + +type ChartBarWrapperProps = { + children?: React.ReactNode; + /** + * The color theme of the bar chart. + */ + colorTheme?: colorTheme; + /** + * The orientation of the bar chart. + */ + layout?: 'horizontal' | 'vertical'; + /** + * Chart data to be rendered + */ + data: data[]; +} & BoxProps; + + +type ChartsCategoricalColorToken = `chart.background.categorical.${ChartColorCategories}.${keyof ChartCategoricalEmphasis}`; + +type ChartSequentialColorToken = `chart.background.sequential.${Exclude}.${keyof ChartSequentialEmphasis}`; + +type colorTheme = 'default'; + +type ChartXAxisProps = Omit & { + /** + * The label of the x-axis. + */ + label?: string; + /** + * The data key of the x-axis. + */ + dataKey?: string; +}; + +type ChartYAxisProps = Omit & { + /** + * The label of the y-axis. + */ + label?: string; + /** + * The data key of the y-axis. + */ + dataKey?: string; +}; + +type ChartTooltipProps = ComponentProps; + +type ChartLegendProps = ComponentProps; + +type ChartCartesianGridProps = Omit; + +type ChartReferenceLineProps = { + /** + * The y-coordinate of the reference line. + */ + y?: RechartsReferenceLineProps['y']; + /** + * The x-coordinate of the reference line. + */ + x?: RechartsReferenceLineProps['x']; + /** + * The label of the reference line. + */ + label: string; +}; +``` + +## Example + +### Basic BarChart with Multiple Series + +```tsx +import React from 'react'; +import { + ChartBar, + ChartBarWrapper, + ChartXAxis, + ChartYAxis, + ChartCartesianGrid, + ChartTooltip, + ChartLegend, +} from '@razorpay/blade/components'; + +const salesData = [ + { month: 'Jan', revenue: 4000, profit: 2000, expenses: 1000 }, + { month: 'Feb', revenue: 3000, profit: 1500, expenses: 800 }, + { month: 'Mar', revenue: 5000, profit: 3000, expenses: 1200 }, + { month: 'Apr', revenue: 4500, profit: 2500, expenses: 1100 }, +]; + +const BasicBarChart = () => { + return ( +
+ + + + + + + + + + +
+ ); +}; +``` \ No newline at end of file diff --git a/packages/blade-mcp/knowledgebase/components/LineChart.md b/packages/blade-mcp/knowledgebase/components/LineChart.md index 9d9b17adc44..a2dc9960e3a 100644 --- a/packages/blade-mcp/knowledgebase/components/LineChart.md +++ b/packages/blade-mcp/knowledgebase/components/LineChart.md @@ -77,7 +77,7 @@ type ChartLineWrapperProps = { */ data: data[]; children: React.ReactNode; -} & Partial>; +} & BoxProps; type ChartReferenceLineProps = { /** diff --git a/packages/blade/src/components/Charts/AreaChart/AreaChart.stories.tsx b/packages/blade/src/components/Charts/AreaChart/AreaChart.stories.tsx index 18aafbb233b..b496f436c6c 100644 --- a/packages/blade/src/components/Charts/AreaChart/AreaChart.stories.tsx +++ b/packages/blade/src/components/Charts/AreaChart/AreaChart.stories.tsx @@ -21,7 +21,7 @@ const Page = (): React.ReactElement => { componentName="AreaChart" componentDescription="An Area Chart component built on top of Recharts with Blade design system styling." apiDecisionLink={ - 'https://github.com/razorpay/blade/blob/5920fbd32c70793454f8c8c6ff544b2a7413afb5/packages/blade/src/components/Charts/_decisions/decisions.md' + 'https://github.com/razorpay/blade/blob/master/packages/blade/src/components/Charts/_decisions/decisions.md' } figmaURL="https://www.figma.com/design/jubmQL9Z8V7881ayUD95ps/Blade-DSL?node-id=92678-188717&p=f&m=dev" > @@ -252,6 +252,14 @@ export const StackedAreaChart: StoryFn = ({ color="chart.background.categorical.azure.moderate" {...args} /> + ); diff --git a/packages/blade/src/components/Charts/AreaChart/types.ts b/packages/blade/src/components/Charts/AreaChart/types.ts index b9ec328ace4..3b472141e75 100644 --- a/packages/blade/src/components/Charts/AreaChart/types.ts +++ b/packages/blade/src/components/Charts/AreaChart/types.ts @@ -1,11 +1,7 @@ import type { AreaProps as RechartAreaProps } from 'recharts'; import type { ChartsCategoricalColorToken } from '../CommonChartComponents/types'; import type { colorTheme } from '../utils'; -import type { - BaseBoxProps, - FlexboxProps, - GridProps, -} from '~components/Box/BaseBox/types/propsTypes'; +import type { BoxProps } from '~components/Box'; type ChartAreaProps = { /** @@ -68,6 +64,6 @@ type ChartAreaWrapperProps = { * Chart data to be rendered */ data: data[]; -} & Partial>; +} & BoxProps; export type { ChartAreaProps, ChartAreaWrapperProps }; diff --git a/packages/blade/src/components/Charts/BarChart/BarChart.native.tsx b/packages/blade/src/components/Charts/BarChart/BarChart.native.tsx new file mode 100644 index 00000000000..38f8793ea9e --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/BarChart.native.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import type { ChartBarProps, ChartBarWrapperProps } from './types'; +import { Text } from '~components/Typography'; +import { throwBladeError } from '~utils/logger'; + +const ChartBar = (_prop: ChartBarProps): React.ReactElement => { + throwBladeError({ + message: 'ChartBar is not yet implemented for native', + moduleName: 'ChartBar', + }); + + return ChartBar is not available for Native mobile apps.; +}; + +const ChartBarWrapper = (_prop: ChartBarWrapperProps): React.ReactElement => { + throwBladeError({ + message: 'ChartBarWrapper is not yet implemented for native', + moduleName: 'ChartBarWrapper', + }); + + return ChartLine is not available for Native mobile apps.; +}; + +export type { ChartBarProps, ChartBarWrapperProps }; +export { ChartBar, ChartBarWrapper }; diff --git a/packages/blade/src/components/Charts/BarChart/BarChart.stories.tsx b/packages/blade/src/components/Charts/BarChart/BarChart.stories.tsx new file mode 100644 index 00000000000..1c175f64346 --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/BarChart.stories.tsx @@ -0,0 +1,321 @@ +import type { StoryFn, Meta } from '@storybook/react'; +import React from 'react'; +import { + ChartBar, + ChartBarWrapper, + ChartXAxis, + ChartYAxis, + ChartCartesianGrid, + ChartTooltip, + ChartLegend, +} from '~components/Charts'; +import { Heading } from '~components/Typography/Heading'; +import { Sandbox } from '~utils/storybook/Sandbox'; +import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; + +const Page = (): React.ReactElement => { + return ( + + Usage + + {` + import { + ChartBar, + ChartBarWrapper, + ChartXAxis, + ChartYAxis, + ChartCartesianGrid, + ChartTooltip, + ChartLegend, + } from '@razorpay/blade/components'; + + function App() { + const data = [ + { name: 'Jan', sales: 4000 }, + { name: 'Feb', sales: 3000 }, + { name: 'Mar', sales: 2000 }, + ]; + + return ( + + + + + + + + + ) + } + + export default App; + `} + + + ); +}; + +const propsCategory = { + CHART_BAR_PROPS: 'ChartBar Props', +}; + +export default { + title: 'Components/Charts/BarChart', + component: ChartBar, + tags: ['autodocs'], + argTypes: { + dataKey: { + control: { type: 'text' }, + table: { + category: propsCategory.CHART_BAR_PROPS, + }, + }, + name: { + control: { type: 'text' }, + table: { + category: propsCategory.CHART_BAR_PROPS, + }, + }, + color: { + control: { type: 'text' }, + table: { + category: propsCategory.CHART_BAR_PROPS, + }, + }, + stackId: { + control: { type: 'text' }, + table: { + category: propsCategory.CHART_BAR_PROPS, + }, + }, + // Hide private props from Storybook + _index: { + table: { disable: true }, + }, + _colorTheme: { + table: { disable: true }, + }, + }, + parameters: { + docs: { + page: Page, + }, + }, +} as Meta; + +const chartData = [ + { name: 'Jan', seriesA: 4000, seriesB: 2400, seriesC: 1200 }, + { name: 'Feb', seriesA: 3000, seriesB: 1398, seriesC: 900 }, + { name: 'Mar', seriesA: 2000, seriesB: 9800, seriesC: 1600 }, + { name: 'Apr', seriesA: 2780, seriesB: 3908, seriesC: 2200 }, + { name: 'May', seriesA: 1890, seriesB: 4800, seriesC: 1700 }, + { name: 'Jun', seriesA: 2390, seriesB: 3800, seriesC: 2100 }, + { name: 'Jul', seriesA: 2390, seriesB: 3800, seriesC: 2100 }, + { name: 'Aug', seriesA: 3000, seriesB: 4800, seriesC: 3000 }, + { name: 'Sep', seriesA: 3500, seriesB: 3400, seriesC: 5300 }, + { name: 'Oct', seriesA: 2000, seriesB: 1400, seriesC: 3300 }, + { name: 'Nov', seriesA: 1400, seriesB: 5400, seriesC: 1300 }, + { name: 'Dec', seriesA: 1200, seriesB: 4600, seriesC: 2000 }, +]; + +export const DefaultChart: StoryFn = ({ + dataKey = 'seriesA', + name = 'Series A', + ...props +}) => { + return ( +
+ + + + + + + + +
+ ); +}; + +export const TinyBarChart: StoryFn = () => { + return ( +
+ + + +
+ ); +}; + +TinyBarChart.parameters = { + controls: { disable: true }, +}; + +export const SimpleBarChart: StoryFn = () => { + return ( +
+ + + + + + + + + +
+ ); +}; + +SimpleBarChart.parameters = { + controls: { disable: true }, +}; + +export const StackedBarChart: StoryFn = () => { + return ( +
+ + + + + + + + + + +
+ ); +}; + +StackedBarChart.parameters = { + controls: { disable: true }, +}; + +export const GroupedBarChart: StoryFn = () => { + return ( +
+ + + + + + + + + + +
+ ); +}; + +GroupedBarChart.parameters = { + controls: { disable: true }, +}; + +export const VerticalBarChart: StoryFn = () => { + return ( +
+ + + + + + + + + + +
+ ); +}; + +VerticalBarChart.parameters = { + controls: { disable: true }, +}; + +export const BarChartWithDefaultColorTheme: StoryFn = () => { + return ( +
+ + + + + + + + + + +
+ ); +}; + +BarChartWithDefaultColorTheme.parameters = { + controls: { disable: true }, +}; + +DefaultChart.storyName = 'Default Bar Chart'; +TinyBarChart.storyName = 'Tiny Bar Chart'; +SimpleBarChart.storyName = 'Simple Bar Chart'; +StackedBarChart.storyName = 'Stacked Bar Chart'; +VerticalBarChart.storyName = 'Vertical Bar Chart'; +BarChartWithDefaultColorTheme.storyName = 'Bar Chart With Default Color Theme'; diff --git a/packages/blade/src/components/Charts/BarChart/BarChart.web.tsx b/packages/blade/src/components/Charts/BarChart/BarChart.web.tsx new file mode 100644 index 00000000000..2f0144cf703 --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/BarChart.web.tsx @@ -0,0 +1,177 @@ +import React, { useState } from 'react'; +import { + BarChart as RechartsBarChart, + Bar as RechartsBar, + ResponsiveContainer as RechartsResponsiveContainer, +} from 'recharts'; +import { useChartsColorTheme } from '../utils'; +import { BarChartContext, useBarChartContext } from './BarChartContext'; +import type { ChartBarProps, ChartBarWrapperProps } from './types'; +import { + BAR_CHART_CORNER_RADIUS, + DISTANCE_BETWEEN_STACKED_BARS, + componentIds, + BAR_SIZE, + DISTANCE_BETWEEN_BARS, + DISTANCE_BETWEEN_CATEGORY_BARS, +} from './tokens'; +import { useTheme } from '~components/BladeProvider'; +import BaseBox from '~components/Box/BaseBox'; +import { metaAttribute } from '~utils/metaAttribute'; +import getIn from '~utils/lodashButBetter/get'; +import isNumber from '~utils/lodashButBetter/isNumber'; +import { assignWithoutSideEffects } from '~utils/assignWithoutSideEffects'; +import { getComponentId } from '~utils/isValidAllowedChildren'; +import { makeAnalyticsAttribute } from '~utils/makeAnalyticsAttribute'; +import type { DataAnalyticsAttribute, TestID } from '~utils/types'; + +export type RechartsShapeProps = { + x: number; + y: number; + width: number; + height: number; + fill: string; + index: number; +}; + +// Bar component - resolves Blade color tokens to actual colors +const _ChartBar: React.FC = ({ + color, + name, + dataKey, + activeBar = false, + label = false, + showLegend = true, + _index = 0, + ...rest +}) => { + const { theme } = useTheme(); + const { layout, activeIndex, colorTheme: _colorTheme, totalBars } = useBarChartContext(); + const defaultColorArray = useChartsColorTheme({ colorTheme: _colorTheme ?? 'default' }); + const fill = color ? getIn(theme.colors, color) : defaultColorArray[_index]; + const isStacked = rest.stackId !== undefined; + const animationBegin = isStacked + ? (theme.motion.duration.gentle / totalBars) * _index + : theme.motion.duration.gentle; + const animationDuration = isStacked + ? theme.motion.duration.gentle / totalBars + : theme.motion.duration.gentle; + + return ( + { + const { fill, x, y, width, height, index: barIndex } = props as RechartsShapeProps; + const fillOpacity = isNumber(activeIndex) ? (barIndex === activeIndex ? 1 : 0.2) : 1; + const gap = DISTANCE_BETWEEN_STACKED_BARS; + const isVertical = layout === 'vertical'; + + if (isVertical) { + return ( + + ); + } + return ( + gap ? height - gap : 0} + rx={BAR_CHART_CORNER_RADIUS} + ry={BAR_CHART_CORNER_RADIUS} + fillOpacity={fillOpacity} + /> + ); + }} + /> + ); +}; + +const ChartBar = assignWithoutSideEffects(_ChartBar, { + componentId: componentIds.chartBar, +}); + +// BarChart wrapper with default margin, auto-color assignment, and max bars guard +const ChartBarWrapper: React.FC = ({ + children, + colorTheme = 'default', + layout = 'horizontal', + testID, + data = [], + ...restProps +}) => { + const [activeIndex, setActiveIndex] = useState(undefined); + + const { barChartModifiedChildrens, totalBars } = React.useMemo(() => { + let BarChartIndex = 0; + const modifiedChildren = React.Children.map(children, (child) => { + if (React.isValidElement(child) && getComponentId(child) === componentIds.chartBar) { + return React.cloneElement(child, { + _index: BarChartIndex++, + } as Partial); + } + return child; + }); + + return { + barChartModifiedChildrens: modifiedChildren, + totalBars: BarChartIndex, + }; + }, [children]); + + return ( + + + + { + setActiveIndex(state?.activeIndex ? Number(state?.activeIndex) : undefined); + }} + layout={layout} + data={data} + > + {barChartModifiedChildrens} + + + + + ); +}; + +export { ChartBarWrapper, ChartBar }; +export type { ChartBarProps, ChartBarWrapperProps }; diff --git a/packages/blade/src/components/Charts/BarChart/BarChartContext.ts b/packages/blade/src/components/Charts/BarChart/BarChartContext.ts new file mode 100644 index 00000000000..b13de58bcd4 --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/BarChartContext.ts @@ -0,0 +1,11 @@ +import { createContext, useContext } from 'react'; +import type { BarChartContextType } from './types'; + +export const BarChartContext = createContext({ + layout: 'horizontal', + activeIndex: undefined, + colorTheme: 'default', + totalBars: 0, +}); + +export const useBarChartContext = (): BarChartContextType => useContext(BarChartContext); diff --git a/packages/blade/src/components/Charts/BarChart/__tests__/BarChart.web.test.tsx b/packages/blade/src/components/Charts/BarChart/__tests__/BarChart.web.test.tsx new file mode 100644 index 00000000000..c5baa8540c7 --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/__tests__/BarChart.web.test.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { ChartBarWrapper, ChartBar } from '../BarChart'; +import { + ChartXAxis, + ChartYAxis, + ChartCartesianGrid, + ChartTooltip, + ChartLegend, +} from '../../CommonChartComponents'; +import renderWithTheme from '~utils/testing/renderWithTheme.web'; +import { Box } from '~components/Box/Box'; + +const mockData = [ + { name: 'Jan', sales: 4000, profit: 2000, revenue: 6000 }, + { name: 'Feb', sales: 3000, profit: 1500, revenue: 4500 }, + { name: 'Mar', sales: 2000, profit: 1000, revenue: 3000 }, + { name: 'Apr', sales: 5000, profit: 2500, revenue: 7500 }, +]; + +// Mock recharts ResponsiveContainer for consistent testing +// Thanks to : https://jskim1991.medium.com/react-writing-tests-with-graphs-9b7f2c9eeefc + +jest.mock('recharts', () => { + const OriginalModule = jest.requireActual('recharts'); + return { + ...OriginalModule, + ResponsiveContainer: ({ children }: { children: React.ReactNode }) => ( + + {children} + + ), + }; +}); + +describe('', () => { + it('should render basic BarChart with single bar', () => { + const { container } = renderWithTheme( + + + + + , + ); + expect(container).toMatchSnapshot(); + }); + + it('should render BarChart with multiple bars', () => { + const { container } = renderWithTheme( + + + + + + + , + ); + expect(container).toMatchSnapshot(); + }); + + it('should render stacked BarChart', () => { + const { container } = renderWithTheme( + + + + + + + , + ); + expect(container).toMatchSnapshot(); + }); + + it('should render BarChart with custom colors', () => { + const { container } = renderWithTheme( + + + + + + , + ); + expect(container).toMatchSnapshot(); + }); + + it('should render complete chart with all components', () => { + const { container } = renderWithTheme( + + + + + + + + + + + , + ); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/blade/src/components/Charts/BarChart/__tests__/__snapshots__/BarChart.web.test.tsx.snap b/packages/blade/src/components/Charts/BarChart/__tests__/__snapshots__/BarChart.web.test.tsx.snap new file mode 100644 index 00000000000..4a2731d8ac5 --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/__tests__/__snapshots__/BarChart.web.test.tsx.snap @@ -0,0 +1,1701 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render BarChart with custom colors 1`] = ` +.c0.c0.c0.c0.c0 { + height: 500px; + width: 500px; +} + +.c1.c1.c1.c1.c1 { + height: 100%; + width: 100%; +} + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+`; + +exports[` should render BarChart with multiple bars 1`] = ` +.c0.c0.c0.c0.c0 { + height: 500px; + width: 500px; +} + +.c1.c1.c1.c1.c1 { + height: 100%; + width: 100%; +} + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+`; + +exports[` should render basic BarChart with single bar 1`] = ` +.c0.c0.c0.c0.c0 { + height: 500px; + width: 500px; +} + +.c1.c1.c1.c1.c1 { + height: 100%; + width: 100%; +} + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+`; + +exports[` should render complete chart with all components 1`] = ` +.c0.c0.c0.c0.c0 { + height: 500px; + width: 500px; +} + +.c1.c1.c1.c1.c1 { + height: 100%; + width: 100%; +} + +.c3.c3.c3.c3.c3 { + padding-top: 12px; +} + +.c4.c4.c4.c4.c4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + gap: 16px; +} + +.c5.c5.c5.c5.c5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c6.c6.c6.c6.c6 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + gap: 8px; +} + +.c2.c2.c2.c2.c2 { + color: hsla(0,0%,100%,1); + font-family: "TASA Orbiter","TASA Orbiter Fallback Arial",Arial; + font-size: 1.125rem; + font-weight: 600; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.5rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + margin: 0; + padding: 0; +} + +.c7.c7.c7.c7.c7 { + color: hsla(211,22%,56%,1); + font-family: "Inter","Inter Fallback Arial",Arial; + font-size: 0.875rem; + font-weight: 400; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.25rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + margin: 0; + padding: 0; +} + +
+
+
+
+
+
+ +
+
+
+
+
+`; + +exports[` should render stacked BarChart 1`] = ` +.c0.c0.c0.c0.c0 { + height: 500px; + width: 500px; +} + +.c1.c1.c1.c1.c1 { + height: 100%; + width: 100%; +} + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+`; diff --git a/packages/blade/src/components/Charts/BarChart/index.ts b/packages/blade/src/components/Charts/BarChart/index.ts new file mode 100644 index 00000000000..003cde642b3 --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/index.ts @@ -0,0 +1,2 @@ +export { ChartBarWrapper, ChartBar } from './BarChart'; +export type { ChartBarProps, ChartBarWrapperProps } from './types'; diff --git a/packages/blade/src/components/Charts/BarChart/tokens.ts b/packages/blade/src/components/Charts/BarChart/tokens.ts new file mode 100644 index 00000000000..90ce8eef94d --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/tokens.ts @@ -0,0 +1,21 @@ +// Arbitrary sequential limit per palette (we will not have this in updated design) +const BAR_CHART_CORNER_RADIUS = 2; +const DISTANCE_BETWEEN_STACKED_BARS = 2; +const BAR_SIZE = 49; +const DISTANCE_BETWEEN_BARS = 2; +const DISTANCE_BETWEEN_CATEGORY_BARS = 2; +const ANIMATION_TIME_OFFEST = 200; + +const componentIds = { + chartBar: 'ChartBar', +}; + +export { + componentIds, + DISTANCE_BETWEEN_STACKED_BARS, + BAR_CHART_CORNER_RADIUS, + BAR_SIZE, + DISTANCE_BETWEEN_BARS, + DISTANCE_BETWEEN_CATEGORY_BARS, + ANIMATION_TIME_OFFEST, +}; diff --git a/packages/blade/src/components/Charts/BarChart/types.ts b/packages/blade/src/components/Charts/BarChart/types.ts new file mode 100644 index 00000000000..d9fdce0c1b4 --- /dev/null +++ b/packages/blade/src/components/Charts/BarChart/types.ts @@ -0,0 +1,72 @@ +import type { BarProps as RechartsBarProps } from 'recharts'; +import type { + ChartsCategoricalColorToken, + ChartSequentialColorToken, +} from '../CommonChartComponents/types'; +import type { colorTheme } from '../utils'; +import type { BoxProps } from '~components/Box'; + +type ChartBarProps = { + /** + * The data key of the bar chart. + */ + dataKey: RechartsBarProps['dataKey']; + /** + * The name of the bar chart. + */ + name?: RechartsBarProps['name']; // default to dataKey + /** + * The color of the bar chart. + */ + color?: ChartsCategoricalColorToken | ChartSequentialColorToken; + /** + * The stack id of the bar chart. + */ + stackId?: RechartsBarProps['stackId']; + /** + * The active bar of the bar chart. + */ + activeBar?: RechartsBarProps['activeBar']; + /** + * The label of the bar chart. + */ + label?: RechartsBarProps['label']; + /** + * The show legend of the bar chart. + */ + showLegend?: boolean; + /** + * The index of the bar chart. + * @private + */ + _index?: number; +}; + +type data = { + [key: string]: string | number; +}; + +type ChartBarWrapperProps = { + children?: React.ReactNode; + /** + * The color theme of the bar chart. + */ + colorTheme?: colorTheme; + /** + * The layout of the bar chart. + */ + layout?: 'horizontal' | 'vertical'; + /** + * Chart data to be rendered + */ + data: data[]; +} & BoxProps; + +interface BarChartContextType { + layout?: 'horizontal' | 'vertical'; + activeIndex?: number; + colorTheme: colorTheme; + totalBars: number; +} + +export type { ChartBarProps, ChartBarWrapperProps, BarChartContextType }; diff --git a/packages/blade/src/components/Charts/CommonChartComponents/CommonChartComponents.web.tsx b/packages/blade/src/components/Charts/CommonChartComponents/CommonChartComponents.web.tsx index db7cb782872..ca88a0b2ddf 100644 --- a/packages/blade/src/components/Charts/CommonChartComponents/CommonChartComponents.web.tsx +++ b/packages/blade/src/components/Charts/CommonChartComponents/CommonChartComponents.web.tsx @@ -157,7 +157,7 @@ const ChartTooltip: React.FC = (props) => {
); }} - cursor={{ fill: 'rgba(0, 0, 0, 0.1)', stroke: theme.colors.surface.border.gray.muted }} + cursor={{ fill: 'transparent', stroke: 'transparent' }} {...props} /> ); diff --git a/packages/blade/src/components/Charts/CommonChartComponents/types.ts b/packages/blade/src/components/Charts/CommonChartComponents/types.ts index 78a8652b503..c4703910cef 100644 --- a/packages/blade/src/components/Charts/CommonChartComponents/types.ts +++ b/packages/blade/src/components/Charts/CommonChartComponents/types.ts @@ -7,7 +7,11 @@ import type { ReferenceLineProps as RechartsReferenceLineProps, } from 'recharts'; import type { ComponentProps } from 'react'; -import type { ChartColorCategories, ChartCategoricalEmphasis } from '~tokens/theme/theme'; +import type { + ChartColorCategories, + ChartCategoricalEmphasis, + ChartSequentialEmphasis, +} from '~tokens/theme/theme'; type ChartReferenceLineProps = { /** @@ -55,6 +59,11 @@ type ChartCartesianGridProps = Omit< type ChartsCategoricalColorToken = `chart.background.categorical.${ChartColorCategories}.${keyof ChartCategoricalEmphasis}`; +type ChartSequentialColorToken = `chart.background.sequential.${Exclude< + ChartColorCategories, + 'gray' +>}.${keyof ChartSequentialEmphasis}`; + export type { ChartReferenceLineProps, ChartXAxisProps, @@ -63,4 +72,5 @@ export type { ChartLegendProps, ChartCartesianGridProps, ChartsCategoricalColorToken, + ChartSequentialColorToken, }; diff --git a/packages/blade/src/components/Charts/LineChart/LineChart.stories.tsx b/packages/blade/src/components/Charts/LineChart/LineChart.stories.tsx index 10fa0cdd0dc..b77f72294be 100644 --- a/packages/blade/src/components/Charts/LineChart/LineChart.stories.tsx +++ b/packages/blade/src/components/Charts/LineChart/LineChart.stories.tsx @@ -19,7 +19,7 @@ const Page = (): React.ReactElement => { Usage diff --git a/packages/blade/src/components/Charts/LineChart/types.ts b/packages/blade/src/components/Charts/LineChart/types.ts index 550a83d655e..cd195bd5bfd 100644 --- a/packages/blade/src/components/Charts/LineChart/types.ts +++ b/packages/blade/src/components/Charts/LineChart/types.ts @@ -1,11 +1,7 @@ import type { LineProps as RechartsLineProps } from 'recharts'; import type { ChartsCategoricalColorToken } from '../CommonChartComponents/types'; import type { colorTheme } from '../utils'; -import type { - BaseBoxProps, - FlexboxProps, - GridProps, -} from '~components/Box/BaseBox/types/propsTypes'; +import type { BoxProps } from '~components/Box'; interface ChartLineProps { /** @@ -74,6 +70,6 @@ type ChartLineWrapperProps = { */ data: data[]; children: React.ReactNode; -} & Partial>; +} & BoxProps; export type { ChartLineProps, ChartLineWrapperProps }; diff --git a/packages/blade/src/components/Charts/index.ts b/packages/blade/src/components/Charts/index.ts index b4294a425d4..40949e184ef 100644 --- a/packages/blade/src/components/Charts/index.ts +++ b/packages/blade/src/components/Charts/index.ts @@ -1,4 +1,4 @@ -// Export LineCharts (includes shared components) export * from './LineChart'; export * from './AreaChart'; +export * from './BarChart'; export * from './CommonChartComponents';