From 441b7941cb3f9a5963ff87c2c4d3e8676151c89a Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Mon, 8 Dec 2025 10:59:55 -0500 Subject: [PATCH 01/13] feat: legend and tooltip on web --- packages/web-visualization/package.json | 1 + .../src/chart/CartesianChart.tsx | 85 ++- .../web-visualization/src/chart/ChartSlot.tsx | 27 + .../src/chart/ChartTooltip.tsx | 261 +++++++ .../src/chart/PolarChart.tsx | 74 +- .../__stories__/ChartTooltip.stories.tsx | 365 ++++++++++ .../src/chart/area/AreaChart.tsx | 1 + packages/web-visualization/src/chart/index.ts | 2 + .../src/chart/legend/DefaultLegendItem.tsx | 147 ++++ .../src/chart/legend/DefaultLegendShape.tsx | 77 ++ .../src/chart/legend/Legend.tsx | 101 +++ .../legend/__stories__/Legend.stories.tsx | 667 ++++++++++++++++++ .../src/chart/legend/index.tsx | 3 + .../src/chart/line/LineChart.tsx | 1 + .../src/chart/utils/chart.ts | 10 + .../src/chart/utils/context.ts | 24 +- yarn.lock | 1 + 17 files changed, 1802 insertions(+), 45 deletions(-) create mode 100644 packages/web-visualization/src/chart/ChartSlot.tsx create mode 100644 packages/web-visualization/src/chart/ChartTooltip.tsx create mode 100644 packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx create mode 100644 packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx create mode 100644 packages/web-visualization/src/chart/legend/DefaultLegendShape.tsx create mode 100644 packages/web-visualization/src/chart/legend/Legend.tsx create mode 100644 packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx create mode 100644 packages/web-visualization/src/chart/legend/index.tsx diff --git a/packages/web-visualization/package.json b/packages/web-visualization/package.json index 02aaba9569..ed4cb11d44 100644 --- a/packages/web-visualization/package.json +++ b/packages/web-visualization/package.json @@ -42,6 +42,7 @@ "@coinbase/cds-lottie-files": "workspace:^", "@coinbase/cds-utils": "workspace:^", "@coinbase/cds-web": "workspace:^", + "@floating-ui/react-dom": "^2.1.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/packages/web-visualization/src/chart/CartesianChart.tsx b/packages/web-visualization/src/chart/CartesianChart.tsx index cfd5a89dc6..ec9f32e936 100644 --- a/packages/web-visualization/src/chart/CartesianChart.tsx +++ b/packages/web-visualization/src/chart/CartesianChart.tsx @@ -25,7 +25,12 @@ import { useTotalAxisPadding, } from './utils'; -const focusStylesCss = css` +const rootCss = css` + display: flex; + flex-direction: column; + overflow: hidden; +`; +const focusCss = css` &:focus { outline: none; } @@ -34,6 +39,11 @@ const focusStylesCss = css` outline-offset: 2px; } `; +const middleRowCss = css` + display: flex; + flex: 1; + min-height: 0; +`; export type CartesianChartBaseProps = BoxBaseProps & Pick & { @@ -125,6 +135,20 @@ export const CartesianChart = memo( ) => { const { observe, width: chartWidth, height: chartHeight } = useDimensions(); const svgRef = useRef(null); + const topSlotRef = useRef(null); + const bottomSlotRef = useRef(null); + const leftSlotRef = useRef(null); + const rightSlotRef = useRef(null); + + const slotRefs = useMemo( + () => ({ + topRef: topSlotRef, + bottomRef: bottomSlotRef, + leftRef: leftSlotRef, + rightRef: rightSlotRef, + }), + [], + ); const calculatedInset = useMemo(() => getChartInset(inset, defaultChartInset), [inset]); @@ -368,6 +392,8 @@ export const CartesianChart = memo( registerAxis, unregisterAxis, getAxisBounds, + svgRef, + slotRefs, }), [ series, @@ -385,11 +411,13 @@ export const CartesianChart = memo( registerAxis, unregisterAxis, getAxisBounds, + svgRef, + slotRefs, ], ); const rootClassNames = useMemo( - () => cx(className, classNames?.root), + () => cx(rootCss, className, classNames?.root), [className, classNames], ); const rootStyles = useMemo(() => ({ ...style, ...styles?.root }), [style, styles?.root]); @@ -402,38 +430,43 @@ export const CartesianChart = memo( svgRef={svgRef} > { - observe(node as unknown as HTMLElement); - }} className={rootClassNames} height={height} style={rootStyles} width={width} {...props} > - { - const svgElement = node as unknown as SVGSVGElement; - svgRef.current = svgElement; - // Forward the ref to the user - if (ref) { - if (typeof ref === 'function') { - ref(svgElement); - } else { - (ref as React.MutableRefObject).current = svgElement; + + + + { + const svgElement = node as unknown as SVGSVGElement; + svgRef.current = svgElement; + observe(node as unknown as HTMLElement); + + // Forward the ref to the user + if (ref) { + if (typeof ref === 'function') { + ref(svgElement); + } else { + (ref as React.MutableRefObject).current = svgElement; + } } - } - }} - aria-live="polite" - as="svg" - className={cx(enableScrubbing && focusStylesCss, classNames?.chart)} - height="100%" - style={styles?.chart} - tabIndex={enableScrubbing ? 0 : undefined} - width="100%" - > - {children} + }} + aria-live="polite" + as="svg" + className={cx(enableScrubbing && focusCss, classNames?.chart)} + height="100%" + style={styles?.chart} + tabIndex={enableScrubbing ? 0 : undefined} + width="100%" + > + {children} + + + diff --git a/packages/web-visualization/src/chart/ChartSlot.tsx b/packages/web-visualization/src/chart/ChartSlot.tsx new file mode 100644 index 0000000000..66ef55e488 --- /dev/null +++ b/packages/web-visualization/src/chart/ChartSlot.tsx @@ -0,0 +1,27 @@ +import { memo, useEffect, useState } from 'react'; +import type React from 'react'; +import { createPortal } from 'react-dom'; + +export type ChartSlotProps = { + children: React.ReactNode; + /** + * The slot ref to portal into. + */ + slotRef?: React.RefObject; +}; + +export const ChartSlot = memo(function ChartSlot({ children, slotRef }: ChartSlotProps) { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + // We only want to mount after the slotRef is available + if (!mounted || !slotRef?.current) { + return null; + } + + // Portal directly into the slot element + return createPortal(children, slotRef.current); +}); diff --git a/packages/web-visualization/src/chart/ChartTooltip.tsx b/packages/web-visualization/src/chart/ChartTooltip.tsx new file mode 100644 index 0000000000..165e487d14 --- /dev/null +++ b/packages/web-visualization/src/chart/ChartTooltip.tsx @@ -0,0 +1,261 @@ +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { Box, Divider, HStack, VStack, type VStackProps } from '@coinbase/cds-web/layout'; +import { Portal } from '@coinbase/cds-web/overlays/Portal'; +import { tooltipContainerId } from '@coinbase/cds-web/overlays/PortalProvider'; +import { Text } from '@coinbase/cds-web/typography'; +import { flip, offset, shift, useFloating, type VirtualElement } from '@floating-ui/react-dom'; +import { css } from '@linaria/core'; + +import { DefaultLegendShape } from './legend/DefaultLegendShape'; + +const legendMediaWrapperCss = css` + height: 24px; + display: flex; + align-items: center; + justify-content: center; +`; +import type { LegendShape } from './utils/chart'; +import { useCartesianChartContext } from './ChartProvider'; +import { useScrubberContext } from './utils'; + +export type ChartTooltipProps = VStackProps<'div'> & { + /** + * Label text displayed at the top of the tooltip. + * Can be a static string, a custom ReactNode, or a function that receives the current dataIndex. + * If not provided, defaults to the x-axis data value at the current index. + * If null is returned, the label is omitted. + */ + label?: React.ReactNode | ((dataIndex: number) => React.ReactNode); + /** + * Array of series IDs to include in the tooltip. + * By default, all series will be included. + */ + seriesIds?: string[]; + /** + * Formatter function for series values. + * Receives the numeric series value and should return a ReactNode. + * String results will automatically be wrapped in Text with font="label2". + */ + valueFormatter?: (value: number) => React.ReactNode; +}; + +export const ChartTooltip = ({ + label, + seriesIds, + valueFormatter, + gap = 1, + minWidth = 320, + ...props +}: ChartTooltipProps) => { + const { svgRef, series, getSeriesData, getXAxis } = useCartesianChartContext(); + const { scrubberPosition, enableScrubbing } = useScrubberContext(); + const [legendMediaWidth, setLegendMediaWidth] = useState(0); + const legendMediaRefs = useRef>(new Map()); + + const isTooltipVisible = enableScrubbing && scrubberPosition !== undefined; + + const { refs, floatingStyles } = useFloating({ + open: isTooltipVisible, + placement: 'bottom-start', + middleware: [ + offset(({ placement }) => { + const mainAxis = placement.includes('bottom') ? 16 : 8; + const crossAxis = placement.includes('start') ? 16 : -8; + + return { mainAxis, crossAxis }; + }), + flip({ + fallbackPlacements: ['top-start', 'bottom-end', 'top-end'], + }), + shift({ padding: 8 }), + ], + }); + + useEffect(() => { + const element = svgRef?.current; + if (!element || !enableScrubbing) return; + + const handleMouseMove = (event: Event) => { + const { clientX, clientY } = event as MouseEvent; + const virtualEl: VirtualElement = { + getBoundingClientRect() { + return { + width: 0, + height: 0, + x: clientX, + y: clientY, + left: clientX, + right: clientX, + top: clientY, + bottom: clientY, + } as DOMRect; + }, + }; + refs.setReference(virtualEl); + }; + + element.addEventListener('mousemove', handleMouseMove); + + return () => element.removeEventListener('mousemove', handleMouseMove); + }, [enableScrubbing, refs, svgRef]); + + const { resolvedLabel, seriesItems } = useMemo(() => { + if (scrubberPosition === undefined) { + return { resolvedLabel: null, seriesItems: [] as TooltipSeriesItem[] }; + } + + // Resolve label + let resolvedLabel: React.ReactNode; + if (label !== undefined) { + resolvedLabel = typeof label === 'function' ? label(scrubberPosition) : label; + } else { + // Default to x-axis data value + const xAxis = getXAxis(); + if (xAxis?.data && xAxis.data[scrubberPosition] !== undefined) { + resolvedLabel = xAxis.data[scrubberPosition]; + } else { + resolvedLabel = scrubberPosition; + } + } + + // Wrap string label in Text + if (typeof resolvedLabel === 'string' || typeof resolvedLabel === 'number') { + resolvedLabel = {resolvedLabel}; + } + + // Filter series + const filteredSeries = seriesIds ? series.filter((s) => seriesIds.includes(s.id)) : series; + + // Resolve series data + const seriesItems: TooltipSeriesItem[] = []; + filteredSeries.forEach((s) => { + const data = getSeriesData(s.id); + const dataPoint = data?.[scrubberPosition]; + let value: number | undefined; + + if (dataPoint && dataPoint !== null) { + const [start, end] = dataPoint; + value = end - start; + } else if (s.data) { + const rawPoint = s.data[scrubberPosition]; + if (rawPoint !== undefined && rawPoint !== null) { + value = Array.isArray(rawPoint) ? (rawPoint[1] ?? undefined) : (rawPoint as number); + } + } + + if (value === undefined || value === null || Number.isNaN(value)) return; + + let formattedValue: React.ReactNode = value; + if (valueFormatter) { + formattedValue = valueFormatter(value); + } + + if (formattedValue === null || formattedValue === undefined) { + return; + } + + if (typeof formattedValue === 'string' || typeof formattedValue === 'number') { + formattedValue = {formattedValue}; + } + + seriesItems.push({ + id: s.id, + label: s.label, + color: s.color, + shape: s.legendShape, + value: formattedValue, + }); + }); + + return { + resolvedLabel: resolvedLabel ?? null, + seriesItems, + }; + }, [scrubberPosition, label, seriesIds, series, getSeriesData, getXAxis, valueFormatter]); + + // Measure legend media widths and find the maximum + useLayoutEffect(() => { + if (seriesItems.length === 0) { + setLegendMediaWidth(0); + return; + } + + let maxWidth = 0; + legendMediaRefs.current.forEach((el) => { + if (el) { + const width = el.scrollWidth; + if (width > maxWidth) { + maxWidth = width; + } + } + }); + + if (maxWidth !== legendMediaWidth) { + setLegendMediaWidth(maxWidth); + } + }, [seriesItems, legendMediaWidth]); + + // Create a ref callback for each legend media item + const setLegendMediaRef = useCallback( + (id: string) => (el: HTMLDivElement | null) => { + if (el) { + legendMediaRefs.current.set(id, el); + } else { + legendMediaRefs.current.delete(id); + } + }, + [], + ); + + if (!isTooltipVisible || (!resolvedLabel && seriesItems.length === 0)) { + return null; + } + + return ( + + + {resolvedLabel} + + {seriesItems.length > 0 && ( + + {seriesItems.map((item) => ( + + + 0 ? { width: legendMediaWidth } : undefined} + > + + + {item.label ?? item.id} + + {item.value} + + ))} + + )} + + + ); +}; + +type TooltipSeriesItem = { + id: string; + label?: string; + color?: string; + shape?: LegendShape; + value: React.ReactNode; +}; diff --git a/packages/web-visualization/src/chart/PolarChart.tsx b/packages/web-visualization/src/chart/PolarChart.tsx index e32b214b27..02ed2c19a3 100644 --- a/packages/web-visualization/src/chart/PolarChart.tsx +++ b/packages/web-visualization/src/chart/PolarChart.tsx @@ -1,8 +1,9 @@ -import React, { forwardRef, memo, useCallback, useMemo } from 'react'; +import { forwardRef, memo, useCallback, useMemo, useRef } from 'react'; import type { Rect } from '@coinbase/cds-common/types'; import { cx } from '@coinbase/cds-web'; import { useDimensions } from '@coinbase/cds-web/hooks/useDimensions'; import { Box, type BoxBaseProps, type BoxProps } from '@coinbase/cds-web/layout'; +import { css } from '@linaria/core'; import { PolarChartProvider } from './ChartProvider'; import { @@ -24,6 +25,18 @@ import { type RadialAxisConfigProps, } from './utils'; +const rootCss = css` + display: flex; + flex-direction: column; + overflow: hidden; +`; + +const middleRowCss = css` + display: flex; + flex: 1; + min-height: 0; +`; + export type PolarChartBaseProps = BoxBaseProps & { /** * Configuration object that defines the data to visualize. @@ -162,6 +175,20 @@ export const PolarChart = memo( ref, ) => { const { observe, width: chartWidth, height: chartHeight } = useDimensions(); + const topSlotRef = useRef(null); + const bottomSlotRef = useRef(null); + const leftSlotRef = useRef(null); + const rightSlotRef = useRef(null); + + const slotRefs = useMemo( + () => ({ + topRef: topSlotRef, + bottomRef: bottomSlotRef, + leftRef: leftSlotRef, + rightRef: rightSlotRef, + }), + [], + ); const inset = useMemo(() => { return getChartInset(insetInput, defaultChartInset); @@ -332,6 +359,7 @@ export const PolarChart = memo( getAngularScale, getRadialScale, dataLength, + slotRefs, }), [ series, @@ -347,11 +375,12 @@ export const PolarChart = memo( getAngularScale, getRadialScale, dataLength, + slotRefs, ], ); const rootClassNames = useMemo( - () => cx(className, classNames?.root), + () => cx(rootCss, className, classNames?.root), [className, classNames], ); const rootStyles = useMemo(() => ({ ...style, ...styles?.root }), [style, styles?.root]); @@ -359,30 +388,43 @@ export const PolarChart = memo( return ( { - observe(node as unknown as HTMLElement); - }} className={rootClassNames} height={height} style={rootStyles} width={width} {...props} > - - {children} + + + + { + const svgElement = node as unknown as SVGSVGElement; + observe(node as unknown as HTMLElement); + // Forward the ref to the user + if (ref) { + if (typeof ref === 'function') { + ref(svgElement); + } else { + (ref as React.MutableRefObject).current = svgElement; + } + } + }} + aria-live="polite" + as="svg" + className={classNames?.chart} + height="100%" + style={styles?.chart} + width="100%" + > + {children} + + + ); }, ), ); - diff --git a/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx b/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx new file mode 100644 index 0000000000..2181b79f68 --- /dev/null +++ b/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx @@ -0,0 +1,365 @@ +import { useCallback, useMemo } from 'react'; +import { Box, HStack, VStack } from '@coinbase/cds-web/layout'; +import { Text } from '@coinbase/cds-web/typography'; + +import { AreaChart } from '../area'; +import { XAxis, YAxis } from '../axis'; +import { BarPlot } from '../bar'; +import { CartesianChart } from '../CartesianChart'; +import { ChartTooltip } from '../ChartTooltip'; +import { Legend } from '../legend'; +import { LineChart } from '../line'; +import { Scrubber } from '../scrubber'; + +export default { + component: ChartTooltip, + title: 'Components/Chart/ChartTooltip', +}; + +const Example: React.FC> = ({ children, title }) => { + return ( + + + {title} + + {children} + + ); +}; + +const Basic = () => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const revenue = [120, 150, 180, 165, 190, 210]; + const expenses = [80, 95, 110, 105, 120, 130]; + + return ( + + + Hover over the chart to see the tooltip. The label defaults to the x-axis value. + + + + + + + ); +}; + +const WithValueFormatter = () => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const sales = [45000, 52000, 48000, 61000, 55000, 67000]; + const profit = [12000, 15000, 11000, 18000, 14000, 21000]; + + const currencyFormatter = useCallback( + (value: number) => + new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + maximumFractionDigits: 0, + }).format(value), + [], + ); + + return ( + + + Format tooltip values with a custom formatter function. + + + + + + + ); +}; + +const CustomLabel = () => { + const dates = ['2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06']; + const displayDates = [ + 'January 2024', + 'February 2024', + 'March 2024', + 'April 2024', + 'May 2024', + 'June 2024', + ]; + const temperature = [32, 35, 45, 58, 68, 78]; + const humidity = [65, 60, 55, 50, 55, 60]; + + return ( + + + Use a function to customize the tooltip label based on the data index. + + + + ( + + {displayDates[dataIndex]} + + )} + /> + + + ); +}; + +const FilteredSeries = () => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const primary = [100, 120, 140, 130, 150, 170]; + const secondary = [60, 70, 80, 75, 85, 95]; + const tertiary = [30, 35, 40, 38, 42, 48]; + + return ( + + + Show only specific series in the tooltip using the seriesIds prop. + + + + + + + + ); +}; + +const WithBarChart = () => { + const categories = ['Q1', 'Q2', 'Q3', 'Q4']; + const revenue = [250, 320, 280, 410]; + const costs = [180, 220, 200, 280]; + + const numberFormatter = useCallback((value: number) => `$${value}k`, []); + + return ( + + + ChartTooltip works with bar charts too. + + + + + + + + + + ); +}; + +const StackedAreaTooltip = () => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug']; + const desktop = [4000, 4200, 3800, 4500, 4800, 5200, 5000, 5500]; + const mobile = [2400, 2800, 3000, 3200, 3500, 3800, 4000, 4200]; + const tablet = [1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900]; + + return ( + + + Tooltip shows individual series values for stacked charts. + + + + + value.toLocaleString()} /> + + + ); +}; + +const CustomValueDisplay = () => { + const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + const steps = [8500, 12000, 7200, 9800, 11500, 15000, 6500]; + const goal = 10000; + + const stepsFormatter = useCallback((value: number) => { + const percentage = Math.round((value / goal) * 100); + const isGoalMet = value >= goal; + return ( + + + {value.toLocaleString()} steps + + + ({percentage}%) + + + ); + }, []); + + return ( + + + Return a custom ReactNode from valueFormatter for rich value display. + + + + + + + ); +}; + +const MultiAxisTooltip = () => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const revenue = [455, 520, 380, 455, 285, 235]; + const profitMargin = [23, 20, 16, 38, 12, 9]; + + return ( + + + Tooltip works with multiple y-axes, showing values from all series. + + + + `$${value}k`} + /> + `${value}%`} + /> + + + { + // Simple formatter - in real usage you'd differentiate by series + return value < 100 ? `${value}%` : `$${value}k`; + }} + /> + + + ); +}; + +export const All = () => { + return ( + + + + + + + + + + + ); +}; diff --git a/packages/web-visualization/src/chart/area/AreaChart.tsx b/packages/web-visualization/src/chart/area/AreaChart.tsx index 352534ef0d..853c9c7c6f 100644 --- a/packages/web-visualization/src/chart/area/AreaChart.tsx +++ b/packages/web-visualization/src/chart/area/AreaChart.tsx @@ -127,6 +127,7 @@ export const AreaChart = memo( yAxisId: s.yAxisId, stackId: s.stackId, gradient: s.gradient, + legendShape: s.legendShape, }), ); }, [series]); diff --git a/packages/web-visualization/src/chart/index.ts b/packages/web-visualization/src/chart/index.ts index 2265caa59e..b3f1145f32 100644 --- a/packages/web-visualization/src/chart/index.ts +++ b/packages/web-visualization/src/chart/index.ts @@ -4,6 +4,8 @@ export * from './axis/index'; export * from './bar/index'; export * from './CartesianChart'; export * from './ChartProvider'; +export * from './ChartSlot'; +export * from './ChartTooltip'; export * from './DonutChart'; export * from './gradient/index'; export * from './line/index'; diff --git a/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx b/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx new file mode 100644 index 0000000000..b568f4b3e6 --- /dev/null +++ b/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx @@ -0,0 +1,147 @@ +import { memo } from 'react'; +import type { SharedProps } from '@coinbase/cds-common/types'; +import { cx } from '@coinbase/cds-web'; +import { + Box, + HStack, + type HStackBaseProps, + type HStackDefaultElement, + type HStackProps, +} from '@coinbase/cds-web/layout'; +import { Text } from '@coinbase/cds-web/typography'; +import { css } from '@linaria/core'; + +import type { Series } from '../utils'; + +import { DefaultLegendShape, type LegendShapeComponent } from './DefaultLegendShape'; + +const shapeWrapperCss = css` + width: 10px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; +`; + +const legendItemCss = css` + align-items: center; +`; + +export type LegendItemBaseProps = Omit & + SharedProps & { + /** + * Id of the series. + */ + seriesId: Series['id']; + /** + * Display label for the legend item. + * Can be a string or a custom ReactNode. + * If a ReactNode is provided, it replaces the default Text component. + */ + label: React.ReactNode; + /** + * Color associated with the series. + */ + color?: Series['color']; + /** + * Shape to display in the legend. + */ + shape?: Series['legendShape']; + /** + * Custom component to render the legend shape. + * @default DefaultLegendShape + */ + ShapeComponent?: LegendShapeComponent; + }; + +export type LegendItemProps = Omit, 'children'> & + LegendItemBaseProps & { + /** + * Custom class names for the component parts. + */ + classNames?: { + /** + * Custom class name for the root element. + */ + root?: string; + /** + * Custom class name for the shape wrapper element. + */ + shapeWrapper?: string; + /** + * Custom class name for the shape element. + */ + shape?: string; + /** + * Custom class name for the text element. + */ + text?: string; + }; + /** + * Custom styles for the component parts. + */ + styles?: { + /** + * Custom styles for the root element. + */ + root?: React.CSSProperties; + /** + * Custom styles for the shape wrapper element. + */ + shapeWrapper?: React.CSSProperties; + /** + * Custom styles for the shape element. + */ + shape?: React.CSSProperties; + /** + * Custom styles for the text element. + */ + text?: React.CSSProperties; + }; + }; + +export type LegendItemComponent = React.FC; + +export const DefaultLegendItem = memo( + ({ + label, + color, + shape, + ShapeComponent = DefaultLegendShape, + gap = 1, + className, + classNames, + style, + styles, + testID, + ...props + }: LegendItemProps) => { + const isStringLabel = typeof label === 'string'; + + return ( + + + + + {isStringLabel ? ( + + {label} + + ) : ( + label + )} + + ); + }, +); diff --git a/packages/web-visualization/src/chart/legend/DefaultLegendShape.tsx b/packages/web-visualization/src/chart/legend/DefaultLegendShape.tsx new file mode 100644 index 0000000000..7d0c6e01b9 --- /dev/null +++ b/packages/web-visualization/src/chart/legend/DefaultLegendShape.tsx @@ -0,0 +1,77 @@ +import React, { memo } from 'react'; +import { cx } from '@coinbase/cds-web'; +import { Box, type BoxProps } from '@coinbase/cds-web/layout'; +import { css } from '@linaria/core'; + +import type { LegendShape, LegendShapeVariant } from '../utils/chart'; + +const pillCss = css` + width: 6px; + height: 24px; + border-radius: 3px; +`; + +const circleCss = css` + width: 10px; + height: 10px; + border-radius: 5px; +`; + +const squareCss = css` + width: 10px; + height: 10px; +`; + +const squircleCss = css` + width: 10px; + height: 10px; + border-radius: 2px; +`; + +const stylesByVariant: Record = { + pill: pillCss, + circle: circleCss, + square: squareCss, + squircle: squircleCss, +}; + +const isVariantShape = (shape: LegendShape): shape is LegendShapeVariant => + typeof shape === 'string' && shape in stylesByVariant; + +export type LegendShapeProps = BoxProps<'div'> & { + /** + * Color of the legend shape. + * @default 'var(--color-fgPrimary)' + */ + color?: string; + /** + * Shape to display. Can be a preset shape or a custom ReactNode. + * @default 'circle' + */ + shape?: LegendShape; +}; + +export type LegendShapeComponent = React.FC; + +/** + * Default shape component for chart legends. + * Renders a colored shape (pill, circle, square, or squircle) or a custom ReactNode. + */ +export const DefaultLegendShape = memo( + ({ color = 'var(--color-fgPrimary)', shape = 'circle', className, style, ...props }) => { + // If shape is a custom ReactNode, render it directly + if (!isVariantShape(shape)) { + return <>{shape}; + } + + const variantStyle = stylesByVariant[shape]; + + return ( + + ); + }, +); diff --git a/packages/web-visualization/src/chart/legend/Legend.tsx b/packages/web-visualization/src/chart/legend/Legend.tsx new file mode 100644 index 0000000000..6a0a79ef70 --- /dev/null +++ b/packages/web-visualization/src/chart/legend/Legend.tsx @@ -0,0 +1,101 @@ +import { forwardRef, memo, useMemo } from 'react'; +import { Box, type BoxProps } from '@coinbase/cds-web/layout'; + +import { useChartContext } from '../ChartProvider'; +import { ChartSlot } from '../ChartSlot'; + +import { DefaultLegendItem, type LegendItemComponent } from './DefaultLegendItem'; +import type { LegendShapeComponent } from './DefaultLegendShape'; + +export type LegendBaseProps = { + /** + * The position of the legend relative to the chart. + * @default 'top' + */ + position?: 'top' | 'bottom' | 'left' | 'right'; + /** + * Array of series IDs to display in the legend. + * By default, all series will be displayed. + */ + seriesIds?: string[]; + /** + * Custom component to render each legend item. + * @default DefaultLegendItem + */ + ItemComponent?: LegendItemComponent; + /** + * Custom component to render the legend shape within each item. + * Only used when ItemComponent is not provided or is DefaultLegendItem. + * @default DefaultLegendShape + */ + ShapeComponent?: LegendShapeComponent; +}; + +export type LegendProps = Omit, 'position'> & LegendBaseProps; + +export const Legend = memo( + forwardRef( + ( + { + position = 'top', + flexDirection = position === 'top' || position === 'bottom' ? 'row' : 'column', + justifyContent = 'center', + alignItems = position === 'top' || position === 'bottom' ? 'center' : 'flex-start', + flexWrap = 'wrap', + gap = 1, + seriesIds, + ItemComponent = DefaultLegendItem, + ShapeComponent, + width = position === 'top' || position === 'bottom' ? '100%' : undefined, + height = position === 'left' || position === 'right' ? '100%' : undefined, + ...props + }, + ref, + ) => { + const { series, slotRefs } = useChartContext(); + + const filteredSeries = useMemo(() => { + if (seriesIds === undefined) return series; + return series.filter((s) => seriesIds.includes(s.id)); + }, [series, seriesIds]); + + if (filteredSeries.length === 0) return; + + const slotRef = + position === 'top' + ? slotRefs?.topRef + : position === 'bottom' + ? slotRefs?.bottomRef + : position === 'left' + ? slotRefs?.leftRef + : slotRefs?.rightRef; + + return ( + + + {filteredSeries.map((s) => ( + + ))} + + + ); + }, + ), +); diff --git a/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx b/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx new file mode 100644 index 0000000000..a7f84e560a --- /dev/null +++ b/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx @@ -0,0 +1,667 @@ +import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'; +import { Chip } from '@coinbase/cds-web/chips'; +import { Box, HStack, VStack } from '@coinbase/cds-web/layout'; +import { Text } from '@coinbase/cds-web/typography'; + +import { XAxis, YAxis } from '../../axis'; +import { type BarComponentProps, BarPlot, DefaultBar } from '../../bar'; +import { CartesianChart } from '../../CartesianChart'; +import { useCartesianChartContext } from '../../ChartProvider'; +import { ChartTooltip } from '../../ChartTooltip'; +import { DonutChart } from '../../DonutChart'; +import { LineChart } from '../../line'; +import { PieChart } from '../../pie'; +import { Scrubber } from '../../scrubber'; +import type { LegendShapeVariant } from '../../utils/chart'; +import { useScrubberContext } from '../../utils/context'; +import { type LegendItemProps } from '../DefaultLegendItem'; +import { DefaultLegendShape } from '../DefaultLegendShape'; +import { Legend } from '../Legend'; + +export default { + component: Legend, + title: 'Components/Chart/Legend', +}; + +const Example: React.FC> = ({ children, title }) => { + return ( + + + {title} + + {children} + + ); +}; + +const spectrumColors = [ + 'blue', + 'green', + 'orange', + 'yellow', + 'gray', + 'indigo', + 'pink', + 'purple', + 'red', + 'teal', + 'chartreuse', +]; + +const shapes: LegendShapeVariant[] = ['pill', 'circle', 'squircle', 'square']; + +const Shapes = () => { + return ( + + + {shapes.map((shape) => ( + + {spectrumColors.map((color) => ( + + + + ))} + + ))} + + + ); +}; + +const Basic = () => { + const pages = useMemo( + () => ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'], + [], + ); + const pageViews = useMemo(() => [2400, 1398, 9800, 3908, 4800, 3800, 4300], []); + const uniqueVisitors = useMemo(() => [4000, 3000, 2000, 2780, 1890, 2390, 3490], []); + + const numberFormatter = useCallback( + (value: number) => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value), + [], + ); + + return ( + + + + + + ); +}; + +const AutoScale = () => { + const precipitationData = [ + { + id: 'northeast', + label: 'Northeast', + data: [5.14, 1.53, 5.73, 4.29, 3.78, 3.92, 4.19, 5.54, 2.03, 1.42, 2.95, 3.89], + color: 'rgb(var(--blue40))', + }, + { + id: 'upperMidwest', + label: 'Upper Midwest', + data: [1.44, 0.49, 2.16, 3.67, 5.44, 6.21, 4.02, 3.67, 0.92, 1.47, 3.05, 1.48], + color: 'rgb(var(--green40))', + }, + { + id: 'ohioValley', + label: 'Ohio Valley', + data: [4.74, 1.83, 3.1, 5.42, 5.69, 3.29, 5.02, 2.57, 4.13, 0.79, 4.31, 3.67], + color: 'rgb(var(--orange40))', + }, + { + id: 'southeast', + label: 'Southeast', + data: [5.48, 3.11, 5.73, 2.97, 5.45, 3.28, 7.18, 5.67, 7.93, 1.33, 2.69, 3.21], + color: 'rgb(var(--yellow40))', + }, + { + id: 'northernRockiesAndPlains', + label: 'Northern Rockies and Plains', + data: [0.64, 1.01, 1.06, 2.12, 3.34, 2.65, 1.54, 1.89, 0.95, 0.57, 1.23, 0.67], + color: 'rgb(var(--indigo40))', + }, + { + id: 'south', + label: 'South', + data: [4.19, 1.79, 2.93, 3.84, 5.25, 3.4, 4.27, 1.84, 3.08, 0.52, 4.5, 2.62], + color: 'rgb(var(--pink40))', + }, + { + id: 'southwest', + label: 'Southwest', + data: [1.12, 1.5, 1.52, 0.75, 0.76, 1.27, 1.44, 2.01, 0.62, 1.08, 1.23, 0.25], + color: 'rgb(var(--purple40))', + }, + { + id: 'northwest', + label: 'Northwest', + data: [5.69, 3.67, 3.32, 1.95, 2.08, 1.31, 0.28, 0.81, 0.95, 2.03, 5.45, 5.8], + color: 'rgb(var(--red40))', + }, + { + id: 'west', + label: 'West', + data: [3.39, 4.7, 3.09, 1.07, 0.55, 0.12, 0.23, 0.26, 0.22, 0.4, 2.7, 2.54], + color: 'rgb(var(--teal40))', + }, + ]; + + const xAxisData = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + + return ( + + + + 2024 Precipitation by Climate Region + + + + + `${value} in`} /> + + + + ); +}; + +const Position = () => { + return ( + + + + `$${value}k`} + width={60} + /> + `${value}%`} + /> + + + + + ); +}; + +const ShapeVariants = () => { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + + return ( + + + + + + ); +}; + +const DynamicData = () => { + const timeLabels = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + const series = [ + { + id: 'candidate-a', + label: 'Candidate A', + data: [48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 38], + color: 'rgb(var(--blue40))', + legendIndicator: 'circle' as const, + }, + { + id: 'candidate-b', + label: 'Candidate B', + data: [null, null, null, 6, 10, 14, 18, 22, 26, 29, 32, 35], + color: 'rgb(var(--orange40))', + legendIndicator: 'circle' as const, + }, + { + id: 'candidate-c', + label: 'Candidate C', + data: [52, 53, 54, 49, 46, 43, 40, 37, 34, 32, 30, 27], + color: 'rgb(var(--gray40))', + legendIndicator: 'circle' as const, + }, + ]; + + const ValueLegendItem = memo(function ValueLegendItem({ + seriesId, + label, + color, + shape, + }: LegendItemProps) { + const { scrubberPosition } = useScrubberContext(); + const { series, dataLength } = useCartesianChartContext(); + + const dataIndex = scrubberPosition ?? dataLength - 1; + + const seriesData = series.find((s) => s.id === seriesId); + const rawValue = seriesData?.data?.[dataIndex]; + + const formattedValue = + rawValue === null || rawValue === undefined ? '--' : `${Math.round(rawValue as number)}%`; + + return ( + + + {label} + + {formattedValue} + + + ); + }); + + return ( + + + + Election Polls + + `${value}%`, + }} + > + + + + + + ); +}; + +const Interactive = () => { + const [emphasizedId, setEmphasizedId] = useState(null); + + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + const seriesConfig = useMemo( + () => [ + { + id: 'revenue', + label: 'Revenue', + data: [120, 150, 180, 165, 190, 210, 240, 220, 260, 280, 310, 350], + baseColor: '--blue', + }, + { + id: 'expenses', + label: 'Expenses', + data: [80, 95, 110, 105, 120, 130, 145, 140, 155, 165, 180, 195], + baseColor: '--orange', + }, + { + id: 'profit', + label: 'Profit', + data: [40, 55, 70, 60, 70, 80, 95, 80, 105, 115, 130, 155], + baseColor: '--green', + }, + ], + [], + ); + + const handleToggle = useCallback((seriesId: string) => { + setEmphasizedId((prev) => (prev === seriesId ? null : seriesId)); + }, []); + + const ChipLegendItem = memo(function ChipLegendItem({ seriesId, label }: LegendItemProps) { + const chipRef = useRef(null); + const isEmphasized = emphasizedId === seriesId; + const config = seriesConfig.find((s) => s.id === seriesId); + const baseColor = config?.baseColor ?? '--gray'; + + // Restore focus when chip becomes emphasized + useEffect(() => { + if (isEmphasized && chipRef.current) { + chipRef.current.focus(); + } + }, [isEmphasized]); + + return ( + handleToggle(seriesId)} + style={{ + backgroundColor: `rgb(var(${baseColor}10))`, + borderWidth: 0, + color: 'var(--color-fg)', + outlineColor: `rgb(var(${baseColor}50))`, + }} + > + + + {label} + + + ); + }); + + const series = useMemo(() => { + return seriesConfig.map((config) => { + const isEmphasized = emphasizedId === config.id; + const isDimmed = emphasizedId !== null && !isEmphasized; + + return { + id: config.id, + label: config.label, + data: config.data, + color: `rgb(var(${config.baseColor}40))`, + opacity: isDimmed ? 0.3 : 1, + }; + }); + }, [emphasizedId, seriesConfig]); + + return ( + + + + Financial Overview + + `$${value}k`, + }} + > + + + + + ); +}; + +const PieChartLegend = () => { + const series = [ + { + id: 'stocks', + data: 45, + label: 'Stocks', + color: 'rgb(var(--blue40))', + legendShape: 'circle' as const, + }, + { + id: 'bonds', + data: 25, + label: 'Bonds', + color: 'rgb(var(--green40))', + legendShape: 'circle' as const, + }, + { + id: 'realEstate', + data: 15, + label: 'Real Estate', + color: 'rgb(var(--orange40))', + legendShape: 'circle' as const, + }, + { + id: 'commodities', + data: 10, + label: 'Commodities', + color: 'rgb(var(--purple40))', + legendShape: 'circle' as const, + }, + { + id: 'cash', + data: 5, + label: 'Cash', + color: 'rgb(var(--gray40))', + legendShape: 'circle' as const, + }, + ]; + + return ( + + + + Portfolio Allocation + + + + + + + ); +}; + +const DonutChartLegend = () => { + const series = [ + { + id: 'completed', + data: 68, + label: 'Completed', + color: 'var(--color-fgPositive)', + legendShape: 'squircle' as const, + }, + { + id: 'inProgress', + data: 22, + label: 'In Progress', + color: 'rgb(var(--blue40))', + legendShape: 'squircle' as const, + }, + { + id: 'pending', + data: 10, + label: 'Pending', + color: 'rgb(var(--gray40))', + legendShape: 'squircle' as const, + }, + ]; + + return ( + + + + Task Status + + + + + + + ); +}; + +export const All = () => { + return ( + + + + + + + + + + + + ); +}; diff --git a/packages/web-visualization/src/chart/legend/index.tsx b/packages/web-visualization/src/chart/legend/index.tsx new file mode 100644 index 0000000000..ba2be3549c --- /dev/null +++ b/packages/web-visualization/src/chart/legend/index.tsx @@ -0,0 +1,3 @@ +export * from './DefaultLegendItem'; +export * from './DefaultLegendShape'; +export * from './Legend'; diff --git a/packages/web-visualization/src/chart/line/LineChart.tsx b/packages/web-visualization/src/chart/line/LineChart.tsx index 5e3b71d00e..753b50042a 100644 --- a/packages/web-visualization/src/chart/line/LineChart.tsx +++ b/packages/web-visualization/src/chart/line/LineChart.tsx @@ -126,6 +126,7 @@ export const LineChart = memo( yAxisId: s.yAxisId, stackId: s.stackId, gradient: s.gradient, + legendShape: s.legendShape, }), ); }, [series]); diff --git a/packages/web-visualization/src/chart/utils/chart.ts b/packages/web-visualization/src/chart/utils/chart.ts index ca9330e374..df0be9f64b 100644 --- a/packages/web-visualization/src/chart/utils/chart.ts +++ b/packages/web-visualization/src/chart/utils/chart.ts @@ -17,6 +17,10 @@ export type AxisBounds = { export const isValidBounds = (bounds: Partial): bounds is AxisBounds => bounds.min !== undefined && bounds.max !== undefined; +export type LegendShapeVariant = 'circle' | 'square' | 'squircle' | 'pill'; + +export type LegendShape = LegendShapeVariant | React.ReactNode; + export type Series = { /** * Id of the series. @@ -33,6 +37,12 @@ export type Series = { * Color will still be used by scrubber beacon labels */ color?: string; + /** + * Legend shape for this series. + * Can be a LegendShapeVariant or a custom ReactNode. + * @default 'circle' + */ + legendShape?: LegendShape; }; export type CartesianSeries = Series & { diff --git a/packages/web-visualization/src/chart/utils/context.ts b/packages/web-visualization/src/chart/utils/context.ts index 95ed097f1f..4014d6b7b1 100644 --- a/packages/web-visualization/src/chart/utils/context.ts +++ b/packages/web-visualization/src/chart/utils/context.ts @@ -2,13 +2,18 @@ import { createContext, useContext } from 'react'; import type { Rect } from '@coinbase/cds-common/types'; import type { AngularAxisConfig, CartesianAxisConfig, RadialAxisConfig } from './axis'; -import type { CartesianSeries, PolarSeries } from './chart'; +import type { CartesianSeries, PolarSeries, Series } from './chart'; import type { ChartScaleFunction } from './scale'; /** * Base context value for all chart types. */ export type ChartContextValue = { + /** + * The series data for the chart. + * Contains common series properties (id, label, color, legendShape). + */ + series: Series[]; /** * Whether to animate the chart. */ @@ -29,13 +34,26 @@ export type ChartContextValue = { * Length of the data domain. */ dataLength: number; + /** + * Reference to the chart's SVG element. + */ + svgRef?: React.RefObject; + /** + * References to the chart's slot containers for slot content. + */ + slotRefs?: { + topRef: React.RefObject; + bottomRef: React.RefObject; + leftRef: React.RefObject; + rightRef: React.RefObject; + }; }; /** * Context value for Cartesian (X/Y) coordinate charts. * Contains axis-specific methods and properties for rectangular coordinate systems. */ -export type CartesianChartContextValue = ChartContextValue & { +export type CartesianChartContextValue = Omit & { /** * The series data for the chart. */ @@ -92,7 +110,7 @@ export type CartesianChartContextValue = ChartContextValue & { * Context value for Polar (Angular/Radial) coordinate charts. * Contains axis-specific methods and properties for polar coordinate systems. */ -export type PolarChartContextValue = ChartContextValue & { +export type PolarChartContextValue = Omit & { /** * The series data for the chart. */ diff --git a/yarn.lock b/yarn.lock index 8a5d2380ac..286d26860c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2609,6 +2609,7 @@ __metadata: "@coinbase/cds-lottie-files": "workspace:^" "@coinbase/cds-utils": "workspace:^" "@coinbase/cds-web": "workspace:^" + "@floating-ui/react-dom": ^2.1.1 react: ^18.3.1 react-dom: ^18.3.1 languageName: unknown From 97c7c3103de46ccc1432150c9dd22e368786dcef Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Mon, 8 Dec 2025 11:22:42 -0500 Subject: [PATCH 02/13] Support hideBeaconLabels and bar component --- .../src/chart/bar/Bar.tsx | 10 +- .../src/chart/bar/BarChart.tsx | 11 +- .../src/chart/bar/BarStack.tsx | 17 +- .../src/chart/scrubber/Scrubber.tsx | 7 +- .../web-visualization/src/chart/bar/Bar.tsx | 10 +- .../src/chart/bar/BarChart.tsx | 11 +- .../src/chart/bar/BarStack.tsx | 14 +- .../legend/__stories__/Legend.stories.tsx | 173 +++++++++++++++++- .../src/chart/scrubber/Scrubber.tsx | 7 +- 9 files changed, 225 insertions(+), 35 deletions(-) diff --git a/packages/mobile-visualization/src/chart/bar/Bar.tsx b/packages/mobile-visualization/src/chart/bar/Bar.tsx index c6e8ae0524..becb330a42 100644 --- a/packages/mobile-visualization/src/chart/bar/Bar.tsx +++ b/packages/mobile-visualization/src/chart/bar/Bar.tsx @@ -6,6 +6,10 @@ import { getBarPath, type Transition } from '../utils'; import { DefaultBar } from './DefaultBar'; export type BarBaseProps = { + /** + * The series ID this bar belongs to. + */ + seriesId?: string; /** * X coordinate of the bar (left edge). */ @@ -99,6 +103,7 @@ export type BarComponent = React.FC; */ export const Bar = memo( ({ + seriesId, x, y, width, @@ -129,9 +134,7 @@ export const Bar = memo( const effectiveOriginY = originY ?? y + height; - if (!barPath) { - return null; - } + if (!barPath) return; // Always use the BarComponent for rendering return ( @@ -146,6 +149,7 @@ export const Bar = memo( originY={effectiveOriginY} roundBottom={roundBottom} roundTop={roundTop} + seriesId={seriesId} stroke={stroke} strokeWidth={strokeWidth} transition={transition} diff --git a/packages/mobile-visualization/src/chart/bar/BarChart.tsx b/packages/mobile-visualization/src/chart/bar/BarChart.tsx index a47211d11e..e681477b15 100644 --- a/packages/mobile-visualization/src/chart/bar/BarChart.tsx +++ b/packages/mobile-visualization/src/chart/bar/BarChart.tsx @@ -7,15 +7,10 @@ import { type CartesianChartBaseProps, type CartesianChartProps, } from '../CartesianChart'; -import { - type CartesianAxisConfigProps, - type CartesianSeries, - defaultChartInset, - defaultStackId, - getChartInset, -} from '../utils'; +import { type CartesianAxisConfigProps, defaultChartInset, defaultStackId, getChartInset } from '../utils'; import { BarPlot, type BarPlotProps } from './BarPlot'; +import type { BarSeries } from './BarStack'; export type BarChartBaseProps = Omit & Pick< @@ -36,7 +31,7 @@ export type BarChartBaseProps = Omit; + series?: Array; /** * Whether to stack the areas on top of each other. * When true, each series builds cumulative values on top of the previous series. diff --git a/packages/mobile-visualization/src/chart/bar/BarStack.tsx b/packages/mobile-visualization/src/chart/bar/BarStack.tsx index e5698e490e..7d551589d8 100644 --- a/packages/mobile-visualization/src/chart/bar/BarStack.tsx +++ b/packages/mobile-visualization/src/chart/bar/BarStack.tsx @@ -7,11 +7,21 @@ import type { CartesianSeries, ChartScaleFunction, Transition } from '../utils'; import { evaluateGradientAtValue, getGradientStops } from '../utils/gradient'; import { convertToSerializableScale } from '../utils/scale'; -import { Bar, type BarProps } from './Bar'; +import { Bar, type BarComponent, type BarProps } from './Bar'; import { DefaultBarStack } from './DefaultBarStack'; const EPSILON = 1e-4; +/** + * Extended series type that includes bar-specific properties. + */ +export type BarSeries = CartesianSeries & { + /** + * Custom component to render bars for this series. + */ + BarComponent?: BarComponent; +}; + export type BarStackBaseProps = Pick< BarProps, 'BarComponent' | 'fillOpacity' | 'stroke' | 'strokeWidth' | 'borderRadius' @@ -19,7 +29,7 @@ export type BarStackBaseProps = Pick< /** * Array of series configurations that belong to this stack. */ - series: CartesianSeries[]; + series: BarSeries[]; /** * The category index for this stack. */ @@ -180,6 +190,7 @@ export const BarStack = memo( roundTop?: boolean; roundBottom?: boolean; shouldApplyGap?: boolean; + BarComponent?: BarComponent; }> = []; // Track how many bars we've stacked in each direction for gap calculation @@ -262,6 +273,7 @@ export const BarStack = memo( width, height, dataY: value, // Store the actual data value + BarComponent: s.BarComponent, fill: barFill, // Check if the bar should be rounded based on the baseline, with an epsilon to handle floating-point rounding roundTop: roundBaseline || Math.abs(barTop - baseline) >= EPSILON, @@ -676,6 +688,7 @@ export const BarStack = memo( originY={baseline} roundBottom={bar.roundBottom} roundTop={bar.roundTop} + seriesId={bar.seriesId} stroke={defaultStroke} strokeWidth={defaultStrokeWidth} transition={transition} diff --git a/packages/mobile-visualization/src/chart/scrubber/Scrubber.tsx b/packages/mobile-visualization/src/chart/scrubber/Scrubber.tsx index 89d619b2da..b3e0b4bf8e 100644 --- a/packages/mobile-visualization/src/chart/scrubber/Scrubber.tsx +++ b/packages/mobile-visualization/src/chart/scrubber/Scrubber.tsx @@ -146,6 +146,10 @@ export type ScrubberBaseProps = Pick * By default, all series will be highlighted. */ seriesIds?: string[]; + /** + * Hides the beacon labels while keeping the line label visible (if provided). + */ + hideBeaconLabels?: boolean; /** * Hides the scrubber line. * @note This hides Scrubber's ReferenceLine including the label. @@ -211,6 +215,7 @@ export const Scrubber = memo( ( { seriesIds, + hideBeaconLabels, hideLine, label, lineStroke, @@ -380,7 +385,7 @@ export const Scrubber = memo( seriesIds={filteredSeriesIds} transitions={beaconTransitions} /> - {beaconLabels.length > 0 && ( + {!hideBeaconLabels && beaconLabels.length > 0 && ( ; */ export const Bar = memo( ({ + seriesId, x, y, width, @@ -124,9 +129,7 @@ export const Bar = memo( const effectiveOriginY = originY ?? y + height; - if (!barPath) { - return null; - } + if (!barPath) return; return ( ( originY={effectiveOriginY} roundBottom={roundBottom} roundTop={roundTop} + seriesId={seriesId} stroke={stroke} strokeWidth={strokeWidth} transition={transition} diff --git a/packages/web-visualization/src/chart/bar/BarChart.tsx b/packages/web-visualization/src/chart/bar/BarChart.tsx index 038a5a2634..44336c05a6 100644 --- a/packages/web-visualization/src/chart/bar/BarChart.tsx +++ b/packages/web-visualization/src/chart/bar/BarChart.tsx @@ -6,15 +6,10 @@ import { type CartesianChartBaseProps, type CartesianChartProps, } from '../CartesianChart'; -import { - type CartesianAxisConfigProps, - type CartesianSeries, - defaultChartInset, - defaultStackId, - getChartInset, -} from '../utils'; +import { type CartesianAxisConfigProps, defaultChartInset, defaultStackId, getChartInset } from '../utils'; import { BarPlot, type BarPlotProps } from './BarPlot'; +import type { BarSeries } from './BarStack'; export type BarChartBaseProps = Omit & Pick< @@ -35,7 +30,7 @@ export type BarChartBaseProps = Omit; + series?: Array; /** * Whether to stack the areas on top of each other. * When true, each series builds cumulative values on top of the previous series. diff --git a/packages/web-visualization/src/chart/bar/BarStack.tsx b/packages/web-visualization/src/chart/bar/BarStack.tsx index 8b768a93fd..d35a458981 100644 --- a/packages/web-visualization/src/chart/bar/BarStack.tsx +++ b/packages/web-visualization/src/chart/bar/BarStack.tsx @@ -11,6 +11,16 @@ import { DefaultBarStack } from './DefaultBarStack'; const EPSILON = 1e-4; +/** + * Extended series type that includes bar-specific properties. + */ +export type BarSeries = CartesianSeries & { + /** + * Custom component to render bars for this series. + */ + BarComponent?: BarComponent; +}; + export type BarStackBaseProps = Pick< BarProps, 'BarComponent' | 'fillOpacity' | 'stroke' | 'strokeWidth' | 'borderRadius' @@ -18,7 +28,7 @@ export type BarStackBaseProps = Pick< /** * Array of series configurations that belong to this stack. */ - series: CartesianSeries[]; + series: BarSeries[]; /** * The category index for this stack. */ @@ -276,6 +286,7 @@ export const BarStack = memo( // Check if the bar should be rounded based on the baseline, with an epsilon to handle floating-point rounding roundTop: roundBaseline || Math.abs(barTop - baseline) >= EPSILON, roundBottom: roundBaseline || Math.abs(barBottom - baseline) >= EPSILON, + BarComponent: s.BarComponent, shouldApplyGap, }); }); @@ -687,6 +698,7 @@ export const BarStack = memo( originY={baseline} roundBottom={bar.roundBottom} roundTop={bar.roundTop} + seriesId={bar.seriesId} stroke={bar.stroke ?? defaultStroke} strokeWidth={bar.strokeWidth ?? defaultStrokeWidth} transition={transition} diff --git a/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx b/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx index a7f84e560a..f7bc952ef9 100644 --- a/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx +++ b/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx @@ -4,7 +4,7 @@ import { Box, HStack, VStack } from '@coinbase/cds-web/layout'; import { Text } from '@coinbase/cds-web/typography'; import { XAxis, YAxis } from '../../axis'; -import { type BarComponentProps, BarPlot, DefaultBar } from '../../bar'; +import { type BarComponentProps, BarPlot, type BarSeries, DefaultBar } from '../../bar'; import { CartesianChart } from '../../CartesianChart'; import { useCartesianChartContext } from '../../ChartProvider'; import { ChartTooltip } from '../../ChartTooltip'; @@ -12,7 +12,7 @@ import { DonutChart } from '../../DonutChart'; import { LineChart } from '../../line'; import { PieChart } from '../../pie'; import { Scrubber } from '../../scrubber'; -import type { LegendShapeVariant } from '../../utils/chart'; +import type { CartesianSeries, LegendShapeVariant } from '../../utils/chart'; import { useScrubberContext } from '../../utils/context'; import { type LegendItemProps } from '../DefaultLegendItem'; import { DefaultLegendShape } from '../DefaultLegendShape'; @@ -112,6 +112,7 @@ const Basic = () => { tickLabelFormatter: numberFormatter, }} > + @@ -212,7 +213,7 @@ const AutoScale = () => { showTickMarks: true, }} > - + `${value} in`} /> @@ -351,27 +352,27 @@ const DynamicData = () => { 'Dec', ]; - const series = [ + const series: CartesianSeries[] = [ { id: 'candidate-a', label: 'Candidate A', data: [48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 38], color: 'rgb(var(--blue40))', - legendIndicator: 'circle' as const, + legendShape: 'circle', }, { id: 'candidate-b', label: 'Candidate B', data: [null, null, null, 6, 10, 14, 18, 22, 26, 29, 32, 35], color: 'rgb(var(--orange40))', - legendIndicator: 'circle' as const, + legendShape: 'circle', }, { id: 'candidate-c', label: 'Candidate C', data: [52, 53, 54, 49, 46, 43, 40, 37, 34, 32, 30, 27], color: 'rgb(var(--gray40))', - legendIndicator: 'circle' as const, + legendShape: 'circle', }, ]; @@ -425,7 +426,7 @@ const DynamicData = () => { tickLabelFormatter: (value) => `${value}%`, }} > - + @@ -611,6 +612,161 @@ const PieChartLegend = () => { ); }; +const LegendShapes = () => { + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + // Actual revenue (first 9 months) + const actualRevenue = [320, 380, 420, 390, 450, 480, 520, 490, 540, null, null, null]; + + // Forecasted revenue (last 3 months) + const forecastRevenue = [null, null, null, null, null, null, null, null, null, 580, 620, 680]; + + const numberFormatter = useCallback( + (value: number) => + `$${new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value)}k`, + [], + ); + + // Pattern settings for dotted fill + const patternSize = 4; + const dotSize = 1; + const patternId = useId(); + const maskId = useId(); + const legendPatternId = useId(); + + // Custom legend indicator that matches the dotted bar pattern + const DottedLegendIndicator = ( + + + + + + + + + + + + + + + ); + + // Custom bar component that renders bars with dotted pattern fill + const DottedBarComponent = memo(function DottedBarComponent(props) { + const { dataX, x, y } = props; + // Create unique IDs per bar so patterns are scoped to each bar + const uniqueMaskId = `${maskId}-${dataX}`; + const uniquePatternId = `${patternId}-${dataX}`; + return ( + <> + + {/* Pattern positioned relative to this bar's origin */} + + + + + + + + + + + + + ); + }); + + return ( + + + + Annual Revenue + + + + + + + + + + ); +}; + const DonutChartLegend = () => { const series = [ { @@ -662,6 +818,7 @@ export const All = () => { + ); }; diff --git a/packages/web-visualization/src/chart/scrubber/Scrubber.tsx b/packages/web-visualization/src/chart/scrubber/Scrubber.tsx index cc4b1a05e9..d8f32c94dd 100644 --- a/packages/web-visualization/src/chart/scrubber/Scrubber.tsx +++ b/packages/web-visualization/src/chart/scrubber/Scrubber.tsx @@ -135,6 +135,10 @@ export type ScrubberBaseProps = SharedProps & * By default, all series will be highlighted. */ seriesIds?: string[]; + /** + * Hides the beacon labels while keeping the line label visible (if provided). + */ + hideBeaconLabels?: boolean; /** * Hides the scrubber line. * @note This hides Scrubber's ReferenceLine including the label. @@ -226,6 +230,7 @@ export const Scrubber = memo( ( { seriesIds, + hideBeaconLabels, hideLine, label, accessibilityLabel, @@ -383,7 +388,7 @@ export const Scrubber = memo( testID={testID} transitions={beaconTransitions} /> - {beaconLabels.length > 0 && ( + {!hideBeaconLabels && beaconLabels.length > 0 && ( Date: Mon, 8 Dec 2025 11:29:16 -0500 Subject: [PATCH 03/13] Cleanup web tooltip examples --- .../__stories__/ChartTooltip.stories.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx b/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx index 2181b79f68..f3977c5e87 100644 --- a/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx +++ b/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx @@ -49,7 +49,7 @@ const Basic = () => { xAxis={{ data: months }} yAxis={{ domain: { min: 0 }, showGrid: true }} > - + @@ -86,9 +86,9 @@ const WithValueFormatter = () => { { id: 'profit', label: 'Profit', data: profit, color: 'rgb(var(--purple40))' }, ]} xAxis={{ data: months }} - yAxis={{ domain: { min: 0 }, showGrid: true }} + yAxis={{ domain: { min: 0 }, showGrid: true, tickLabelFormatter: currencyFormatter }} > - + @@ -131,7 +131,7 @@ const CustomLabel = () => { xAxis={{ data: dates }} yAxis={{ domain: { min: 0 }, showGrid: true }} > - + ( @@ -165,7 +165,7 @@ const FilteredSeries = () => { { id: 'secondary', label: 'Secondary', data: secondary, color: 'rgb(var(--green40))' }, { id: 'tertiary', - label: 'Tertiary (hidden)', + label: 'Tertiary', data: tertiary, color: 'rgb(var(--gray40))', }, @@ -206,7 +206,6 @@ const WithBarChart = () => { - @@ -219,6 +218,8 @@ const StackedAreaTooltip = () => { const mobile = [2400, 2800, 3000, 3200, 3500, 3800, 4000, 4200]; const tablet = [1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900]; + const numberFormatter = useCallback((value: number) => value.toLocaleString(), []); + return ( @@ -236,11 +237,11 @@ const StackedAreaTooltip = () => { { id: 'tablet', label: 'Tablet', data: tablet, color: 'rgb(var(--orange40))' }, ]} xAxis={{ data: months }} - yAxis={{ domain: { min: 0 }, showGrid: true }} + yAxis={{ domain: { min: 0 }, showGrid: true, tickLabelFormatter: numberFormatter }} > - + - value.toLocaleString()} /> + ); @@ -280,7 +281,7 @@ const CustomValueDisplay = () => { xAxis={{ data: days }} yAxis={{ domain: { min: 0, max: 20000 }, showGrid: true }} > - + @@ -337,7 +338,6 @@ const MultiAxisTooltip = () => { tickLabelFormatter={(value) => `${value}%`} /> - { // Simple formatter - in real usage you'd differentiate by series From e541fd01efb82b1f69a8649a6d0c76827dc49e2d Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Mon, 8 Dec 2025 14:18:55 -0500 Subject: [PATCH 04/13] Switch from legend being a child to being a prop --- .../src/chart/CartesianChart.tsx | 140 +++++++++++------- .../web-visualization/src/chart/ChartSlot.tsx | 27 ---- .../src/chart/ChartTooltip.tsx | 6 +- .../src/chart/PolarChart.tsx | 129 ++++++++++------ .../__stories__/ChartTooltip.stories.tsx | 4 +- packages/web-visualization/src/chart/index.ts | 1 - .../src/chart/legend/Legend.tsx | 79 ++++------ .../legend/__stories__/Legend.stories.tsx | 47 +++--- .../src/chart/scrubber/ScrubberProvider.tsx | 13 +- .../src/chart/utils/context.ts | 13 +- 10 files changed, 238 insertions(+), 221 deletions(-) delete mode 100644 packages/web-visualization/src/chart/ChartSlot.tsx diff --git a/packages/web-visualization/src/chart/CartesianChart.tsx b/packages/web-visualization/src/chart/CartesianChart.tsx index ec9f32e936..fc9de28d5c 100644 --- a/packages/web-visualization/src/chart/CartesianChart.tsx +++ b/packages/web-visualization/src/chart/CartesianChart.tsx @@ -5,6 +5,7 @@ import { useDimensions } from '@coinbase/cds-web/hooks/useDimensions'; import { Box, type BoxBaseProps, type BoxProps } from '@coinbase/cds-web/layout'; import { css } from '@linaria/core'; +import { Legend } from './legend/Legend'; import { ScrubberProvider, type ScrubberProviderProps } from './scrubber/ScrubberProvider'; import { CartesianChartProvider } from './ChartProvider'; import { @@ -27,7 +28,6 @@ import { const rootCss = css` display: flex; - flex-direction: column; overflow: hidden; `; const focusCss = css` @@ -39,12 +39,20 @@ const focusCss = css` outline-offset: 2px; } `; -const middleRowCss = css` - display: flex; +const verticalCss = css` + flex-direction: column; +`; +const horizontalCss = css` + flex-direction: row; +`; +const chartContainerCss = css` flex: 1; min-height: 0; + min-width: 0; `; +export type LegendPosition = 'top' | 'bottom' | 'left' | 'right'; + export type CartesianChartBaseProps = BoxBaseProps & Pick & { /** @@ -71,6 +79,17 @@ export type CartesianChartBaseProps = BoxBaseProps & * Inset around the entire chart (outside the axes). */ inset?: number | Partial; + /** + * Whether to show a legend, or a custom legend element. + * When `true`, renders the default Legend component. + * When a ReactNode, renders the provided element. + */ + legend?: boolean | React.ReactNode; + /** + * Position of the legend relative to the chart. + * @default 'bottom' + */ + legendPosition?: LegendPosition; }; export type CartesianChartProps = Omit, 'title'> & @@ -91,6 +110,11 @@ export type CartesianChartProps = Omit, 'title'> & * Custom class name for the chart SVG element. */ chart?: string; + /** + * Custom class name for the legend element. + * @note not used when legend is a ReactNode. + */ + legend?: string; }; /** * Custom styles for the root element. @@ -108,6 +132,11 @@ export type CartesianChartProps = Omit, 'title'> & * Custom styles for the chart SVG element. */ chart?: React.CSSProperties; + /** + * Custom styles for the legend element. + * @note not used when legend is a ReactNode. + */ + legend?: React.CSSProperties; }; }; @@ -123,6 +152,8 @@ export const CartesianChart = memo( inset, enableScrubbing, onScrubberPositionChange, + legend, + legendPosition = 'bottom', width = '100%', height = '100%', className, @@ -134,21 +165,7 @@ export const CartesianChart = memo( ref, ) => { const { observe, width: chartWidth, height: chartHeight } = useDimensions(); - const svgRef = useRef(null); - const topSlotRef = useRef(null); - const bottomSlotRef = useRef(null); - const leftSlotRef = useRef(null); - const rightSlotRef = useRef(null); - - const slotRefs = useMemo( - () => ({ - topRef: topSlotRef, - bottomRef: bottomSlotRef, - leftRef: leftSlotRef, - rightRef: rightSlotRef, - }), - [], - ); + const chartRef = useRef(null); const calculatedInset = useMemo(() => getChartInset(inset, defaultChartInset), [inset]); @@ -392,8 +409,7 @@ export const CartesianChart = memo( registerAxis, unregisterAxis, getAxisBounds, - svgRef, - slotRefs, + ref: chartRef, }), [ series, @@ -411,14 +427,35 @@ export const CartesianChart = memo( registerAxis, unregisterAxis, getAxisBounds, - svgRef, - slotRefs, + chartRef, ], ); + const isVerticalLegend = useMemo( + () => legendPosition === 'top' || legendPosition === 'bottom', + [legendPosition], + ); + const isLegendBefore = useMemo( + () => legendPosition === 'top' || legendPosition === 'left', + [legendPosition], + ); + + const legendElement = useMemo(() => { + if (!legend) return; + if (typeof legend !== 'boolean') return legend; + return ( + + ); + }, [legend, isVerticalLegend, classNames?.legend, styles?.legend]); + const rootClassNames = useMemo( - () => cx(rootCss, className, classNames?.root), - [className, classNames], + () => + cx(rootCss, isVerticalLegend ? verticalCss : horizontalCss, className, classNames?.root), + [className, classNames, isVerticalLegend], ); const rootStyles = useMemo(() => ({ ...style, ...styles?.root }), [style, styles?.root]); @@ -427,7 +464,6 @@ export const CartesianChart = memo( - - - - { - const svgElement = node as unknown as SVGSVGElement; - svgRef.current = svgElement; - observe(node as unknown as HTMLElement); - - // Forward the ref to the user - if (ref) { - if (typeof ref === 'function') { - ref(svgElement); - } else { - (ref as React.MutableRefObject).current = svgElement; - } + {isLegendBefore && legendElement} + { + const svgElement = node as unknown as SVGSVGElement; + chartRef.current = svgElement; + observe(node as unknown as HTMLElement); + + // Forward the ref to the user + if (ref) { + if (typeof ref === 'function') { + ref(svgElement); + } else { + (ref as React.MutableRefObject).current = svgElement; } - }} - aria-live="polite" - as="svg" - className={cx(enableScrubbing && focusCss, classNames?.chart)} - height="100%" - style={styles?.chart} - tabIndex={enableScrubbing ? 0 : undefined} - width="100%" - > - {children} - - + } + }} + aria-live="polite" + as="svg" + className={cx(chartContainerCss, enableScrubbing && focusCss, classNames?.chart)} + height="100%" + style={styles?.chart} + tabIndex={enableScrubbing ? 0 : undefined} + width="100%" + > + {children} - + {!isLegendBefore && legendElement} diff --git a/packages/web-visualization/src/chart/ChartSlot.tsx b/packages/web-visualization/src/chart/ChartSlot.tsx deleted file mode 100644 index 66ef55e488..0000000000 --- a/packages/web-visualization/src/chart/ChartSlot.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { memo, useEffect, useState } from 'react'; -import type React from 'react'; -import { createPortal } from 'react-dom'; - -export type ChartSlotProps = { - children: React.ReactNode; - /** - * The slot ref to portal into. - */ - slotRef?: React.RefObject; -}; - -export const ChartSlot = memo(function ChartSlot({ children, slotRef }: ChartSlotProps) { - const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - }, []); - - // We only want to mount after the slotRef is available - if (!mounted || !slotRef?.current) { - return null; - } - - // Portal directly into the slot element - return createPortal(children, slotRef.current); -}); diff --git a/packages/web-visualization/src/chart/ChartTooltip.tsx b/packages/web-visualization/src/chart/ChartTooltip.tsx index 165e487d14..88d14bc634 100644 --- a/packages/web-visualization/src/chart/ChartTooltip.tsx +++ b/packages/web-visualization/src/chart/ChartTooltip.tsx @@ -47,7 +47,7 @@ export const ChartTooltip = ({ minWidth = 320, ...props }: ChartTooltipProps) => { - const { svgRef, series, getSeriesData, getXAxis } = useCartesianChartContext(); + const { ref, series, getSeriesData, getXAxis } = useCartesianChartContext(); const { scrubberPosition, enableScrubbing } = useScrubberContext(); const [legendMediaWidth, setLegendMediaWidth] = useState(0); const legendMediaRefs = useRef>(new Map()); @@ -72,7 +72,7 @@ export const ChartTooltip = ({ }); useEffect(() => { - const element = svgRef?.current; + const element = ref?.current; if (!element || !enableScrubbing) return; const handleMouseMove = (event: Event) => { @@ -97,7 +97,7 @@ export const ChartTooltip = ({ element.addEventListener('mousemove', handleMouseMove); return () => element.removeEventListener('mousemove', handleMouseMove); - }, [enableScrubbing, refs, svgRef]); + }, [enableScrubbing, refs, ref]); const { resolvedLabel, seriesItems } = useMemo(() => { if (scrubberPosition === undefined) { diff --git a/packages/web-visualization/src/chart/PolarChart.tsx b/packages/web-visualization/src/chart/PolarChart.tsx index 02ed2c19a3..abbf8bc444 100644 --- a/packages/web-visualization/src/chart/PolarChart.tsx +++ b/packages/web-visualization/src/chart/PolarChart.tsx @@ -1,10 +1,12 @@ -import { forwardRef, memo, useCallback, useMemo, useRef } from 'react'; +import React, { forwardRef, memo, useCallback, useMemo, useRef } from 'react'; import type { Rect } from '@coinbase/cds-common/types'; import { cx } from '@coinbase/cds-web'; import { useDimensions } from '@coinbase/cds-web/hooks/useDimensions'; import { Box, type BoxBaseProps, type BoxProps } from '@coinbase/cds-web/layout'; import { css } from '@linaria/core'; +import { Legend } from './legend/Legend'; +import type { LegendPosition } from './CartesianChart'; import { PolarChartProvider } from './ChartProvider'; import { type AngularAxisConfig, @@ -27,14 +29,18 @@ import { const rootCss = css` display: flex; - flex-direction: column; overflow: hidden; `; - -const middleRowCss = css` - display: flex; +const verticalCss = css` + flex-direction: column; +`; +const horizontalCss = css` + flex-direction: row; +`; +const chartContainerCss = css` flex: 1; min-height: 0; + min-width: 0; `; export type PolarChartBaseProps = BoxBaseProps & { @@ -110,6 +116,17 @@ export type PolarChartBaseProps = BoxBaseProps & { * Inset around the entire chart (outside the drawing area). */ inset?: number | Partial; + /** + * Whether to show a legend, or a custom legend element. + * When `true`, renders the default Legend component. + * When a ReactNode, renders the provided element. + */ + legend?: boolean | React.ReactNode; + /** + * Position of the legend relative to the chart. + * @default 'bottom' + */ + legendPosition?: LegendPosition; }; export type PolarChartProps = Omit, 'title'> & @@ -130,6 +147,11 @@ export type PolarChartProps = Omit, 'title'> & * Custom class name for the chart SVG element. */ chart?: string; + /** + * Custom class name for the legend element. + * @note not used when legend is a ReactNode. + */ + legend?: string; }; /** * Custom styles for the root element. @@ -147,6 +169,11 @@ export type PolarChartProps = Omit, 'title'> & * Custom styles for the chart SVG element. */ chart?: React.CSSProperties; + /** + * Custom styles for the legend element. + * @note not used when legend is a ReactNode. + */ + legend?: React.CSSProperties; }; }; @@ -164,6 +191,8 @@ export const PolarChart = memo( angularAxis, radialAxis, inset: insetInput, + legend, + legendPosition = 'bottom', width = '100%', height = '100%', className, @@ -175,20 +204,6 @@ export const PolarChart = memo( ref, ) => { const { observe, width: chartWidth, height: chartHeight } = useDimensions(); - const topSlotRef = useRef(null); - const bottomSlotRef = useRef(null); - const leftSlotRef = useRef(null); - const rightSlotRef = useRef(null); - - const slotRefs = useMemo( - () => ({ - topRef: topSlotRef, - bottomRef: bottomSlotRef, - leftRef: leftSlotRef, - rightRef: rightSlotRef, - }), - [], - ); const inset = useMemo(() => { return getChartInset(insetInput, defaultChartInset); @@ -359,7 +374,6 @@ export const PolarChart = memo( getAngularScale, getRadialScale, dataLength, - slotRefs, }), [ series, @@ -375,13 +389,34 @@ export const PolarChart = memo( getAngularScale, getRadialScale, dataLength, - slotRefs, ], ); + const isVerticalLegend = useMemo( + () => legendPosition === 'top' || legendPosition === 'bottom', + [legendPosition], + ); + const isLegendBefore = useMemo( + () => legendPosition === 'top' || legendPosition === 'left', + [legendPosition], + ); + + const legendElement = useMemo(() => { + if (!legend) return; + if (typeof legend !== 'boolean') return legend; + return ( + + ); + }, [legend, isVerticalLegend, classNames?.legend, styles?.legend]); + const rootClassNames = useMemo( - () => cx(rootCss, className, classNames?.root), - [className, classNames], + () => + cx(rootCss, isVerticalLegend ? verticalCss : horizontalCss, className, classNames?.root), + [className, classNames, isVerticalLegend], ); const rootStyles = useMemo(() => ({ ...style, ...styles?.root }), [style, styles?.root]); @@ -394,34 +429,30 @@ export const PolarChart = memo( width={width} {...props} > - - - - { - const svgElement = node as unknown as SVGSVGElement; - observe(node as unknown as HTMLElement); - // Forward the ref to the user - if (ref) { - if (typeof ref === 'function') { - ref(svgElement); - } else { - (ref as React.MutableRefObject).current = svgElement; - } + {isLegendBefore && legendElement} + { + const svgElement = node as unknown as SVGSVGElement; + observe(node as unknown as HTMLElement); + // Forward the ref to the user + if (ref) { + if (typeof ref === 'function') { + ref(svgElement); + } else { + (ref as React.MutableRefObject).current = svgElement; } - }} - aria-live="polite" - as="svg" - className={classNames?.chart} - height="100%" - style={styles?.chart} - width="100%" - > - {children} - - + } + }} + aria-live="polite" + as="svg" + className={cx(chartContainerCss, classNames?.chart)} + height="100%" + style={styles?.chart} + width="100%" + > + {children} - + {!isLegendBefore && legendElement} ); diff --git a/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx b/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx index f3977c5e87..808327ca5d 100644 --- a/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx +++ b/packages/web-visualization/src/chart/__stories__/ChartTooltip.stories.tsx @@ -157,6 +157,7 @@ const FilteredSeries = () => { { yAxis={{ domain: { min: 0 }, showGrid: true }} > - @@ -227,6 +227,7 @@ const StackedAreaTooltip = () => { { yAxis={{ domain: { min: 0 }, showGrid: true, tickLabelFormatter: numberFormatter }} > - diff --git a/packages/web-visualization/src/chart/index.ts b/packages/web-visualization/src/chart/index.ts index b3f1145f32..e4c321a585 100644 --- a/packages/web-visualization/src/chart/index.ts +++ b/packages/web-visualization/src/chart/index.ts @@ -4,7 +4,6 @@ export * from './axis/index'; export * from './bar/index'; export * from './CartesianChart'; export * from './ChartProvider'; -export * from './ChartSlot'; export * from './ChartTooltip'; export * from './DonutChart'; export * from './gradient/index'; diff --git a/packages/web-visualization/src/chart/legend/Legend.tsx b/packages/web-visualization/src/chart/legend/Legend.tsx index 6a0a79ef70..281ae5e1ce 100644 --- a/packages/web-visualization/src/chart/legend/Legend.tsx +++ b/packages/web-visualization/src/chart/legend/Legend.tsx @@ -1,18 +1,17 @@ import { forwardRef, memo, useMemo } from 'react'; -import { Box, type BoxProps } from '@coinbase/cds-web/layout'; +import { + Box, + type BoxBaseProps, + type BoxDefaultElement, + type BoxProps, +} from '@coinbase/cds-web/layout'; import { useChartContext } from '../ChartProvider'; -import { ChartSlot } from '../ChartSlot'; import { DefaultLegendItem, type LegendItemComponent } from './DefaultLegendItem'; import type { LegendShapeComponent } from './DefaultLegendShape'; -export type LegendBaseProps = { - /** - * The position of the legend relative to the chart. - * @default 'top' - */ - position?: 'top' | 'bottom' | 'left' | 'right'; +export type LegendBaseProps = BoxBaseProps & { /** * Array of series IDs to display in the legend. * By default, all series will be displayed. @@ -31,28 +30,25 @@ export type LegendBaseProps = { ShapeComponent?: LegendShapeComponent; }; -export type LegendProps = Omit, 'position'> & LegendBaseProps; +export type LegendProps = BoxProps & LegendBaseProps; export const Legend = memo( forwardRef( ( { - position = 'top', - flexDirection = position === 'top' || position === 'bottom' ? 'row' : 'column', + flexDirection = 'row', justifyContent = 'center', - alignItems = position === 'top' || position === 'bottom' ? 'center' : 'flex-start', + alignItems = flexDirection === 'row' ? 'center' : 'flex-start', flexWrap = 'wrap', gap = 1, seriesIds, ItemComponent = DefaultLegendItem, ShapeComponent, - width = position === 'top' || position === 'bottom' ? '100%' : undefined, - height = position === 'left' || position === 'right' ? '100%' : undefined, ...props }, ref, ) => { - const { series, slotRefs } = useChartContext(); + const { series } = useChartContext(); const filteredSeries = useMemo(() => { if (seriesIds === undefined) return series; @@ -61,40 +57,27 @@ export const Legend = memo( if (filteredSeries.length === 0) return; - const slotRef = - position === 'top' - ? slotRefs?.topRef - : position === 'bottom' - ? slotRefs?.bottomRef - : position === 'left' - ? slotRefs?.leftRef - : slotRefs?.rightRef; - return ( - - - {filteredSeries.map((s) => ( - - ))} - - + + {filteredSeries.map((s) => ( + + ))} + ); }, ), diff --git a/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx b/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx index f7bc952ef9..b38aafeba5 100644 --- a/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx +++ b/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx @@ -85,10 +85,12 @@ const Basic = () => { { }} > - ); @@ -200,10 +201,12 @@ const AutoScale = () => { { }} > - `${value} in`} /> @@ -228,6 +230,8 @@ const Position = () => { } + legendPosition="bottom" series={[ { id: 'revenue', @@ -281,7 +285,6 @@ const Position = () => { tickLabelFormatter={(value) => `${value}%`} /> - ); @@ -297,6 +300,8 @@ const ShapeVariants = () => { showXAxis showYAxis height={{ base: 200, tablet: 250, desktop: 300 }} + legend={} + legendPosition="left" series={[ { id: 'pill', @@ -329,9 +334,7 @@ const ShapeVariants = () => { ]} xAxis={{ data: months }} yAxis={{ domain: { min: 0 }, showGrid: true }} - > - - + /> ); }; @@ -416,6 +419,10 @@ const DynamicData = () => { showXAxis showYAxis height={{ base: 200, tablet: 250, desktop: 300 }} + legend={ + + } + legendPosition="top" series={series} xAxis={{ data: timeLabels, @@ -427,7 +434,6 @@ const DynamicData = () => { }} > - @@ -542,6 +548,8 @@ const Interactive = () => { showXAxis showYAxis height={{ base: 300, tablet: 350, desktop: 400 }} + legend={} + legendPosition="top" series={series} xAxis={{ data: months, @@ -551,9 +559,7 @@ const Interactive = () => { showGrid: true, tickLabelFormatter: (value) => `$${value}k`, }} - > - - + /> ); @@ -604,9 +610,12 @@ const PieChartLegend = () => { Portfolio Allocation - - - + } + legendPosition="right" + series={series} + /> ); @@ -718,8 +727,10 @@ const LegendShapes = () => { Annual Revenue { width={60} /> - @@ -798,9 +808,12 @@ const DonutChartLegend = () => { Task Status - - - + ); diff --git a/packages/web-visualization/src/chart/scrubber/ScrubberProvider.tsx b/packages/web-visualization/src/chart/scrubber/ScrubberProvider.tsx index cf72454622..8b35dde42c 100644 --- a/packages/web-visualization/src/chart/scrubber/ScrubberProvider.tsx +++ b/packages/web-visualization/src/chart/scrubber/ScrubberProvider.tsx @@ -7,10 +7,6 @@ export type ScrubberProviderProps = Partial< Pick > & { children: React.ReactNode; - /** - * A reference to the root SVG element, where interaction event handlers will be attached. - */ - svgRef: React.RefObject | null; }; /** @@ -19,7 +15,6 @@ export type ScrubberProviderProps = Partial< */ export const ScrubberProvider: React.FC = ({ children, - svgRef, enableScrubbing, onScrubberPositionChange, }) => { @@ -29,7 +24,7 @@ export const ScrubberProvider: React.FC = ({ throw new Error('ScrubberProvider must be used within a ChartContext'); } - const { getXScale, getXAxis, series } = chartContext; + const { getXScale, getXAxis, series, ref } = chartContext; const [scrubberPosition, setScrubberPosition] = useState(undefined); const getDataIndexFromX = useCallback( @@ -233,9 +228,9 @@ export const ScrubberProvider: React.FC = ({ // Attach event listeners to SVG element useEffect(() => { - if (!svgRef?.current || !enableScrubbing) return; + if (!ref?.current || !enableScrubbing) return; - const svg = svgRef.current; + const svg = ref.current; // Add event listeners svg.addEventListener('mousemove', handleMouseMove); @@ -258,7 +253,7 @@ export const ScrubberProvider: React.FC = ({ svg.removeEventListener('blur', handleBlur); }; }, [ - svgRef, + ref, enableScrubbing, handleMouseMove, handleMouseLeave, diff --git a/packages/web-visualization/src/chart/utils/context.ts b/packages/web-visualization/src/chart/utils/context.ts index 4014d6b7b1..05214d5599 100644 --- a/packages/web-visualization/src/chart/utils/context.ts +++ b/packages/web-visualization/src/chart/utils/context.ts @@ -35,18 +35,9 @@ export type ChartContextValue = { */ dataLength: number; /** - * Reference to the chart's SVG element. + * Reference to the chart's root element (SVG on web, Canvas on mobile). */ - svgRef?: React.RefObject; - /** - * References to the chart's slot containers for slot content. - */ - slotRefs?: { - topRef: React.RefObject; - bottomRef: React.RefObject; - leftRef: React.RefObject; - rightRef: React.RefObject; - }; + ref?: React.RefObject; }; /** From 6c858527cb37578a162fb33c2a86e2c6f56c03b7 Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Mon, 8 Dec 2025 14:24:27 -0500 Subject: [PATCH 05/13] Bring legend to mobile --- .../src/chart/CartesianChart.tsx | 93 ++++- .../src/chart/PolarChart.tsx | 92 ++++- .../mobile-visualization/src/chart/index.ts | 35 +- .../src/chart/legend/DefaultLegendItem.tsx | 105 ++++++ .../src/chart/legend/DefaultLegendShape.tsx | 82 +++++ .../src/chart/legend/Legend.tsx | 79 ++++ .../legend/__stories__/Legend.stories.tsx | 343 ++++++++++++++++++ .../src/chart/legend/index.ts | 3 + .../src/chart/utils/chart.ts | 10 + .../src/chart/utils/context.ts | 18 +- .../src/chart/legend/DefaultLegendItem.tsx | 2 +- 11 files changed, 804 insertions(+), 58 deletions(-) create mode 100644 packages/mobile-visualization/src/chart/legend/DefaultLegendItem.tsx create mode 100644 packages/mobile-visualization/src/chart/legend/DefaultLegendShape.tsx create mode 100644 packages/mobile-visualization/src/chart/legend/Legend.tsx create mode 100644 packages/mobile-visualization/src/chart/legend/__stories__/Legend.stories.tsx create mode 100644 packages/mobile-visualization/src/chart/legend/index.ts diff --git a/packages/mobile-visualization/src/chart/CartesianChart.tsx b/packages/mobile-visualization/src/chart/CartesianChart.tsx index 1d3e957d28..575b59e4f8 100644 --- a/packages/mobile-visualization/src/chart/CartesianChart.tsx +++ b/packages/mobile-visualization/src/chart/CartesianChart.tsx @@ -1,11 +1,12 @@ -import React, { forwardRef, memo, useCallback, useMemo } from 'react'; -import { type StyleProp, type View, type ViewStyle } from 'react-native'; +import React, { forwardRef, memo, useCallback, useMemo, useRef } from 'react'; +import { type LayoutChangeEvent, type StyleProp, type View, type ViewStyle } from 'react-native'; import type { Rect } from '@coinbase/cds-common/types'; import { useLayout } from '@coinbase/cds-mobile/hooks/useLayout'; import type { BoxBaseProps, BoxProps } from '@coinbase/cds-mobile/layout'; import { Box } from '@coinbase/cds-mobile/layout'; import { Canvas, Skia, type SkTypefaceFontProvider } from '@shopify/react-native-skia'; +import { Legend } from './legend/Legend'; import { ScrubberProvider, type ScrubberProviderProps } from './scrubber/ScrubberProvider'; import { convertToSerializableScale, type SerializableScale } from './utils/scale'; import { useChartContextBridge } from './ChartContextBridge'; @@ -28,17 +29,23 @@ import { useTotalAxisPadding, } from './utils'; -const ChartCanvas = memo( - ({ children, style }: { children: React.ReactNode; style?: StyleProp }) => { - const ContextBridge = useChartContextBridge(); +type ChartCanvasProps = { + children: React.ReactNode; + style?: StyleProp; + onLayout?: (event: LayoutChangeEvent) => void; +}; - return ( - - {children} - - ); - }, -); +const ChartCanvas = memo(({ children, style, onLayout }: ChartCanvasProps) => { + const ContextBridge = useChartContextBridge(); + + return ( + + {children} + + ); +}); + +export type LegendPosition = 'top' | 'bottom' | 'left' | 'right'; export type CartesianChartBaseProps = Omit & Pick & { @@ -64,6 +71,18 @@ export type CartesianChartBaseProps = Omit & * Inset around the entire chart (outside the axes). */ inset?: number | Partial; + /** + * Whether to show a legend, or a custom legend element. + * When `true`, renders the default Legend component. + * When a ReactNode, renders the provided element. + * @default false + */ + legend?: boolean | React.ReactNode; + /** + * Position of the legend relative to the chart. + * @default 'bottom' + */ + legendPosition?: LegendPosition; }; export type CartesianChartProps = CartesianChartBaseProps & @@ -97,6 +116,11 @@ export type CartesianChartProps = CartesianChartBaseProps & * Custom styles for the chart canvas element. */ chart?: StyleProp; + /** + * Custom styles for the legend element. + * @note not used when legend is a ReactNode. + */ + legend?: StyleProp; }; }; @@ -112,6 +136,8 @@ export const CartesianChart = memo( yAxis: yAxisConfigProp, inset, onScrubberPositionChange, + legend, + legendPosition = 'bottom', width = '100%', height = '100%', style, @@ -386,6 +412,8 @@ export const CartesianChart = memo( return Skia.TypefaceFontProvider.Make(); }, [fontProviderProp]); + const chartRef = useRef(null); + const contextValue: CartesianChartContextValue = useMemo( () => ({ series: series ?? [], @@ -407,6 +435,7 @@ export const CartesianChart = memo( registerAxis, unregisterAxis, getAxisBounds, + ref: chartRef, }), [ series, @@ -428,12 +457,28 @@ export const CartesianChart = memo( registerAxis, unregisterAxis, getAxisBounds, + chartRef, ], ); - const rootStyles = useMemo(() => { - return [style, styles?.root]; - }, [style, styles?.root]); + const isVerticalLegend = legendPosition === 'top' || legendPosition === 'bottom'; + const isLegendBefore = legendPosition === 'top' || legendPosition === 'left'; + + const legendElement = useMemo(() => { + if (!legend) return; + if (typeof legend !== 'boolean') return legend; + return ( + + ); + }, [legend, isVerticalLegend, styles?.legend]); + + const rootStyles = useMemo(() => { + return [ + { flexDirection: isVerticalLegend ? 'column' : 'row' } as ViewStyle, + style as ViewStyle, + styles?.root as ViewStyle, + ].filter(Boolean); + }, [isVerticalLegend, style, styles?.root]); return ( @@ -443,17 +488,29 @@ export const CartesianChart = memo( onScrubberPositionChange={onScrubberPositionChange} > { + chartRef.current = node; + if (ref) { + if (typeof ref === 'function') { + ref(node); + } else { + ref.current = node; + } + } + }} accessibilityLiveRegion="polite" accessibilityRole="image" collapsable={collapsable} height={height} - onLayout={onContainerLayout} style={rootStyles} width={width} {...props} > - {children} + {isLegendBefore && legendElement} + + {children} + + {!isLegendBefore && legendElement} diff --git a/packages/mobile-visualization/src/chart/PolarChart.tsx b/packages/mobile-visualization/src/chart/PolarChart.tsx index b87aea4a17..4afd50d6da 100644 --- a/packages/mobile-visualization/src/chart/PolarChart.tsx +++ b/packages/mobile-visualization/src/chart/PolarChart.tsx @@ -1,12 +1,14 @@ -import React, { forwardRef, memo, useCallback, useMemo } from 'react'; -import { type StyleProp, type View, type ViewStyle } from 'react-native'; +import React, { forwardRef, memo, useCallback, useMemo, useRef } from 'react'; +import { type LayoutChangeEvent, type StyleProp, type View, type ViewStyle } from 'react-native'; import type { Rect } from '@coinbase/cds-common/types'; import { useLayout } from '@coinbase/cds-mobile/hooks/useLayout'; import type { BoxBaseProps, BoxProps } from '@coinbase/cds-mobile/layout'; import { Box } from '@coinbase/cds-mobile/layout'; import { Canvas, Skia, type SkTypefaceFontProvider } from '@shopify/react-native-skia'; +import { Legend } from './legend/Legend'; import { convertToSerializableScale, type SerializableScale } from './utils/scale'; +import type { LegendPosition } from './CartesianChart'; import { useChartContextBridge } from './ChartContextBridge'; import { PolarChartProvider } from './ChartProvider'; import { @@ -28,17 +30,21 @@ import { type RadialAxisConfigProps, } from './utils'; -const ChartCanvas = memo( - ({ children, style }: { children: React.ReactNode; style?: StyleProp }) => { - const ContextBridge = useChartContextBridge(); +type ChartCanvasProps = { + children: React.ReactNode; + style?: StyleProp; + onLayout?: (event: LayoutChangeEvent) => void; +}; - return ( - - {children} - - ); - }, -); +const ChartCanvas = memo(({ children, style, onLayout }: ChartCanvasProps) => { + const ContextBridge = useChartContextBridge(); + + return ( + + {children} + + ); +}); export type PolarChartBaseProps = Omit & { /** @@ -113,6 +119,18 @@ export type PolarChartBaseProps = Omit & { * Inset around the entire chart (outside the drawing area). */ inset?: number | Partial; + /** + * Whether to show a legend, or a custom legend element. + * When `true`, renders the default Legend component. + * When a ReactNode, renders the provided element. + * @default false + */ + legend?: boolean | React.ReactNode; + /** + * Position of the legend relative to the chart. + * @default 'bottom' + */ + legendPosition?: LegendPosition; }; export type PolarChartProps = PolarChartBaseProps & @@ -145,6 +163,11 @@ export type PolarChartProps = PolarChartBaseProps & * Custom styles for the chart canvas element. */ chart?: StyleProp; + /** + * Custom styles for the legend element. + * @note not used when legend is a ReactNode. + */ + legend?: StyleProp; }; }; @@ -162,6 +185,8 @@ export const PolarChart = memo( angularAxis, radialAxis, inset: insetInput, + legend, + legendPosition = 'bottom', width = '100%', height = '100%', style, @@ -370,6 +395,8 @@ export const PolarChart = memo( return Skia.TypefaceFontProvider.Make(); }, [fontProviderProp]); + const chartRef = useRef(null); + const contextValue: PolarChartContextValue = useMemo( () => ({ series: series ?? [], @@ -389,6 +416,7 @@ export const PolarChart = memo( getAngularSerializableScale, getRadialSerializableScale, dataLength, + ref: chartRef, }), [ series, @@ -408,27 +436,55 @@ export const PolarChart = memo( getAngularSerializableScale, getRadialSerializableScale, dataLength, + chartRef, ], ); - const rootStyles = useMemo(() => { - return [style, styles?.root]; - }, [style, styles?.root]); + const isVerticalLegend = legendPosition === 'top' || legendPosition === 'bottom'; + const isLegendBefore = legendPosition === 'top' || legendPosition === 'left'; + + const legendElement = useMemo(() => { + if (!legend) return; + if (typeof legend !== 'boolean') return legend; + return ( + + ); + }, [legend, isVerticalLegend, styles?.legend]); + + const rootStyles = useMemo(() => { + return [ + { flexDirection: isVerticalLegend ? 'column' : 'row' } as ViewStyle, + style as ViewStyle, + styles?.root as ViewStyle, + ].filter(Boolean); + }, [isVerticalLegend, style, styles?.root]); return ( { + chartRef.current = node; + if (ref) { + if (typeof ref === 'function') { + ref(node); + } else { + ref.current = node; + } + } + }} accessibilityLiveRegion="polite" accessibilityRole="image" collapsable={collapsable} height={height} - onLayout={onContainerLayout} style={rootStyles} width={width} {...props} > - {children} + {isLegendBefore && legendElement} + + {children} + + {!isLegendBefore && legendElement} ); diff --git a/packages/mobile-visualization/src/chart/index.ts b/packages/mobile-visualization/src/chart/index.ts index daa21d9f75..a7e276c42c 100644 --- a/packages/mobile-visualization/src/chart/index.ts +++ b/packages/mobile-visualization/src/chart/index.ts @@ -1,19 +1,20 @@ // codegen:start {preset: barrel, include: [./*.tsx, ./*/index.ts]} -export * from './area/index' -export * from './axis/index' -export * from './bar/index' -export * from './CartesianChart' -export * from './ChartContextBridge' -export * from './ChartProvider' -export * from './DonutChart' -export * from './gradient/index' -export * from './line/index' -export * from './Path' -export * from './PeriodSelector' -export * from './pie/index' -export * from './point/index' -export * from './PolarChart' -export * from './scrubber/index' -export * from './text/index' -export * from './utils/index' +export * from './area/index'; +export * from './axis/index'; +export * from './bar/index'; +export * from './CartesianChart'; +export * from './ChartContextBridge'; +export * from './ChartProvider'; +export * from './DonutChart'; +export * from './gradient/index'; +export * from './legend/index'; +export * from './line/index'; +export * from './Path'; +export * from './PeriodSelector'; +export * from './pie/index'; +export * from './point/index'; +export * from './PolarChart'; +export * from './scrubber/index'; +export * from './text/index'; +export * from './utils/index'; // codegen:end diff --git a/packages/mobile-visualization/src/chart/legend/DefaultLegendItem.tsx b/packages/mobile-visualization/src/chart/legend/DefaultLegendItem.tsx new file mode 100644 index 0000000000..728a35182c --- /dev/null +++ b/packages/mobile-visualization/src/chart/legend/DefaultLegendItem.tsx @@ -0,0 +1,105 @@ +import { memo } from 'react'; +import { type StyleProp, StyleSheet, type ViewStyle } from 'react-native'; +import type { SharedProps } from '@coinbase/cds-common/types'; +import { Box, HStack, type HStackProps } from '@coinbase/cds-mobile/layout'; +import { TextLabel2 } from '@coinbase/cds-mobile/typography'; + +import type { Series } from '../utils'; + +import { DefaultLegendShape, type LegendShapeComponent } from './DefaultLegendShape'; + +const styles = StyleSheet.create({ + shapeWrapper: { + width: 10, + height: 24, + alignItems: 'center', + justifyContent: 'center', + }, + legendItem: { + alignItems: 'center', + }, +}); + +export type LegendItemBaseProps = Omit & + SharedProps & { + /** + * Id of the series. + */ + seriesId: string; + /** + * Display label for the legend item. + * Can be a string or a custom ReactNode. + * If a ReactNode is provided, it replaces the default Text component. + */ + label: React.ReactNode; + /** + * Color associated with the series. + * This is a raw string color value (e.g. 'rgb(...)' or hex). + */ + color?: string; + /** + * Shape to display in the legend. + */ + shape?: Series['legendShape']; + /** + * Custom component to render the legend shape. + * @default DefaultLegendShape + */ + ShapeComponent?: LegendShapeComponent; + }; + +export type LegendItemProps = LegendItemBaseProps & { + /** + * Custom styles for the component parts. + */ + styles?: { + /** + * Custom styles for the root element. + */ + root?: StyleProp; + /** + * Custom styles for the shape wrapper element. + */ + shapeWrapper?: StyleProp; + /** + * Custom styles for the shape element. + */ + shape?: StyleProp; + /** + * Custom styles for the text element. + */ + text?: StyleProp; + }; +}; + +export type LegendItemComponent = React.FC; + +export const DefaultLegendItem = memo(function DefaultLegendItem({ + label, + color, + shape, + ShapeComponent = DefaultLegendShape, + gap = 1, + style, + styles: stylesProp, + testID, + ...props +}) { + return ( + + + + + {typeof label === 'string' ? ( + {label} + ) : ( + label + )} + + ); +}); diff --git a/packages/mobile-visualization/src/chart/legend/DefaultLegendShape.tsx b/packages/mobile-visualization/src/chart/legend/DefaultLegendShape.tsx new file mode 100644 index 0000000000..c2a7f03c99 --- /dev/null +++ b/packages/mobile-visualization/src/chart/legend/DefaultLegendShape.tsx @@ -0,0 +1,82 @@ +import { memo } from 'react'; +import { StyleSheet, type ViewStyle } from 'react-native'; +import type { SharedProps } from '@coinbase/cds-common/types'; +import { useTheme } from '@coinbase/cds-mobile'; +import { Box, type BoxProps } from '@coinbase/cds-mobile/layout'; + +import type { LegendShape, LegendShapeVariant } from '../utils/chart'; + +const styles = StyleSheet.create({ + pill: { + width: 6, + height: 24, + borderRadius: 3, + }, + circle: { + width: 10, + height: 10, + borderRadius: 5, + }, + square: { + width: 10, + height: 10, + }, + squircle: { + width: 10, + height: 10, + borderRadius: 2, + }, +}); + +const stylesByVariant: Record = { + pill: styles.pill, + circle: styles.circle, + square: styles.square, + squircle: styles.squircle, +}; + +const isVariantShape = (shape: LegendShape): shape is LegendShapeVariant => + typeof shape === 'string' && shape in stylesByVariant; + +export type LegendShapeBaseProps = SharedProps & { + /** + * Color of the legend shape. + * @default theme.color.fgPrimary + */ + color?: string; + /** + * Shape to display. Can be a preset shape or a custom ReactNode. + * @default 'circle' + */ + shape?: LegendShape; +}; + +export type LegendShapeProps = Omit & LegendShapeBaseProps; + +export type LegendShapeComponent = React.FC; + +/** + * Default shape component for chart legends. + * Renders a colored shape (pill, circle, square, or squircle) or a custom ReactNode. + */ +export const DefaultLegendShape = memo(function DefaultLegendShape({ + color, + shape = 'circle', + style, + testID, + ...props +}) { + const theme = useTheme(); + + if (!isVariantShape(shape)) return shape; + + const variantStyle = stylesByVariant[shape]; + + return ( + + ); +}); diff --git a/packages/mobile-visualization/src/chart/legend/Legend.tsx b/packages/mobile-visualization/src/chart/legend/Legend.tsx new file mode 100644 index 0000000000..45959b6b4e --- /dev/null +++ b/packages/mobile-visualization/src/chart/legend/Legend.tsx @@ -0,0 +1,79 @@ +import { forwardRef, memo, useMemo } from 'react'; +import type { View } from 'react-native'; +import type { SharedProps } from '@coinbase/cds-common/types'; +import { Box, type BoxProps } from '@coinbase/cds-mobile/layout'; + +import { useChartContext } from '../ChartProvider'; + +import { DefaultLegendItem, type LegendItemComponent } from './DefaultLegendItem'; +import type { LegendShapeComponent } from './DefaultLegendShape'; + +export type LegendBaseProps = SharedProps & { + /** + * Array of series IDs to display in the legend. + * By default, all series will be displayed. + */ + seriesIds?: string[]; + /** + * Custom component to render each legend item. + * @default DefaultLegendItem + */ + ItemComponent?: LegendItemComponent; + /** + * Custom component to render the legend shape within each item. + * Only used when ItemComponent is not provided or is DefaultLegendItem. + * @default DefaultLegendShape + */ + ShapeComponent?: LegendShapeComponent; +}; + +export type LegendProps = BoxProps & LegendBaseProps; + +export const Legend = memo( + forwardRef(function Legend( + { + flexDirection = 'row', + justifyContent = 'center', + alignItems = flexDirection === 'row' ? 'center' : 'flex-start', + flexWrap = 'wrap', + gap = 1, + seriesIds, + ItemComponent = DefaultLegendItem, + ShapeComponent, + ...props + }, + ref, + ) { + const { series } = useChartContext(); + + const filteredSeries = useMemo(() => { + if (seriesIds === undefined) return series; + return series.filter((s) => seriesIds.includes(s.id)); + }, [series, seriesIds]); + + if (filteredSeries.length === 0) return null; + + return ( + + {filteredSeries.map((s) => ( + + ))} + + ); + }), +); diff --git a/packages/mobile-visualization/src/chart/legend/__stories__/Legend.stories.tsx b/packages/mobile-visualization/src/chart/legend/__stories__/Legend.stories.tsx new file mode 100644 index 0000000000..5648eb4bb3 --- /dev/null +++ b/packages/mobile-visualization/src/chart/legend/__stories__/Legend.stories.tsx @@ -0,0 +1,343 @@ +import { memo, useCallback, useMemo, useState } from 'react'; +import { useTheme } from '@coinbase/cds-mobile'; +import { IconButton } from '@coinbase/cds-mobile/buttons'; +import { ExampleScreen } from '@coinbase/cds-mobile/examples/ExampleScreen'; +import { Box, HStack, VStack } from '@coinbase/cds-mobile/layout'; +import { TextLabel1, TextLabel2 } from '@coinbase/cds-mobile/typography'; +import { Text } from '@coinbase/cds-mobile/typography/Text'; + +import { DonutChart } from '../../DonutChart'; +import { LineChart } from '../../line'; +import { PieChart } from '../../pie'; +import type { LegendShapeVariant } from '../../utils/chart'; +import type { LegendItemProps } from '../DefaultLegendItem'; +import { DefaultLegendShape } from '../DefaultLegendShape'; +import { Legend } from '../Legend'; + +const spectrumColors = [ + 'blue40', + 'green40', + 'orange40', + 'yellow40', + 'gray40', + 'indigo40', + 'pink40', + 'purple40', + 'red40', + 'teal40', +]; + +const shapes: LegendShapeVariant[] = ['pill', 'circle', 'squircle', 'square']; + +const Shapes = () => { + const theme = useTheme(); + + return ( + + {shapes.map((shape) => ( + + {spectrumColors.map((color) => ( + + + + ))} + + ))} + + ); +}; + +const BasicLegend = () => { + const theme = useTheme(); + const pages = useMemo( + () => ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'], + [], + ); + const pageViews = useMemo(() => [2400, 1398, 9800, 3908, 4800, 3800, 4300], []); + const uniqueVisitors = useMemo(() => [4000, 3000, 2000, 2780, 1890, 2390, 3490], []); + + return ( + + + + ); +}; + +const ShapeVariants = () => { + const theme = useTheme(); + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + + return ( + + } + legendPosition="bottom" + series={[ + { + id: 'pill', + label: 'Pill', + data: [120, 150, 130, 170, 160, 190], + color: `rgb(${theme.spectrum.blue40})`, + legendShape: 'pill', + }, + { + id: 'circle', + label: 'Circle', + data: [80, 110, 95, 125, 115, 140], + color: `rgb(${theme.spectrum.green40})`, + legendShape: 'circle', + }, + { + id: 'square', + label: 'Square', + data: [60, 85, 70, 100, 90, 115], + color: `rgb(${theme.spectrum.orange40})`, + legendShape: 'square', + }, + { + id: 'squircle', + label: 'Squircle', + data: [40, 60, 50, 75, 65, 85], + color: `rgb(${theme.spectrum.purple40})`, + legendShape: 'squircle', + }, + ]} + width="100%" + xAxis={{ data: months }} + /> + + ); +}; + +const PieChartLegend = () => { + const theme = useTheme(); + + const series = [ + { + id: 'stocks', + data: 45, + label: 'Stocks', + color: `rgb(${theme.spectrum.blue40})`, + legendShape: 'circle' as const, + }, + { + id: 'bonds', + data: 25, + label: 'Bonds', + color: `rgb(${theme.spectrum.green40})`, + legendShape: 'circle' as const, + }, + { + id: 'realEstate', + data: 15, + label: 'Real Estate', + color: `rgb(${theme.spectrum.orange40})`, + legendShape: 'circle' as const, + }, + { + id: 'commodities', + data: 10, + label: 'Commodities', + color: `rgb(${theme.spectrum.purple40})`, + legendShape: 'circle' as const, + }, + { + id: 'cash', + data: 5, + label: 'Cash', + color: `rgb(${theme.spectrum.gray40})`, + legendShape: 'circle' as const, + }, + ]; + + return ( + + Portfolio Allocation + } + legendPosition="right" + series={series} + width={300} + /> + + ); +}; + +const DonutChartLegend = () => { + const theme = useTheme(); + + const series = [ + { + id: 'completed', + data: 68, + label: 'Completed', + color: theme.color.fgPositive, + legendShape: 'squircle' as const, + }, + { + id: 'inProgress', + data: 22, + label: 'In Progress', + color: `rgb(${theme.spectrum.blue40})`, + legendShape: 'squircle' as const, + }, + { + id: 'pending', + data: 10, + label: 'Pending', + color: `rgb(${theme.spectrum.gray40})`, + legendShape: 'squircle' as const, + }, + ]; + + return ( + + Task Status + + + ); +}; + +const CustomLegendItem = () => { + const theme = useTheme(); + + const CustomItem = memo(function CustomItem({ label, color, shape }) { + return ( + + + {label} + 100% + + ); + }); + + return ( + + Custom Legend Item + } + legendPosition="bottom" + series={[ + { + id: 'btc', + data: 60, + label: 'Bitcoin', + color: `rgb(${theme.spectrum.orange40})`, + }, + { + id: 'eth', + data: 30, + label: 'Ethereum', + color: `rgb(${theme.spectrum.purple40})`, + }, + { + id: 'other', + data: 10, + label: 'Other', + color: `rgb(${theme.spectrum.gray40})`, + }, + ]} + width={150} + /> + + ); +}; + +type ExampleItem = { + title: string; + component: React.ReactNode; +}; + +function ExampleNavigator() { + const [currentIndex, setCurrentIndex] = useState(0); + + const examples = useMemo( + () => [ + { title: 'Shapes', component: }, + { title: 'Basic Legend', component: }, + { title: 'Shape Variants', component: }, + { title: 'Pie Chart Legend', component: }, + { title: 'Donut Chart Legend', component: }, + { title: 'Custom Legend Item', component: }, + ], + [], + ); + + const currentExample = examples[currentIndex]; + const isFirstExample = currentIndex === 0; + const isLastExample = currentIndex === examples.length - 1; + + const handlePrevious = useCallback(() => { + setCurrentIndex((prev) => Math.max(0, prev - 1)); + }, []); + + const handleNext = useCallback(() => { + setCurrentIndex((prev) => Math.min(examples.length - 1, prev + 1)); + }, [examples.length]); + + return ( + + + + + + {currentExample.title} + + {currentIndex + 1} / {examples.length} + + + + + + {currentExample.component} + + + + ); +} + +export default ExampleNavigator; diff --git a/packages/mobile-visualization/src/chart/legend/index.ts b/packages/mobile-visualization/src/chart/legend/index.ts new file mode 100644 index 0000000000..ba2be3549c --- /dev/null +++ b/packages/mobile-visualization/src/chart/legend/index.ts @@ -0,0 +1,3 @@ +export * from './DefaultLegendItem'; +export * from './DefaultLegendShape'; +export * from './Legend'; diff --git a/packages/mobile-visualization/src/chart/utils/chart.ts b/packages/mobile-visualization/src/chart/utils/chart.ts index 62a18ca73e..fb04ece580 100644 --- a/packages/mobile-visualization/src/chart/utils/chart.ts +++ b/packages/mobile-visualization/src/chart/utils/chart.ts @@ -19,6 +19,10 @@ export type AxisBounds = { export const isValidBounds = (bounds: Partial): bounds is AxisBounds => bounds.min !== undefined && bounds.max !== undefined; +export type LegendShapeVariant = 'circle' | 'square' | 'squircle' | 'pill'; + +export type LegendShape = LegendShapeVariant | React.ReactNode; + export type Series = { /** * Id of the series. @@ -32,6 +36,12 @@ export type Series = { * Color for the series. */ color?: string; + /** + * Legend shape for this series. + * Can be a LegendShapeVariant or a custom ReactNode. + * @default 'circle' + */ + legendShape?: LegendShape; }; export type CartesianSeries = Series & { diff --git a/packages/mobile-visualization/src/chart/utils/context.ts b/packages/mobile-visualization/src/chart/utils/context.ts index 0c49f3fdc4..b27551e37b 100644 --- a/packages/mobile-visualization/src/chart/utils/context.ts +++ b/packages/mobile-visualization/src/chart/utils/context.ts @@ -1,4 +1,5 @@ -import { createContext, useContext } from 'react'; +import { createContext, type RefObject, useContext } from 'react'; +import type { View } from 'react-native'; import type { SharedValue } from 'react-native-reanimated'; import type { Rect } from '@coinbase/cds-common/types'; import type { SkTypefaceFontProvider } from '@shopify/react-native-skia'; @@ -8,9 +9,14 @@ import type { CartesianSeries, PolarSeries, Series } from './chart'; import type { ChartScaleFunction, SerializableScale } from './scale'; /** - * Context value for charts. + * Base context value for all chart types. */ export type ChartContextValue = { + /** + * The series data for the chart. + * Contains common series properties (id, label, color, legendShape). + */ + series: Series[]; /** * Whether to animate the chart. */ @@ -40,13 +46,17 @@ export type ChartContextValue = { * Length of the data domain. */ dataLength: number; + /** + * Reference to the chart's root element (SVG on web, Canvas on mobile). + */ + ref?: RefObject; }; /** * Context value for Cartesian (X/Y) coordinate charts. * Contains axis-specific methods and properties for rectangular coordinate systems. */ -export type CartesianChartContextValue = ChartContextValue & { +export type CartesianChartContextValue = Omit & { /** * The series data for the chart. */ @@ -112,7 +122,7 @@ export type CartesianChartContextValue = ChartContextValue & { * Context value for Polar (Angular/Radial) coordinate charts. * Contains axis-specific methods and properties for polar coordinate systems. */ -export type PolarChartContextValue = ChartContextValue & { +export type PolarChartContextValue = Omit & { /** * The series data for the chart. */ diff --git a/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx b/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx index b568f4b3e6..e2535a4cb6 100644 --- a/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx +++ b/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx @@ -32,7 +32,7 @@ export type LegendItemBaseProps = Omit & /** * Id of the series. */ - seriesId: Series['id']; + seriesId: string; /** * Display label for the legend item. * Can be a string or a custom ReactNode. From 5d2fc99a772cafdbeeeb9bac43732a759c989b41 Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Mon, 8 Dec 2025 14:24:33 -0500 Subject: [PATCH 06/13] Create docs --- apps/docs/docgen.config.js | 2 + .../graphs/ChartTooltip/_webExamples.mdx | 404 ++++++++++++ .../graphs/ChartTooltip/_webPropsTable.mdx | 11 + .../components/graphs/ChartTooltip/index.mdx | 27 + .../graphs/ChartTooltip/webMetadata.json | 29 + .../graphs/Legend/_mobileExamples.mdx | 296 +++++++++ .../graphs/Legend/_mobilePropsTable.mdx | 11 + .../components/graphs/Legend/_webExamples.mdx | 604 ++++++++++++++++++ .../graphs/Legend/_webPropsTable.mdx | 11 + .../docs/components/graphs/Legend/index.mdx | 35 + .../graphs/Legend/mobileMetadata.json | 37 ++ .../components/graphs/Legend/webMetadata.json | 33 + apps/docs/sidebars.ts | 10 + apps/mobile-app/scripts/utils/routes.mjs | 6 + apps/mobile-app/src/routes.ts | 6 + packages/ui-mobile-playground/src/routes.ts | 6 + packages/ui-mobile-visreg/src/routes.ts | 6 + 17 files changed, 1534 insertions(+) create mode 100644 apps/docs/docs/components/graphs/ChartTooltip/_webExamples.mdx create mode 100644 apps/docs/docs/components/graphs/ChartTooltip/_webPropsTable.mdx create mode 100644 apps/docs/docs/components/graphs/ChartTooltip/index.mdx create mode 100644 apps/docs/docs/components/graphs/ChartTooltip/webMetadata.json create mode 100644 apps/docs/docs/components/graphs/Legend/_mobileExamples.mdx create mode 100644 apps/docs/docs/components/graphs/Legend/_mobilePropsTable.mdx create mode 100644 apps/docs/docs/components/graphs/Legend/_webExamples.mdx create mode 100644 apps/docs/docs/components/graphs/Legend/_webPropsTable.mdx create mode 100644 apps/docs/docs/components/graphs/Legend/index.mdx create mode 100644 apps/docs/docs/components/graphs/Legend/mobileMetadata.json create mode 100644 apps/docs/docs/components/graphs/Legend/webMetadata.json diff --git a/apps/docs/docgen.config.js b/apps/docs/docgen.config.js index 6139a942fb..e0ae84a676 100644 --- a/apps/docs/docgen.config.js +++ b/apps/docs/docgen.config.js @@ -74,7 +74,9 @@ module.exports = { 'chart/area/AreaChart', 'chart/bar/BarChart', 'chart/CartesianChart', + 'chart/ChartTooltip', 'chart/DonutChart', + 'chart/legend/Legend', 'chart/line/LineChart', 'chart/pie/PieChart', 'chart/PolarChart', diff --git a/apps/docs/docs/components/graphs/ChartTooltip/_webExamples.mdx b/apps/docs/docs/components/graphs/ChartTooltip/_webExamples.mdx new file mode 100644 index 0000000000..d07431ca18 --- /dev/null +++ b/apps/docs/docs/components/graphs/ChartTooltip/_webExamples.mdx @@ -0,0 +1,404 @@ +ChartTooltip displays series data at the current scrubber position in a floating tooltip. It automatically shows all series values, supports custom formatting, and positions itself near the cursor. + +## Basic Usage + +Add ChartTooltip as a child of any chart with `enableScrubbing` to display values when hovering. The tooltip label defaults to the x-axis value. + +```jsx live +function BasicTooltip() { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const revenue = [120, 150, 180, 165, 190, 210]; + const expenses = [80, 95, 110, 105, 120, 130]; + + return ( + + + + + ); +} +``` + +## Value Formatter + +Use the `valueFormatter` prop to customize how values are displayed. The formatter receives the numeric value and should return a string or ReactNode. + +```jsx live +function ValueFormatterTooltip() { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const sales = [45000, 52000, 48000, 61000, 55000, 67000]; + const profit = [12000, 15000, 11000, 18000, 14000, 21000]; + + const currencyFormatter = useCallback( + (value) => + new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + maximumFractionDigits: 0, + }).format(value), + [], + ); + + return ( + + + + + ); +} +``` + +## Custom Label + +Use the `label` prop to customize the tooltip header. It can be a static string, ReactNode, or a function that receives the data index. + +```jsx live +function CustomLabelTooltip() { + const dates = ['2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06']; + const displayDates = [ + 'January 2024', + 'February 2024', + 'March 2024', + 'April 2024', + 'May 2024', + 'June 2024', + ]; + const temperature = [32, 35, 45, 58, 68, 78]; + const humidity = [65, 60, 55, 50, 55, 60]; + + return ( + + + ( + + {displayDates[dataIndex]} + + )} + /> + + ); +} +``` + +## Filtered Series + +Use the `seriesIds` prop to show only specific series in the tooltip. This is useful when you want to hide certain series from the tooltip while still displaying them on the chart. + +```jsx live +function FilteredSeriesTooltip() { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const primary = [100, 120, 140, 130, 150, 170]; + const secondary = [60, 70, 80, 75, 85, 95]; + const tertiary = [30, 35, 40, 38, 42, 48]; + + return ( + + + + + ); +} +``` + +## With Bar Chart + +ChartTooltip works with bar charts and other chart types. + +```jsx live +function BarChartTooltip() { + const categories = ['Q1', 'Q2', 'Q3', 'Q4']; + const revenue = [250, 320, 280, 410]; + const costs = [180, 220, 200, 280]; + + const numberFormatter = useCallback((value) => `$${value}k`, []); + + return ( + + + + + + + ); +} +``` + +## Stacked Area Tooltip + +When used with stacked charts, ChartTooltip shows individual series values rather than cumulative values. + +```jsx live +function StackedAreaTooltip() { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug']; + const desktop = [4000, 4200, 3800, 4500, 4800, 5200, 5000, 5500]; + const mobile = [2400, 2800, 3000, 3200, 3500, 3800, 4000, 4200]; + const tablet = [1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900]; + + const numberFormatter = useCallback((value) => value.toLocaleString(), []); + + return ( + + + + + ); +} +``` + +## Custom Value Display + +The `valueFormatter` can return a ReactNode for rich value display, including icons, colors, and additional information. + +```jsx live +function CustomValueDisplayTooltip() { + const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + const steps = [8500, 12000, 7200, 9800, 11500, 15000, 6500]; + const goal = 10000; + + const stepsFormatter = useCallback((value) => { + const percentage = Math.round((value / goal) * 100); + const isGoalMet = value >= goal; + return ( + + + {value.toLocaleString()} steps + + + ({percentage}%) + + + ); + }, []); + + return ( + + + + + ); +} +``` + +## Multi-Axis Tooltip + +ChartTooltip works with charts that have multiple y-axes, showing values from all series. + +```jsx live +function MultiAxisTooltip() { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const revenue = [455, 520, 380, 455, 285, 235]; + const profitMargin = [23, 20, 16, 38, 12, 9]; + + return ( + + + `$${value}k`} + /> + `${value}%`} + /> + + { + return value < 100 ? `${value}%` : `$${value}k`; + }} + /> + + ); +} +``` + +## With Legend + +ChartTooltip can be combined with Legend to provide both persistent series identification and on-hover details. + +```jsx live +function TooltipWithLegend() { + const precipitationData = [ + { + id: 'northeast', + label: 'Northeast', + data: [5.14, 1.53, 5.73, 4.29, 3.78, 3.92, 4.19, 5.54, 2.03, 1.42, 2.95, 3.89], + color: 'rgb(var(--blue40))', + }, + { + id: 'upperMidwest', + label: 'Upper Midwest', + data: [1.44, 0.49, 2.16, 3.67, 5.44, 6.21, 4.02, 3.67, 0.92, 1.47, 3.05, 1.48], + color: 'rgb(var(--green40))', + }, + { + id: 'southwest', + label: 'Southwest', + data: [1.12, 1.5, 1.52, 0.75, 0.76, 1.27, 1.44, 2.01, 0.62, 1.08, 1.23, 0.25], + color: 'rgb(var(--purple40))', + }, + ]; + + const xAxisData = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + return ( + + + 2024 Precipitation by Climate Region + + + + `${value} in`} /> + + + ); +} +``` diff --git a/apps/docs/docs/components/graphs/ChartTooltip/_webPropsTable.mdx b/apps/docs/docs/components/graphs/ChartTooltip/_webPropsTable.mdx new file mode 100644 index 0000000000..e175425215 --- /dev/null +++ b/apps/docs/docs/components/graphs/ChartTooltip/_webPropsTable.mdx @@ -0,0 +1,11 @@ +import ComponentPropsTable from '@site/src/components/page/ComponentPropsTable'; + +import webPropsData from ':docgen/web-visualization/chart/ChartTooltip/data'; +import { sharedParentTypes } from ':docgen/_types/sharedParentTypes'; +import { sharedTypeAliases } from ':docgen/_types/sharedTypeAliases'; + + diff --git a/apps/docs/docs/components/graphs/ChartTooltip/index.mdx b/apps/docs/docs/components/graphs/ChartTooltip/index.mdx new file mode 100644 index 0000000000..8e8549f091 --- /dev/null +++ b/apps/docs/docs/components/graphs/ChartTooltip/index.mdx @@ -0,0 +1,27 @@ +--- +id: chartTooltip +title: ChartTooltip +platform_switcher_options: { web: true, mobile: false } +hide_title: true +--- + +import { VStack } from '@coinbase/cds-web/layout'; + +import { ComponentHeader } from '@site/src/components/page/ComponentHeader'; +import { ComponentTabsContainer } from '@site/src/components/page/ComponentTabsContainer'; + +import webPropsToc from ':docgen/web-visualization/chart/ChartTooltip/toc-props'; + +import WebPropsTable from './_webPropsTable.mdx'; +import WebExamples, { toc as webExamplesToc } from './_webExamples.mdx'; +import webMetadata from './webMetadata.json'; + + + + } + webExamples={} + webExamplesToc={webExamplesToc} + webPropsToc={webPropsToc} + /> + diff --git a/apps/docs/docs/components/graphs/ChartTooltip/webMetadata.json b/apps/docs/docs/components/graphs/ChartTooltip/webMetadata.json new file mode 100644 index 0000000000..6efbcabee5 --- /dev/null +++ b/apps/docs/docs/components/graphs/ChartTooltip/webMetadata.json @@ -0,0 +1,29 @@ +{ + "import": "import { ChartTooltip } from '@coinbase/cds-web-visualization'", + "source": "https://github.com/coinbase/cds/blob/master/packages/web-visualization/src/chart/ChartTooltip.tsx", + "description": "A tooltip component for displaying series data at the current scrubber position. Supports custom labels, value formatting, and series filtering.", + "relatedComponents": [ + { + "label": "CartesianChart", + "url": "/components/graphs/CartesianChart/" + }, + { + "label": "Legend", + "url": "/components/graphs/Legend/" + }, + { + "label": "LineChart", + "url": "/components/graphs/LineChart/" + }, + { + "label": "Scrubber", + "url": "/components/graphs/Scrubber/" + } + ], + "dependencies": [ + { + "name": "framer-motion", + "version": "^10.18.0" + } + ] +} diff --git a/apps/docs/docs/components/graphs/Legend/_mobileExamples.mdx b/apps/docs/docs/components/graphs/Legend/_mobileExamples.mdx new file mode 100644 index 0000000000..128fc0019b --- /dev/null +++ b/apps/docs/docs/components/graphs/Legend/_mobileExamples.mdx @@ -0,0 +1,296 @@ +Legend displays series information for charts, showing labels and color indicators for each data series. It can be positioned around the chart and supports custom shapes and item components. + +## Basic Usage + +Use the `legend` prop on chart components to enable a default legend, or pass a `Legend` component for customization. + +```tsx +function BasicLegend() { + const theme = useTheme(); + const pages = useMemo( + () => ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'], + [], + ); + const pageViews = useMemo(() => [2400, 1398, 9800, 3908, 4800, 3800, 4300], []); + const uniqueVisitors = useMemo(() => [4000, 3000, 2000, 2780, 1890, 2390, 3490], []); + + return ( + + + + ); +} +``` + +## Shape Variants + +Legend supports different shape variants: `pill`, `circle`, `square`, and `squircle`. Set the shape on each series using `legendShape`. + +```tsx +function ShapeVariants() { + const theme = useTheme(); + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + + return ( + + } + legendPosition="bottom" + series={[ + { + id: 'pill', + label: 'Pill', + data: [120, 150, 130, 170, 160, 190], + color: `rgb(${theme.spectrum.blue40})`, + legendShape: 'pill', + }, + { + id: 'circle', + label: 'Circle', + data: [80, 110, 95, 125, 115, 140], + color: `rgb(${theme.spectrum.green40})`, + legendShape: 'circle', + }, + { + id: 'square', + label: 'Square', + data: [60, 85, 70, 100, 90, 115], + color: `rgb(${theme.spectrum.orange40})`, + legendShape: 'square', + }, + { + id: 'squircle', + label: 'Squircle', + data: [40, 60, 50, 75, 65, 85], + color: `rgb(${theme.spectrum.purple40})`, + legendShape: 'squircle', + }, + ]} + width="100%" + xAxis={{ data: months }} + /> + + ); +} +``` + +## Layout Direction + +Use the `direction` prop to arrange legend items in a row or column. + +```tsx +function LayoutDirection() { + const theme = useTheme(); + + const series = [ + { + id: 'stocks', + data: 45, + label: 'Stocks', + color: `rgb(${theme.spectrum.blue40})`, + legendShape: 'circle' as const, + }, + { + id: 'bonds', + data: 25, + label: 'Bonds', + color: `rgb(${theme.spectrum.green40})`, + legendShape: 'circle' as const, + }, + { + id: 'realEstate', + data: 15, + label: 'Real Estate', + color: `rgb(${theme.spectrum.orange40})`, + legendShape: 'circle' as const, + }, + { + id: 'commodities', + data: 10, + label: 'Commodities', + color: `rgb(${theme.spectrum.purple40})`, + legendShape: 'circle' as const, + }, + { + id: 'cash', + data: 5, + label: 'Cash', + color: `rgb(${theme.spectrum.gray40})`, + legendShape: 'circle' as const, + }, + ]; + + return ( + + Portfolio Allocation + } + legendPosition="right" + series={series} + width={300} + /> + + ); +} +``` + +## Donut Chart Legend + +Legend works with donut and pie charts to show segment information. + +```tsx +function DonutChartLegend() { + const theme = useTheme(); + + const series = [ + { + id: 'completed', + data: 68, + label: 'Completed', + color: theme.color.fgPositive, + legendShape: 'squircle' as const, + }, + { + id: 'inProgress', + data: 22, + label: 'In Progress', + color: `rgb(${theme.spectrum.blue40})`, + legendShape: 'squircle' as const, + }, + { + id: 'pending', + data: 10, + label: 'Pending', + color: `rgb(${theme.spectrum.gray40})`, + legendShape: 'squircle' as const, + }, + ]; + + return ( + + Task Status + + + ); +} +``` + +## Custom Legend Item + +Use `ItemComponent` to render custom legend items with additional information or interactions. + +```tsx +function CustomLegendItem() { + const theme = useTheme(); + + const CustomItem = memo(function CustomItem({ label, color, shape }) { + return ( + + + {label} + 100% + + ); + }); + + return ( + + Custom Legend Item + } + legendPosition="bottom" + series={[ + { + id: 'btc', + data: 60, + label: 'Bitcoin', + color: `rgb(${theme.spectrum.orange40})`, + }, + { + id: 'eth', + data: 30, + label: 'Ethereum', + color: `rgb(${theme.spectrum.purple40})`, + }, + { + id: 'other', + data: 10, + label: 'Other', + color: `rgb(${theme.spectrum.gray40})`, + }, + ]} + width={150} + /> + + ); +} +``` + +## DefaultLegendShape + +You can use `DefaultLegendShape` directly to create custom legend layouts or displays. + +```tsx +function DefaultShapes() { + const theme = useTheme(); + + const spectrumColors = [ + 'blue40', + 'green40', + 'orange40', + 'yellow40', + 'gray40', + 'indigo40', + 'pink40', + 'purple40', + 'red40', + 'teal40', + ]; + + const shapes: LegendShapeVariant[] = ['pill', 'circle', 'squircle', 'square']; + + return ( + + {shapes.map((shape) => ( + + {spectrumColors.map((color) => ( + + + + ))} + + ))} + + ); +} +``` diff --git a/apps/docs/docs/components/graphs/Legend/_mobilePropsTable.mdx b/apps/docs/docs/components/graphs/Legend/_mobilePropsTable.mdx new file mode 100644 index 0000000000..60808a42b4 --- /dev/null +++ b/apps/docs/docs/components/graphs/Legend/_mobilePropsTable.mdx @@ -0,0 +1,11 @@ +import ComponentPropsTable from '@site/src/components/page/ComponentPropsTable'; + +import mobilePropsData from ':docgen/mobile-visualization/chart/legend/Legend/data'; +import { sharedParentTypes } from ':docgen/_types/sharedParentTypes'; +import { sharedTypeAliases } from ':docgen/_types/sharedTypeAliases'; + + diff --git a/apps/docs/docs/components/graphs/Legend/_webExamples.mdx b/apps/docs/docs/components/graphs/Legend/_webExamples.mdx new file mode 100644 index 0000000000..417e6cfd78 --- /dev/null +++ b/apps/docs/docs/components/graphs/Legend/_webExamples.mdx @@ -0,0 +1,604 @@ +Legend displays series information for charts, showing labels and color indicators for each data series. It can be positioned around the chart and supports custom shapes and item components. + +## Basic Usage + +Use the `legend` prop on chart components to enable a default legend, or pass a `Legend` component for customization. + +```jsx live +function BasicLegend() { + const pages = useMemo( + () => ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'], + [], + ); + const pageViews = useMemo(() => [2400, 1398, 9800, 3908, 4800, 3800, 4300], []); + const uniqueVisitors = useMemo(() => [4000, 3000, 2000, 2780, 1890, 2390, 3490], []); + + const numberFormatter = useCallback( + (value) => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value), + [], + ); + + return ( + + + + ); +} +``` + +## Legend Position + +Use `legendPosition` to place the legend at different positions around the chart. You can also customize alignment using the `justifyContent` prop on Legend. + +```jsx live +function LegendPosition() { + return ( + } + legendPosition="bottom" + series={[ + { + id: 'revenue', + label: 'Revenue', + data: [455, 520, 380, 455, 285, 235], + yAxisId: 'revenue', + color: 'rgb(var(--yellow40))', + legendShape: 'squircle', + }, + { + id: 'profitMargin', + label: 'Profit Margin', + data: [23, 20, 16, 38, 12, 9], + yAxisId: 'profitMargin', + color: 'var(--color-fgPositive)', + legendShape: 'squircle', + }, + ]} + xAxis={{ + data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], + scaleType: 'band', + }} + yAxis={[ + { + id: 'revenue', + domain: { min: 0 }, + }, + { + id: 'profitMargin', + domain: { max: 100, min: 0 }, + }, + ]} + > + + `$${value}k`} + width={60} + /> + `${value}%`} + /> + + + ); +} +``` + +## Shape Variants + +Legend supports different shape variants: `pill`, `circle`, `square`, and `squircle`. Set the shape on each series using `legendShape`. + +```jsx live +function ShapeVariants() { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + + return ( + } + legendPosition="left" + series={[ + { + id: 'pill', + label: 'Pill', + data: [120, 150, 130, 170, 160, 190], + color: 'rgb(var(--blue40))', + legendShape: 'pill', + }, + { + id: 'circle', + label: 'Circle', + data: [80, 110, 95, 125, 115, 140], + color: 'rgb(var(--green40))', + legendShape: 'circle', + }, + { + id: 'square', + label: 'Square', + data: [60, 85, 70, 100, 90, 115], + color: 'rgb(var(--orange40))', + legendShape: 'square', + }, + { + id: 'squircle', + label: 'Squircle', + data: [40, 60, 50, 75, 65, 85], + color: 'rgb(var(--purple40))', + legendShape: 'squircle', + }, + ]} + xAxis={{ data: months }} + yAxis={{ domain: { min: 0 }, showGrid: true }} + /> + ); +} +``` + +## Layout Direction + +Use the `direction` prop to arrange legend items in a row or column. + +```jsx live +function LayoutDirection() { + const series = [ + { + id: 'stocks', + data: 45, + label: 'Stocks', + color: 'rgb(var(--blue40))', + legendShape: 'circle', + }, + { + id: 'bonds', + data: 25, + label: 'Bonds', + color: 'rgb(var(--green40))', + legendShape: 'circle', + }, + { + id: 'realEstate', + data: 15, + label: 'Real Estate', + color: 'rgb(var(--orange40))', + legendShape: 'circle', + }, + { + id: 'commodities', + data: 10, + label: 'Commodities', + color: 'rgb(var(--purple40))', + legendShape: 'circle', + }, + { + id: 'cash', + data: 5, + label: 'Cash', + color: 'rgb(var(--gray40))', + legendShape: 'circle', + }, + ]; + + return ( + + + Portfolio Allocation + + } + legendPosition="right" + series={series} + /> + + ); +} +``` + +## Custom Legend Item + +Use `ItemComponent` to render custom legend items with additional information or interactions. + +```jsx live +function CustomLegendItem() { + const timeLabels = [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', + ]; + + const series = [ + { + id: 'candidate-a', + label: 'Candidate A', + data: [48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 38], + color: 'rgb(var(--blue40))', + legendShape: 'circle', + }, + { + id: 'candidate-b', + label: 'Candidate B', + data: [null, null, null, 6, 10, 14, 18, 22, 26, 29, 32, 35], + color: 'rgb(var(--orange40))', + legendShape: 'circle', + }, + { + id: 'candidate-c', + label: 'Candidate C', + data: [52, 53, 54, 49, 46, 43, 40, 37, 34, 32, 30, 27], + color: 'rgb(var(--gray40))', + legendShape: 'circle', + }, + ]; + + const ValueLegendItem = memo(function ValueLegendItem({ + seriesId, + label, + color, + shape, + }) { + const { scrubberPosition } = useScrubberContext(); + const { series, dataLength } = useCartesianChartContext(); + + const dataIndex = scrubberPosition ?? dataLength - 1; + + const seriesData = series.find((s) => s.id === seriesId); + const rawValue = seriesData?.data?.[dataIndex]; + + const formattedValue = + rawValue === null || rawValue === undefined ? '--' : `${Math.round(rawValue)}%`; + + return ( + + + {label} + + {formattedValue} + + + ); + }); + + return ( + + + Election Polls + + + } + legendPosition="bottom" + series={series} + xAxis={{ + data: timeLabels, + }} + yAxis={{ + domain: { max: 100, min: 0 }, + showGrid: true, + tickLabelFormatter: (value) => `${value}%`, + }} + > + + + + ); +} +``` + +## Interactive Legend + +Create interactive legends by using custom item components with state management. + +```jsx live +function InteractiveLegend() { + const [emphasizedId, setEmphasizedId] = useState(null); + + const months = [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', + ]; + + const seriesConfig = useMemo( + () => [ + { + id: 'revenue', + label: 'Revenue', + data: [120, 150, 180, 165, 190, 210, 240, 220, 260, 280, 310, 350], + baseColor: '--blue', + }, + { + id: 'expenses', + label: 'Expenses', + data: [80, 95, 110, 105, 120, 130, 145, 140, 155, 165, 180, 195], + baseColor: '--orange', + }, + { + id: 'profit', + label: 'Profit', + data: [40, 55, 70, 60, 70, 80, 95, 80, 105, 115, 130, 155], + baseColor: '--green', + }, + ], + [], + ); + + const handleToggle = useCallback((seriesId) => { + setEmphasizedId((prev) => (prev === seriesId ? null : seriesId)); + }, []); + + const ChipLegendItem = memo(function ChipLegendItem({ seriesId, label }) { + const isEmphasized = emphasizedId === seriesId; + const config = seriesConfig.find((s) => s.id === seriesId); + const baseColor = config?.baseColor ?? '--gray'; + + return ( + handleToggle(seriesId)} + style={{ + backgroundColor: `rgb(var(${baseColor}10))`, + borderWidth: 0, + color: 'var(--color-fg)', + outlineColor: `rgb(var(${baseColor}50))`, + }} + > + + + {label} + + + ); + }); + + const series = useMemo(() => { + return seriesConfig.map((config) => { + const isEmphasized = emphasizedId === config.id; + const isDimmed = emphasizedId !== null && !isEmphasized; + + return { + id: config.id, + label: config.label, + data: config.data, + color: `rgb(var(${config.baseColor}40))`, + opacity: isDimmed ? 0.3 : 1, + }; + }); + }, [emphasizedId, seriesConfig]); + + return ( + + + Financial Overview + + } + legendPosition="bottom" + series={series} + xAxis={{ + data: months, + }} + yAxis={{ + domain: { min: 0 }, + showGrid: true, + tickLabelFormatter: (value) => `$${value}k`, + }} + /> + + ); +} +``` + +## Donut Chart Legend + +Legend works with donut and pie charts to show segment information. + +```jsx live +function DonutChartLegend() { + const series = [ + { + id: 'completed', + data: 68, + label: 'Completed', + color: 'var(--color-fgPositive)', + legendShape: 'squircle', + }, + { + id: 'inProgress', + data: 22, + label: 'In Progress', + color: 'rgb(var(--blue40))', + legendShape: 'squircle', + }, + { + id: 'pending', + data: 10, + label: 'Pending', + color: 'rgb(var(--gray40))', + legendShape: 'squircle', + }, + ]; + + return ( + + + Task Status + + + + ); +} +``` + +## Custom Legend Shapes + +You can pass a custom ReactNode as `legendShape` for fully custom indicators. + +```jsx live +function CustomLegendShapes() { + const months = [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', + ]; + + // Actual revenue (first 9 months) + const actualRevenue = [320, 380, 420, 390, 450, 480, 520, 490, 540, null, null, null]; + + // Forecasted revenue (last 3 months) + const forecastRevenue = [null, null, null, null, null, null, null, null, null, 580, 620, 680]; + + const numberFormatter = useCallback( + (value) => + `$${new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value)}k`, + [], + ); + + // Pattern settings for dotted fill + const patternSize = 4; + const dotSize = 1; + const legendPatternId = useId(); + + // Custom legend indicator that matches the dotted bar pattern + const DottedLegendIndicator = ( + + + + + + + + + + + + + + + ); + + return ( + + + Annual Revenue + + + + + + + + ); +} +``` diff --git a/apps/docs/docs/components/graphs/Legend/_webPropsTable.mdx b/apps/docs/docs/components/graphs/Legend/_webPropsTable.mdx new file mode 100644 index 0000000000..7b21df026f --- /dev/null +++ b/apps/docs/docs/components/graphs/Legend/_webPropsTable.mdx @@ -0,0 +1,11 @@ +import ComponentPropsTable from '@site/src/components/page/ComponentPropsTable'; + +import webPropsData from ':docgen/web-visualization/chart/legend/Legend/data'; +import { sharedParentTypes } from ':docgen/_types/sharedParentTypes'; +import { sharedTypeAliases } from ':docgen/_types/sharedTypeAliases'; + + diff --git a/apps/docs/docs/components/graphs/Legend/index.mdx b/apps/docs/docs/components/graphs/Legend/index.mdx new file mode 100644 index 0000000000..4969ce0b7b --- /dev/null +++ b/apps/docs/docs/components/graphs/Legend/index.mdx @@ -0,0 +1,35 @@ +--- +id: legend +title: Legend +platform_switcher_options: { web: true, mobile: true } +hide_title: true +--- + +import { VStack } from '@coinbase/cds-web/layout'; + +import { ComponentHeader } from '@site/src/components/page/ComponentHeader'; +import { ComponentTabsContainer } from '@site/src/components/page/ComponentTabsContainer'; + +import webPropsToc from ':docgen/web-visualization/chart/legend/Legend/toc-props'; +import mobilePropsToc from ':docgen/mobile-visualization/chart/legend/Legend/toc-props'; + +import WebPropsTable from './_webPropsTable.mdx'; +import MobilePropsTable from './_mobilePropsTable.mdx'; +import WebExamples, { toc as webExamplesToc } from './_webExamples.mdx'; +import MobileExamples, { toc as mobileExamplesToc } from './_mobileExamples.mdx'; +import webMetadata from './webMetadata.json'; +import mobileMetadata from './mobileMetadata.json'; + + + + } + webExamples={} + mobilePropsTable={} + mobileExamples={} + webExamplesToc={webExamplesToc} + mobileExamplesToc={mobileExamplesToc} + webPropsToc={webPropsToc} + mobilePropsToc={mobilePropsToc} + /> + diff --git a/apps/docs/docs/components/graphs/Legend/mobileMetadata.json b/apps/docs/docs/components/graphs/Legend/mobileMetadata.json new file mode 100644 index 0000000000..55826f2014 --- /dev/null +++ b/apps/docs/docs/components/graphs/Legend/mobileMetadata.json @@ -0,0 +1,37 @@ +{ + "import": "import { Legend } from '@coinbase/cds-mobile-visualization'", + "source": "https://github.com/coinbase/cds/blob/master/packages/mobile-visualization/src/chart/legend/Legend.tsx", + "description": "A legend component for displaying series information in charts. Supports customizable shapes, layouts, and custom item components.", + "relatedComponents": [ + { + "label": "CartesianChart", + "url": "/components/graphs/CartesianChart/" + }, + { + "label": "DonutChart", + "url": "/components/graphs/DonutChart/" + }, + { + "label": "LineChart", + "url": "/components/graphs/LineChart/" + }, + { + "label": "PieChart", + "url": "/components/graphs/PieChart/" + } + ], + "dependencies": [ + { + "name": "@shopify/react-native-skia", + "version": "^1.12.4 || ^2.0.0" + }, + { + "name": "react-native-gesture-handler", + "version": "^2.16.2" + }, + { + "name": "react-native-reanimated", + "version": "^3.14.0" + } + ] +} diff --git a/apps/docs/docs/components/graphs/Legend/webMetadata.json b/apps/docs/docs/components/graphs/Legend/webMetadata.json new file mode 100644 index 0000000000..9c9246ff48 --- /dev/null +++ b/apps/docs/docs/components/graphs/Legend/webMetadata.json @@ -0,0 +1,33 @@ +{ + "import": "import { Legend } from '@coinbase/cds-web-visualization'", + "source": "https://github.com/coinbase/cds/blob/master/packages/web-visualization/src/chart/legend/Legend.tsx", + "description": "A legend component for displaying series information in charts. Supports customizable shapes, layouts, and custom item components.", + "relatedComponents": [ + { + "label": "CartesianChart", + "url": "/components/graphs/CartesianChart/" + }, + { + "label": "ChartTooltip", + "url": "/components/graphs/ChartTooltip/" + }, + { + "label": "DonutChart", + "url": "/components/graphs/DonutChart/" + }, + { + "label": "LineChart", + "url": "/components/graphs/LineChart/" + }, + { + "label": "PieChart", + "url": "/components/graphs/PieChart/" + } + ], + "dependencies": [ + { + "name": "framer-motion", + "version": "^10.18.0" + } + ] +} diff --git a/apps/docs/sidebars.ts b/apps/docs/sidebars.ts index ce48fdc1a1..e0c5a8c2c3 100644 --- a/apps/docs/sidebars.ts +++ b/apps/docs/sidebars.ts @@ -603,11 +603,21 @@ const sidebars: SidebarsConfig = { id: 'components/graphs/CartesianChart/cartesianChart', label: 'CartesianChart', }, + { + type: 'doc', + id: 'components/graphs/ChartTooltip/chartTooltip', + label: 'ChartTooltip', + }, { type: 'doc', id: 'components/graphs/DonutChart/donutChart', label: 'DonutChart', }, + { + type: 'doc', + id: 'components/graphs/Legend/legend', + label: 'Legend', + }, { type: 'doc', id: 'components/graphs/LineChart/lineChart', diff --git a/apps/mobile-app/scripts/utils/routes.mjs b/apps/mobile-app/scripts/utils/routes.mjs index 292df2b5e6..6cb1369655 100644 --- a/apps/mobile-app/scripts/utils/routes.mjs +++ b/apps/mobile-app/scripts/utils/routes.mjs @@ -367,6 +367,12 @@ export const routes = [ require("@coinbase/cds-mobile/controls/__stories__/InputStack.stories") .default, }, + { + key: "Legend", + getComponent: () => + require("@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories") + .default, + }, { key: "LinearGradient", getComponent: () => diff --git a/apps/mobile-app/src/routes.ts b/apps/mobile-app/src/routes.ts index 292df2b5e6..6cb1369655 100644 --- a/apps/mobile-app/src/routes.ts +++ b/apps/mobile-app/src/routes.ts @@ -367,6 +367,12 @@ export const routes = [ require("@coinbase/cds-mobile/controls/__stories__/InputStack.stories") .default, }, + { + key: "Legend", + getComponent: () => + require("@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories") + .default, + }, { key: "LinearGradient", getComponent: () => diff --git a/packages/ui-mobile-playground/src/routes.ts b/packages/ui-mobile-playground/src/routes.ts index 292df2b5e6..6cb1369655 100644 --- a/packages/ui-mobile-playground/src/routes.ts +++ b/packages/ui-mobile-playground/src/routes.ts @@ -367,6 +367,12 @@ export const routes = [ require("@coinbase/cds-mobile/controls/__stories__/InputStack.stories") .default, }, + { + key: "Legend", + getComponent: () => + require("@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories") + .default, + }, { key: "LinearGradient", getComponent: () => diff --git a/packages/ui-mobile-visreg/src/routes.ts b/packages/ui-mobile-visreg/src/routes.ts index 292df2b5e6..6cb1369655 100644 --- a/packages/ui-mobile-visreg/src/routes.ts +++ b/packages/ui-mobile-visreg/src/routes.ts @@ -367,6 +367,12 @@ export const routes = [ require("@coinbase/cds-mobile/controls/__stories__/InputStack.stories") .default, }, + { + key: "Legend", + getComponent: () => + require("@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories") + .default, + }, { key: "LinearGradient", getComponent: () => From 5e3db462604d0cfe25271ab29f1d66fa162a099f Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Mon, 8 Dec 2025 14:58:46 -0500 Subject: [PATCH 07/13] Support chart type in context --- .../src/chart/CartesianChart.tsx | 1 + .../src/chart/PolarChart.tsx | 1 + .../src/chart/utils/context.ts | 21 +++++++++++++++++-- .../src/chart/CartesianChart.tsx | 1 + .../src/chart/PolarChart.tsx | 1 + .../src/chart/utils/context.ts | 21 +++++++++++++++++-- 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/mobile-visualization/src/chart/CartesianChart.tsx b/packages/mobile-visualization/src/chart/CartesianChart.tsx index 575b59e4f8..c6c3125942 100644 --- a/packages/mobile-visualization/src/chart/CartesianChart.tsx +++ b/packages/mobile-visualization/src/chart/CartesianChart.tsx @@ -416,6 +416,7 @@ export const CartesianChart = memo( const contextValue: CartesianChartContextValue = useMemo( () => ({ + type: 'cartesian', series: series ?? [], getSeries, getSeriesData: getStackedSeriesData, diff --git a/packages/mobile-visualization/src/chart/PolarChart.tsx b/packages/mobile-visualization/src/chart/PolarChart.tsx index 4afd50d6da..ddaf8508cc 100644 --- a/packages/mobile-visualization/src/chart/PolarChart.tsx +++ b/packages/mobile-visualization/src/chart/PolarChart.tsx @@ -399,6 +399,7 @@ export const PolarChart = memo( const contextValue: PolarChartContextValue = useMemo( () => ({ + type: 'polar', series: series ?? [], getSeries, getSeriesData, diff --git a/packages/mobile-visualization/src/chart/utils/context.ts b/packages/mobile-visualization/src/chart/utils/context.ts index b27551e37b..56ceb6c4c0 100644 --- a/packages/mobile-visualization/src/chart/utils/context.ts +++ b/packages/mobile-visualization/src/chart/utils/context.ts @@ -8,10 +8,19 @@ import type { AngularAxisConfig, CartesianAxisConfig, RadialAxisConfig } from '. import type { CartesianSeries, PolarSeries, Series } from './chart'; import type { ChartScaleFunction, SerializableScale } from './scale'; +/** + * Chart context type discriminator. + */ +export type ChartType = 'cartesian' | 'polar'; + /** * Base context value for all chart types. */ export type ChartContextValue = { + /** + * The type of chart. + */ + type: ChartType; /** * The series data for the chart. * Contains common series properties (id, label, color, legendShape). @@ -56,7 +65,11 @@ export type ChartContextValue = { * Context value for Cartesian (X/Y) coordinate charts. * Contains axis-specific methods and properties for rectangular coordinate systems. */ -export type CartesianChartContextValue = Omit & { +export type CartesianChartContextValue = Omit & { + /** + * The type of chart. + */ + type: 'cartesian'; /** * The series data for the chart. */ @@ -122,7 +135,11 @@ export type CartesianChartContextValue = Omit & { * Context value for Polar (Angular/Radial) coordinate charts. * Contains axis-specific methods and properties for polar coordinate systems. */ -export type PolarChartContextValue = Omit & { +export type PolarChartContextValue = Omit & { + /** + * The type of chart. + */ + type: 'polar'; /** * The series data for the chart. */ diff --git a/packages/web-visualization/src/chart/CartesianChart.tsx b/packages/web-visualization/src/chart/CartesianChart.tsx index fc9de28d5c..167fb6d0d6 100644 --- a/packages/web-visualization/src/chart/CartesianChart.tsx +++ b/packages/web-visualization/src/chart/CartesianChart.tsx @@ -394,6 +394,7 @@ export const CartesianChart = memo( const contextValue: CartesianChartContextValue = useMemo( () => ({ + type: 'cartesian', series: series ?? [], getSeries, getSeriesData: getStackedSeriesData, diff --git a/packages/web-visualization/src/chart/PolarChart.tsx b/packages/web-visualization/src/chart/PolarChart.tsx index abbf8bc444..2fae9c7910 100644 --- a/packages/web-visualization/src/chart/PolarChart.tsx +++ b/packages/web-visualization/src/chart/PolarChart.tsx @@ -361,6 +361,7 @@ export const PolarChart = memo( const contextValue: PolarChartContextValue = useMemo( () => ({ + type: 'polar', series: series ?? [], getSeries, getSeriesData, diff --git a/packages/web-visualization/src/chart/utils/context.ts b/packages/web-visualization/src/chart/utils/context.ts index 05214d5599..588014b1e1 100644 --- a/packages/web-visualization/src/chart/utils/context.ts +++ b/packages/web-visualization/src/chart/utils/context.ts @@ -5,10 +5,19 @@ import type { AngularAxisConfig, CartesianAxisConfig, RadialAxisConfig } from '. import type { CartesianSeries, PolarSeries, Series } from './chart'; import type { ChartScaleFunction } from './scale'; +/** + * Chart context type discriminator. + */ +export type ChartType = 'cartesian' | 'polar'; + /** * Base context value for all chart types. */ export type ChartContextValue = { + /** + * The type of chart. + */ + type: ChartType; /** * The series data for the chart. * Contains common series properties (id, label, color, legendShape). @@ -44,7 +53,11 @@ export type ChartContextValue = { * Context value for Cartesian (X/Y) coordinate charts. * Contains axis-specific methods and properties for rectangular coordinate systems. */ -export type CartesianChartContextValue = Omit & { +export type CartesianChartContextValue = Omit & { + /** + * The type of chart. + */ + type: 'cartesian'; /** * The series data for the chart. */ @@ -101,7 +114,11 @@ export type CartesianChartContextValue = Omit & { * Context value for Polar (Angular/Radial) coordinate charts. * Contains axis-specific methods and properties for polar coordinate systems. */ -export type PolarChartContextValue = Omit & { +export type PolarChartContextValue = Omit & { + /** + * The type of chart. + */ + type: 'polar'; /** * The series data for the chart. */ From 18a17f4b7a660b26ad73681e2454db29683d38c2 Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Mon, 8 Dec 2025 18:09:03 -0500 Subject: [PATCH 08/13] Fix spacing --- .../mobile-visualization/src/chart/CartesianChart.tsx | 10 +++++----- packages/mobile-visualization/src/chart/PolarChart.tsx | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/mobile-visualization/src/chart/CartesianChart.tsx b/packages/mobile-visualization/src/chart/CartesianChart.tsx index c6c3125942..d48fbe9019 100644 --- a/packages/mobile-visualization/src/chart/CartesianChart.tsx +++ b/packages/mobile-visualization/src/chart/CartesianChart.tsx @@ -36,13 +36,13 @@ type ChartCanvasProps = { }; const ChartCanvas = memo(({ children, style, onLayout }: ChartCanvasProps) => { - const ContextBridge = useChartContextBridge(); + const ContextBridge = useChartContextBridge(); - return ( + return ( - {children} - - ); + {children} + + ); }); export type LegendPosition = 'top' | 'bottom' | 'left' | 'right'; diff --git a/packages/mobile-visualization/src/chart/PolarChart.tsx b/packages/mobile-visualization/src/chart/PolarChart.tsx index ddaf8508cc..f3ebf68b8b 100644 --- a/packages/mobile-visualization/src/chart/PolarChart.tsx +++ b/packages/mobile-visualization/src/chart/PolarChart.tsx @@ -37,13 +37,13 @@ type ChartCanvasProps = { }; const ChartCanvas = memo(({ children, style, onLayout }: ChartCanvasProps) => { - const ContextBridge = useChartContextBridge(); + const ContextBridge = useChartContextBridge(); - return ( + return ( - {children} - - ); + {children} + + ); }); export type PolarChartBaseProps = Omit & { From afb0bac50e98f0cd6fc4ac95e028f6e3d511ecdf Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Tue, 9 Dec 2025 09:02:39 -0500 Subject: [PATCH 09/13] Fix defaults to be consistent across platforms --- .../src/chart/scrubber/DefaultScrubberBeaconLabel.tsx | 6 ------ .../src/chart/scrubber/DefaultScrubberBeaconLabel.tsx | 6 ------ packages/web-visualization/src/chart/text/ChartText.tsx | 4 ++-- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/mobile-visualization/src/chart/scrubber/DefaultScrubberBeaconLabel.tsx b/packages/mobile-visualization/src/chart/scrubber/DefaultScrubberBeaconLabel.tsx index 6f3234bea1..2cccdabba1 100644 --- a/packages/mobile-visualization/src/chart/scrubber/DefaultScrubberBeaconLabel.tsx +++ b/packages/mobile-visualization/src/chart/scrubber/DefaultScrubberBeaconLabel.tsx @@ -19,12 +19,9 @@ export type DefaultScrubberBeaconLabelProps = ScrubberBeaconLabelProps & */ export const DefaultScrubberBeaconLabel = memo( ({ - background, color, elevated = true, - borderRadius = 4, font = 'label1', - verticalAlignment = 'middle', inset = { left: labelHorizontalInset, right: labelHorizontalInset, @@ -38,13 +35,10 @@ export const DefaultScrubberBeaconLabel = memo( return ( {label} diff --git a/packages/web-visualization/src/chart/scrubber/DefaultScrubberBeaconLabel.tsx b/packages/web-visualization/src/chart/scrubber/DefaultScrubberBeaconLabel.tsx index 3d0386eb91..419934fe0f 100644 --- a/packages/web-visualization/src/chart/scrubber/DefaultScrubberBeaconLabel.tsx +++ b/packages/web-visualization/src/chart/scrubber/DefaultScrubberBeaconLabel.tsx @@ -18,12 +18,9 @@ export type DefaultScrubberBeaconLabelProps = ScrubberBeaconLabelProps & */ export const DefaultScrubberBeaconLabel = memo( ({ - background = 'var(--color-bg', color = 'var(--color-fgPrimary)', elevated = true, - borderRadius = 4, font = 'label1', - verticalAlignment = 'middle', inset = { left: labelHorizontalInset, right: labelHorizontalInset, @@ -36,13 +33,10 @@ export const DefaultScrubberBeaconLabel = memo( return ( {label} diff --git a/packages/web-visualization/src/chart/text/ChartText.tsx b/packages/web-visualization/src/chart/text/ChartText.tsx index 8e731cafba..d442503c76 100644 --- a/packages/web-visualization/src/chart/text/ChartText.tsx +++ b/packages/web-visualization/src/chart/text/ChartText.tsx @@ -175,8 +175,8 @@ export const ChartText = memo( fontWeight, elevated, color = 'var(--color-fgMuted)', - background = elevated ? 'var(--color-bg)' : 'transparent', - borderRadius, + background = elevated ? 'var(--color-bgElevation1)' : 'transparent', + borderRadius = 4, inset: insetInput, onDimensionsChange, style, From 1cae15917aa69bf93a2a1ab45b53658bc5d9c786 Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Tue, 9 Dec 2025 09:55:49 -0500 Subject: [PATCH 10/13] Fis routes --- .../components/graphs/Legend/_webExamples.mdx | 56 +- apps/mobile-app/scripts/utils/routes.mjs | 853 +++++++----------- apps/mobile-app/src/routes.ts | 853 +++++++----------- .../src/chart/CartesianChart.tsx | 10 +- .../src/chart/PolarChart.tsx | 10 +- .../src/chart/bar/BarChart.tsx | 7 +- packages/ui-mobile-playground/src/routes.ts | 853 +++++++----------- packages/ui-mobile-visreg/src/routes.ts | 853 +++++++----------- .../src/chart/bar/BarChart.tsx | 7 +- 9 files changed, 1421 insertions(+), 2081 deletions(-) diff --git a/apps/docs/docs/components/graphs/Legend/_webExamples.mdx b/apps/docs/docs/components/graphs/Legend/_webExamples.mdx index 417e6cfd78..5c5d9e9f77 100644 --- a/apps/docs/docs/components/graphs/Legend/_webExamples.mdx +++ b/apps/docs/docs/components/graphs/Legend/_webExamples.mdx @@ -246,8 +246,18 @@ Use `ItemComponent` to render custom legend items with additional information or ```jsx live function CustomLegendItem() { const timeLabels = [ - 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', ]; const series = [ @@ -274,12 +284,7 @@ function CustomLegendItem() { }, ]; - const ValueLegendItem = memo(function ValueLegendItem({ - seriesId, - label, - color, - shape, - }) { + const ValueLegendItem = memo(function ValueLegendItem({ seriesId, label, color, shape }) { const { scrubberPosition } = useScrubberContext(); const { series, dataLength } = useCartesianChartContext(); @@ -313,9 +318,7 @@ function CustomLegendItem() { showXAxis showYAxis height={{ base: 200, tablet: 250, desktop: 300 }} - legend={ - - } + legend={} legendPosition="bottom" series={series} xAxis={{ @@ -343,8 +346,18 @@ function InteractiveLegend() { const [emphasizedId, setEmphasizedId] = useState(null); const months = [ - 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', ]; const seriesConfig = useMemo( @@ -497,8 +510,18 @@ You can pass a custom ReactNode as `legendShape` for fully custom indicators. ```jsx live function CustomLegendShapes() { const months = [ - 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', ]; // Actual revenue (first 9 months) @@ -508,8 +531,7 @@ function CustomLegendShapes() { const forecastRevenue = [null, null, null, null, null, null, null, null, null, 580, 620, 680]; const numberFormatter = useCallback( - (value) => - `$${new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value)}k`, + (value) => `$${new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value)}k`, [], ); diff --git a/apps/mobile-app/scripts/utils/routes.mjs b/apps/mobile-app/scripts/utils/routes.mjs index 6cb1369655..0b3492e3a1 100644 --- a/apps/mobile-app/scripts/utils/routes.mjs +++ b/apps/mobile-app/scripts/utils/routes.mjs @@ -4,1008 +4,835 @@ */ export const routes = [ { - key: "Accordion", + key: 'Accordion', getComponent: () => - require("@coinbase/cds-mobile/accordion/__stories__/Accordion.stories") - .default, + require('@coinbase/cds-mobile/accordion/__stories__/Accordion.stories').default, }, { - key: "AlertBasic", + key: 'AlertBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertBasic.stories').default, }, { - key: "AlertLongTitle", + key: 'AlertLongTitle', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertLongTitle.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertLongTitle.stories').default, }, { - key: "AlertOverModal", + key: 'AlertOverModal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertOverModal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertOverModal.stories').default, }, { - key: "AlertPortal", + key: 'AlertPortal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertPortal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertPortal.stories').default, }, { - key: "AlertSingleAction", + key: 'AlertSingleAction', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertSingleAction.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertSingleAction.stories').default, }, { - key: "AlertVerticalActions", + key: 'AlertVerticalActions', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertVerticalActions.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertVerticalActions.stories').default, }, { - key: "AlphaSelect", + key: 'AlphaSelect', getComponent: () => - require("@coinbase/cds-mobile/alpha/select/__stories__/AlphaSelect.stories") - .default, + require('@coinbase/cds-mobile/alpha/select/__stories__/AlphaSelect.stories').default, }, { - key: "AlphaSelectChip", + key: 'AlphaSelectChip', getComponent: () => - require("@coinbase/cds-mobile/alpha/select-chip/__stories__/AlphaSelectChip.stories") - .default, + require('@coinbase/cds-mobile/alpha/select-chip/__stories__/AlphaSelectChip.stories').default, }, { - key: "AlphaTabbedChips", + key: 'AlphaTabbedChips', getComponent: () => - require("@coinbase/cds-mobile/alpha/tabbed-chips/__stories__/AlphaTabbedChips.stories") + require('@coinbase/cds-mobile/alpha/tabbed-chips/__stories__/AlphaTabbedChips.stories') .default, }, { - key: "AnimatedCaret", + key: 'AnimatedCaret', getComponent: () => - require("@coinbase/cds-mobile/motion/__stories__/AnimatedCaret.stories") - .default, + require('@coinbase/cds-mobile/motion/__stories__/AnimatedCaret.stories').default, }, { - key: "AreaChart", + key: 'AreaChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/area/__stories__/AreaChart.stories") + require('@coinbase/cds-mobile-visualization/chart/area/__stories__/AreaChart.stories') .default, }, { - key: "Avatar", - getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/Avatar.stories").default, + key: 'Avatar', + getComponent: () => require('@coinbase/cds-mobile/media/__stories__/Avatar.stories').default, }, { - key: "AvatarButton", + key: 'AvatarButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/AvatarButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/AvatarButton.stories').default, }, { - key: "Axis", + key: 'Axis', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/axis/__stories__/Axis.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/axis/__stories__/Axis.stories').default, }, { - key: "Banner", - getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/Banner.stories").default, + key: 'Banner', + getComponent: () => require('@coinbase/cds-mobile/banner/__stories__/Banner.stories').default, }, { - key: "BannerActions", + key: 'BannerActions', getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/BannerActions.stories") - .default, + require('@coinbase/cds-mobile/banner/__stories__/BannerActions.stories').default, }, { - key: "BannerLayout", + key: 'BannerLayout', getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/BannerLayout.stories") - .default, + require('@coinbase/cds-mobile/banner/__stories__/BannerLayout.stories').default, }, { - key: "BarChart", + key: 'BarChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/bar/__stories__/BarChart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/bar/__stories__/BarChart.stories').default, }, { - key: "Box", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Box.stories").default, + key: 'Box', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Box.stories').default, }, { - key: "BrowserBar", + key: 'BrowserBar', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/BrowserBar.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/BrowserBar.stories').default, }, { - key: "BrowserBarSearchInput", + key: 'BrowserBarSearchInput', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/BrowserBarSearchInput.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/BrowserBarSearchInput.stories').default, }, { - key: "Button", - getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/Button.stories") - .default, + key: 'Button', + getComponent: () => require('@coinbase/cds-mobile/buttons/__stories__/Button.stories').default, }, { - key: "ButtonGroup", + key: 'ButtonGroup', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/ButtonGroup.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/ButtonGroup.stories').default, }, { - key: "Card", - getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/Card.stories").default, + key: 'Card', + getComponent: () => require('@coinbase/cds-mobile/cards/__stories__/Card.stories').default, }, { - key: "Carousel", + key: 'Carousel', getComponent: () => - require("@coinbase/cds-mobile/carousel/__stories__/Carousel.stories") - .default, + require('@coinbase/cds-mobile/carousel/__stories__/Carousel.stories').default, }, { - key: "CarouselMedia", + key: 'CarouselMedia', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/CarouselMedia.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/CarouselMedia.stories').default, }, { - key: "CartesianChart", + key: 'CartesianChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/CartesianChart.stories") + require('@coinbase/cds-mobile-visualization/chart/__stories__/CartesianChart.stories') .default, }, { - key: "Chart", + key: 'Chart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/Chart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/__stories__/Chart.stories').default, }, { - key: "Checkbox", + key: 'Checkbox', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Checkbox.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/Checkbox.stories').default, }, { - key: "CheckboxCell", + key: 'CheckboxCell', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/CheckboxCell.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/CheckboxCell.stories').default, }, { - key: "Chip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/Chip.stories").default, + key: 'Chip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/Chip.stories').default, }, { - key: "Coachmark", + key: 'Coachmark', getComponent: () => - require("@coinbase/cds-mobile/coachmark/__stories__/Coachmark.stories") - .default, + require('@coinbase/cds-mobile/coachmark/__stories__/Coachmark.stories').default, }, { - key: "Collapsible", + key: 'Collapsible', getComponent: () => - require("@coinbase/cds-mobile/collapsible/__stories__/Collapsible.stories") - .default, + require('@coinbase/cds-mobile/collapsible/__stories__/Collapsible.stories').default, }, { - key: "ContainedAssetCard", + key: 'ContainedAssetCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/ContainedAssetCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/ContainedAssetCard.stories').default, }, { - key: "ContentCard", + key: 'ContentCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/ContentCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/ContentCard.stories').default, }, { - key: "ContentCell", + key: 'ContentCell', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ContentCell.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ContentCell.stories').default, }, { - key: "ContentCellFallback", + key: 'ContentCellFallback', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ContentCellFallback.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ContentCellFallback.stories').default, }, { - key: "ControlGroup", + key: 'ControlGroup', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/ControlGroup.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/ControlGroup.stories').default, }, { - key: "DateInput", - getComponent: () => - require("@coinbase/cds-mobile/dates/__stories__/DateInput.stories") - .default, + key: 'DateInput', + getComponent: () => require('@coinbase/cds-mobile/dates/__stories__/DateInput.stories').default, }, { - key: "DatePicker", + key: 'DatePicker', getComponent: () => - require("@coinbase/cds-mobile/dates/__stories__/DatePicker.stories") - .default, + require('@coinbase/cds-mobile/dates/__stories__/DatePicker.stories').default, }, { - key: "Divider", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Divider.stories") - .default, + key: 'Divider', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Divider.stories').default, }, { - key: "Dot", - getComponent: () => - require("@coinbase/cds-mobile/dots/__stories__/Dot.stories").default, + key: 'Dot', + getComponent: () => require('@coinbase/cds-mobile/dots/__stories__/Dot.stories').default, }, { - key: "DotMisc", - getComponent: () => - require("@coinbase/cds-mobile/dots/__stories__/DotMisc.stories").default, + key: 'DotMisc', + getComponent: () => require('@coinbase/cds-mobile/dots/__stories__/DotMisc.stories').default, }, { - key: "DrawerBottom", + key: 'DrawerBottom', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerBottom.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerBottom.stories').default, }, { - key: "DrawerFallback", + key: 'DrawerFallback', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerFallback.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerFallback.stories').default, }, { - key: "DrawerLeft", + key: 'DrawerLeft', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerLeft.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerLeft.stories').default, }, { - key: "DrawerMisc", + key: 'DrawerMisc', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerMisc.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerMisc.stories').default, }, { - key: "DrawerRight", + key: 'DrawerRight', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerRight.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerRight.stories').default, }, { - key: "DrawerScrollable", + key: 'DrawerScrollable', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerScrollable.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerScrollable.stories').default, }, { - key: "DrawerTop", + key: 'DrawerTop', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerTop.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerTop.stories').default, }, { - key: "FloatingAssetCard", + key: 'FloatingAssetCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/FloatingAssetCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/FloatingAssetCard.stories').default, }, { - key: "Frontier", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Frontier.stories") - .default, + key: 'Frontier', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Frontier.stories').default, }, { - key: "Group", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Group.stories").default, + key: 'Group', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Group.stories').default, }, { - key: "HeroSquare", + key: 'HeroSquare', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/HeroSquare.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/HeroSquare.stories').default, }, { - key: "HintMotion", + key: 'HintMotion', getComponent: () => - require("@coinbase/cds-mobile/motion/__stories__/HintMotion.stories") - .default, + require('@coinbase/cds-mobile/motion/__stories__/HintMotion.stories').default, }, { - key: "IconButton", + key: 'IconButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/IconButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/IconButton.stories').default, }, { - key: "IconCounterButton", + key: 'IconCounterButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/IconCounterButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/IconCounterButton.stories').default, }, { - key: "InputChip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/InputChip.stories") - .default, + key: 'InputChip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/InputChip.stories').default, }, { - key: "InputIcon", + key: 'InputIcon', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputIcon.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputIcon.stories').default, }, { - key: "InputIconButton", + key: 'InputIconButton', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputIconButton.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputIconButton.stories').default, }, { - key: "InputStack", + key: 'InputStack', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputStack.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputStack.stories').default, }, { - key: "Legend", + key: 'Legend', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories').default, }, { - key: "LinearGradient", + key: 'LinearGradient', getComponent: () => - require("@coinbase/cds-mobile/gradients/__stories__/LinearGradient.stories") - .default, + require('@coinbase/cds-mobile/gradients/__stories__/LinearGradient.stories').default, }, { - key: "LineChart", + key: 'LineChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/line/__stories__/LineChart.stories") + require('@coinbase/cds-mobile-visualization/chart/line/__stories__/LineChart.stories') .default, }, { - key: "Link", - getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/Link.stories") - .default, + key: 'Link', + getComponent: () => require('@coinbase/cds-mobile/typography/__stories__/Link.stories').default, }, { - key: "ListCell", - getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ListCell.stories") - .default, + key: 'ListCell', + getComponent: () => require('@coinbase/cds-mobile/cells/__stories__/ListCell.stories').default, }, { - key: "ListCellFallback", + key: 'ListCellFallback', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ListCellFallback.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ListCellFallback.stories').default, }, { - key: "Logo", - getComponent: () => - require("@coinbase/cds-mobile/icons/__stories__/Logo.stories").default, + key: 'Logo', + getComponent: () => require('@coinbase/cds-mobile/icons/__stories__/Logo.stories').default, }, { - key: "Lottie", + key: 'Lottie', getComponent: () => - require("@coinbase/cds-mobile/animation/__stories__/Lottie.stories") - .default, + require('@coinbase/cds-mobile/animation/__stories__/Lottie.stories').default, }, { - key: "LottieStatusAnimation", + key: 'LottieStatusAnimation', getComponent: () => - require("@coinbase/cds-mobile/animation/__stories__/LottieStatusAnimation.stories") - .default, + require('@coinbase/cds-mobile/animation/__stories__/LottieStatusAnimation.stories').default, }, { - key: "MediaChip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/MediaChip.stories") - .default, + key: 'MediaChip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/MediaChip.stories').default, }, { - key: "ModalBackButton", + key: 'ModalBackButton', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalBackButton.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalBackButton.stories').default, }, { - key: "ModalBasic", + key: 'ModalBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalBasic.stories').default, }, { - key: "ModalLong", + key: 'ModalLong', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalLong.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalLong.stories').default, }, { - key: "ModalPortal", + key: 'ModalPortal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalPortal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalPortal.stories').default, }, { - key: "MultiContentModule", + key: 'MultiContentModule', getComponent: () => - require("@coinbase/cds-mobile/multi-content-module/__stories__/MultiContentModule.stories") + require('@coinbase/cds-mobile/multi-content-module/__stories__/MultiContentModule.stories') .default, }, { - key: "NavBarIconButton", + key: 'NavBarIconButton', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavBarIconButton.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavBarIconButton.stories').default, }, { - key: "NavigationSubtitle", + key: 'NavigationSubtitle', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationSubtitle.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationSubtitle.stories').default, }, { - key: "NavigationTitle", + key: 'NavigationTitle', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationTitle.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationTitle.stories').default, }, { - key: "NavigationTitleSelect", + key: 'NavigationTitleSelect', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationTitleSelect.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationTitleSelect.stories').default, }, { - key: "NudgeCard", - getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/NudgeCard.stories") - .default, + key: 'NudgeCard', + getComponent: () => require('@coinbase/cds-mobile/cards/__stories__/NudgeCard.stories').default, }, { - key: "Numpad", - getComponent: () => - require("@coinbase/cds-mobile/numpad/__stories__/Numpad.stories").default, + key: 'Numpad', + getComponent: () => require('@coinbase/cds-mobile/numpad/__stories__/Numpad.stories').default, }, { - key: "Overlay", + key: 'Overlay', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/Overlay.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/Overlay.stories').default, }, { - key: "PageFooter", - getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageFooter.stories") - .default, + key: 'PageFooter', + getComponent: () => require('@coinbase/cds-mobile/page/__stories__/PageFooter.stories').default, }, { - key: "PageFooterInPage", + key: 'PageFooterInPage', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageFooterInPage.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageFooterInPage.stories').default, }, { - key: "PageHeader", - getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeader.stories") - .default, + key: 'PageHeader', + getComponent: () => require('@coinbase/cds-mobile/page/__stories__/PageHeader.stories').default, }, { - key: "PageHeaderInErrorEmptyState", + key: 'PageHeaderInErrorEmptyState', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeaderInErrorEmptyState.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageHeaderInErrorEmptyState.stories').default, }, { - key: "PageHeaderInPage", + key: 'PageHeaderInPage', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeaderInPage.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageHeaderInPage.stories').default, }, { - key: "Palette", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Palette.stories") - .default, + key: 'Palette', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Palette.stories').default, }, { - key: "PatternDisclosureHighFrictionBenefit", + key: 'PatternDisclosureHighFrictionBenefit', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionBenefit.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionBenefit.stories') .default, }, { - key: "PatternDisclosureHighFrictionRisk", + key: 'PatternDisclosureHighFrictionRisk', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionRisk.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionRisk.stories') .default, }, { - key: "PatternDisclosureLowFriction", + key: 'PatternDisclosureLowFriction', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureLowFriction.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureLowFriction.stories') .default, }, { - key: "PatternDisclosureMedFriction", + key: 'PatternDisclosureMedFriction', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureMedFriction.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureMedFriction.stories') .default, }, { - key: "PatternError", + key: 'PatternError', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternError.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/PatternError.stories').default, }, { - key: "PeriodSelector", + key: 'PeriodSelector', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/PeriodSelector.stories") + require('@coinbase/cds-mobile-visualization/chart/__stories__/PeriodSelector.stories') .default, }, { - key: "Pictogram", + key: 'Pictogram', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/Pictogram.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/Pictogram.stories').default, }, { - key: "PolarChart", + key: 'PolarChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/PolarChart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/__stories__/PolarChart.stories').default, }, { - key: "Pressable", + key: 'Pressable', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Pressable.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/Pressable.stories').default, }, { - key: "PressableOpacity", + key: 'PressableOpacity', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PressableOpacity.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/PressableOpacity.stories').default, }, { - key: "ProgressBar", + key: 'ProgressBar', getComponent: () => - require("@coinbase/cds-mobile/visualizations/__stories__/ProgressBar.stories") - .default, + require('@coinbase/cds-mobile/visualizations/__stories__/ProgressBar.stories').default, }, { - key: "ProgressCircle", + key: 'ProgressCircle', getComponent: () => - require("@coinbase/cds-mobile/visualizations/__stories__/ProgressCircle.stories") - .default, + require('@coinbase/cds-mobile/visualizations/__stories__/ProgressCircle.stories').default, }, { - key: "RadioCell", + key: 'RadioCell', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/RadioCell.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/RadioCell.stories').default, }, { - key: "RadioGroup", + key: 'RadioGroup', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/RadioGroup.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/RadioGroup.stories').default, }, { - key: "ReferenceLine", + key: 'ReferenceLine', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/line/__stories__/ReferenceLine.stories") + require('@coinbase/cds-mobile-visualization/chart/line/__stories__/ReferenceLine.stories') .default, }, { - key: "RemoteImage", + key: 'RemoteImage', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/RemoteImage.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/RemoteImage.stories').default, }, { - key: "RemoteImageGroup", + key: 'RemoteImageGroup', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/RemoteImageGroup.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/RemoteImageGroup.stories').default, }, { - key: "RollingNumber", + key: 'RollingNumber', getComponent: () => - require("@coinbase/cds-mobile/numbers/__stories__/RollingNumber.stories") - .default, + require('@coinbase/cds-mobile/numbers/__stories__/RollingNumber.stories').default, }, { - key: "SearchInput", + key: 'SearchInput', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/SearchInput.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/SearchInput.stories').default, }, { - key: "SectionHeader", + key: 'SectionHeader', getComponent: () => - require("@coinbase/cds-mobile/section-header/__stories__/SectionHeader.stories") - .default, + require('@coinbase/cds-mobile/section-header/__stories__/SectionHeader.stories').default, }, { - key: "SegmentedTabs", + key: 'SegmentedTabs', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/SegmentedTabs.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/SegmentedTabs.stories').default, }, { - key: "Select", - getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Select.stories") - .default, + key: 'Select', + getComponent: () => require('@coinbase/cds-mobile/controls/__stories__/Select.stories').default, }, { - key: "SelectChip", + key: 'SelectChip', getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/SelectChip.stories") - .default, + require('@coinbase/cds-mobile/chips/__stories__/SelectChip.stories').default, }, { - key: "SelectOption", + key: 'SelectOption', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/SelectOption.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/SelectOption.stories').default, }, { - key: "SlideButton", + key: 'SlideButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/SlideButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/SlideButton.stories').default, }, { - key: "Spacer", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Spacer.stories").default, + key: 'Spacer', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Spacer.stories').default, }, { - key: "Sparkline", + key: 'Sparkline', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/__stories__/Sparkline.stories") - .default, + require('@coinbase/cds-mobile-visualization/sparkline/__stories__/Sparkline.stories').default, }, { - key: "SparklineGradient", + key: 'SparklineGradient', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/__stories__/SparklineGradient.stories") + require('@coinbase/cds-mobile-visualization/sparkline/__stories__/SparklineGradient.stories') .default, }, { - key: "SparklineInteractive", + key: 'SparklineInteractive', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories") + require('@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories') .default, }, { - key: "SparklineInteractiveHeader", + key: 'SparklineInteractiveHeader', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories") + require('@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories') .default, }, { - key: "Spectrum", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Spectrum.stories") - .default, + key: 'Spectrum', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Spectrum.stories').default, }, { - key: "Spinner", - getComponent: () => - require("@coinbase/cds-mobile/loaders/__stories__/Spinner.stories") - .default, + key: 'Spinner', + getComponent: () => require('@coinbase/cds-mobile/loaders/__stories__/Spinner.stories').default, }, { - key: "SpotIcon", + key: 'SpotIcon', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotIcon.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotIcon.stories').default, }, { - key: "SpotRectangle", + key: 'SpotRectangle', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotRectangle.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotRectangle.stories').default, }, { - key: "SpotSquare", + key: 'SpotSquare', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotSquare.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotSquare.stories').default, }, { - key: "StepperHorizontal", + key: 'StepperHorizontal', getComponent: () => - require("@coinbase/cds-mobile/stepper/__stories__/StepperHorizontal.stories") - .default, + require('@coinbase/cds-mobile/stepper/__stories__/StepperHorizontal.stories').default, }, { - key: "StepperVertical", + key: 'StepperVertical', getComponent: () => - require("@coinbase/cds-mobile/stepper/__stories__/StepperVertical.stories") - .default, + require('@coinbase/cds-mobile/stepper/__stories__/StepperVertical.stories').default, }, { - key: "StickyFooter", + key: 'StickyFooter', getComponent: () => - require("@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooter.stories") - .default, + require('@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooter.stories').default, }, { - key: "StickyFooterWithTray", + key: 'StickyFooterWithTray', getComponent: () => - require("@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooterWithTray.stories") + require('@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooterWithTray.stories') .default, }, { - key: "Switch", - getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Switch.stories") - .default, + key: 'Switch', + getComponent: () => require('@coinbase/cds-mobile/controls/__stories__/Switch.stories').default, }, { - key: "TabbedChips", + key: 'TabbedChips', getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/TabbedChips.stories") - .default, + require('@coinbase/cds-mobile/chips/__stories__/TabbedChips.stories').default, }, { - key: "TabIndicator", + key: 'TabIndicator', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabIndicator.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/TabIndicator.stories').default, }, { - key: "TabLabel", - getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabLabel.stories").default, + key: 'TabLabel', + getComponent: () => require('@coinbase/cds-mobile/tabs/__stories__/TabLabel.stories').default, }, { - key: "TabNavigation", + key: 'TabNavigation', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabNavigation.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/TabNavigation.stories').default, }, { - key: "Tabs", - getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/Tabs.stories").default, + key: 'Tabs', + getComponent: () => require('@coinbase/cds-mobile/tabs/__stories__/Tabs.stories').default, }, { - key: "Tag", - getComponent: () => - require("@coinbase/cds-mobile/tag/__stories__/Tag.stories").default, + key: 'Tag', + getComponent: () => require('@coinbase/cds-mobile/tag/__stories__/Tag.stories').default, }, { - key: "Text", - getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/Text.stories") - .default, + key: 'Text', + getComponent: () => require('@coinbase/cds-mobile/typography/__stories__/Text.stories').default, }, { - key: "TextBody", + key: 'TextBody', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextBody.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextBody.stories').default, }, { - key: "TextCaption", + key: 'TextCaption', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextCaption.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextCaption.stories').default, }, { - key: "TextCore", + key: 'TextCore', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextCore.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextCore.stories').default, }, { - key: "TextDisplay1", + key: 'TextDisplay1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay1.stories').default, }, { - key: "TextDisplay2", + key: 'TextDisplay2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay2.stories').default, }, { - key: "TextDisplay3", + key: 'TextDisplay3', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay3.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay3.stories').default, }, { - key: "TextHeadline", + key: 'TextHeadline', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextHeadline.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextHeadline.stories').default, }, { - key: "TextInput", + key: 'TextInput', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/TextInput.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/TextInput.stories').default, }, { - key: "TextLabel1", + key: 'TextLabel1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLabel1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLabel1.stories').default, }, { - key: "TextLabel2", + key: 'TextLabel2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLabel2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLabel2.stories').default, }, { - key: "TextLegal", + key: 'TextLegal', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLegal.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLegal.stories').default, }, { - key: "TextTitle1", + key: 'TextTitle1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle1.stories').default, }, { - key: "TextTitle2", + key: 'TextTitle2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle2.stories').default, }, { - key: "TextTitle3", + key: 'TextTitle3', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle3.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle3.stories').default, }, { - key: "TextTitle4", + key: 'TextTitle4', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle4.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle4.stories').default, }, { - key: "ThemeProvider", + key: 'ThemeProvider', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/ThemeProvider.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/ThemeProvider.stories').default, }, { - key: "Toast", - getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/Toast.stories") - .default, + key: 'Toast', + getComponent: () => require('@coinbase/cds-mobile/overlays/__stories__/Toast.stories').default, }, { - key: "TooltipV2", + key: 'TooltipV2', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TooltipV2.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TooltipV2.stories').default, }, { - key: "TopNavBar", + key: 'TopNavBar', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/TopNavBar.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/TopNavBar.stories').default, }, { - key: "Tour", - getComponent: () => - require("@coinbase/cds-mobile/tour/__stories__/Tour.stories").default, + key: 'Tour', + getComponent: () => require('@coinbase/cds-mobile/tour/__stories__/Tour.stories').default, }, { - key: "TrayAction", + key: 'TrayAction', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayAction.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayAction.stories').default, }, { - key: "TrayBasic", + key: 'TrayBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayBasic.stories').default, }, { - key: "TrayFallback", + key: 'TrayFallback', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayFallback.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayFallback.stories').default, }, { - key: "TrayFeedCard", + key: 'TrayFeedCard', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayFeedCard.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayFeedCard.stories').default, }, { - key: "TrayInformational", + key: 'TrayInformational', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayInformational.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayInformational.stories').default, }, { - key: "TrayMessaging", + key: 'TrayMessaging', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayMessaging.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayMessaging.stories').default, }, { - key: "TrayMisc", + key: 'TrayMisc', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayMisc.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayMisc.stories').default, }, { - key: "TrayNavigation", + key: 'TrayNavigation', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayNavigation.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayNavigation.stories').default, }, { - key: "TrayPromotional", + key: 'TrayPromotional', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayPromotional.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayPromotional.stories').default, }, { - key: "TrayScrollable", + key: 'TrayScrollable', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayScrollable.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayScrollable.stories').default, }, { - key: "TrayTall", + key: 'TrayTall', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayTall.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayTall.stories').default, }, { - key: "TrayWithTitle", + key: 'TrayWithTitle', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayWithTitle.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayWithTitle.stories').default, }, { - key: "UpsellCard", + key: 'UpsellCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/UpsellCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/UpsellCard.stories').default, }, ]; diff --git a/apps/mobile-app/src/routes.ts b/apps/mobile-app/src/routes.ts index 6cb1369655..0b3492e3a1 100644 --- a/apps/mobile-app/src/routes.ts +++ b/apps/mobile-app/src/routes.ts @@ -4,1008 +4,835 @@ */ export const routes = [ { - key: "Accordion", + key: 'Accordion', getComponent: () => - require("@coinbase/cds-mobile/accordion/__stories__/Accordion.stories") - .default, + require('@coinbase/cds-mobile/accordion/__stories__/Accordion.stories').default, }, { - key: "AlertBasic", + key: 'AlertBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertBasic.stories').default, }, { - key: "AlertLongTitle", + key: 'AlertLongTitle', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertLongTitle.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertLongTitle.stories').default, }, { - key: "AlertOverModal", + key: 'AlertOverModal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertOverModal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertOverModal.stories').default, }, { - key: "AlertPortal", + key: 'AlertPortal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertPortal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertPortal.stories').default, }, { - key: "AlertSingleAction", + key: 'AlertSingleAction', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertSingleAction.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertSingleAction.stories').default, }, { - key: "AlertVerticalActions", + key: 'AlertVerticalActions', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertVerticalActions.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertVerticalActions.stories').default, }, { - key: "AlphaSelect", + key: 'AlphaSelect', getComponent: () => - require("@coinbase/cds-mobile/alpha/select/__stories__/AlphaSelect.stories") - .default, + require('@coinbase/cds-mobile/alpha/select/__stories__/AlphaSelect.stories').default, }, { - key: "AlphaSelectChip", + key: 'AlphaSelectChip', getComponent: () => - require("@coinbase/cds-mobile/alpha/select-chip/__stories__/AlphaSelectChip.stories") - .default, + require('@coinbase/cds-mobile/alpha/select-chip/__stories__/AlphaSelectChip.stories').default, }, { - key: "AlphaTabbedChips", + key: 'AlphaTabbedChips', getComponent: () => - require("@coinbase/cds-mobile/alpha/tabbed-chips/__stories__/AlphaTabbedChips.stories") + require('@coinbase/cds-mobile/alpha/tabbed-chips/__stories__/AlphaTabbedChips.stories') .default, }, { - key: "AnimatedCaret", + key: 'AnimatedCaret', getComponent: () => - require("@coinbase/cds-mobile/motion/__stories__/AnimatedCaret.stories") - .default, + require('@coinbase/cds-mobile/motion/__stories__/AnimatedCaret.stories').default, }, { - key: "AreaChart", + key: 'AreaChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/area/__stories__/AreaChart.stories") + require('@coinbase/cds-mobile-visualization/chart/area/__stories__/AreaChart.stories') .default, }, { - key: "Avatar", - getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/Avatar.stories").default, + key: 'Avatar', + getComponent: () => require('@coinbase/cds-mobile/media/__stories__/Avatar.stories').default, }, { - key: "AvatarButton", + key: 'AvatarButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/AvatarButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/AvatarButton.stories').default, }, { - key: "Axis", + key: 'Axis', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/axis/__stories__/Axis.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/axis/__stories__/Axis.stories').default, }, { - key: "Banner", - getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/Banner.stories").default, + key: 'Banner', + getComponent: () => require('@coinbase/cds-mobile/banner/__stories__/Banner.stories').default, }, { - key: "BannerActions", + key: 'BannerActions', getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/BannerActions.stories") - .default, + require('@coinbase/cds-mobile/banner/__stories__/BannerActions.stories').default, }, { - key: "BannerLayout", + key: 'BannerLayout', getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/BannerLayout.stories") - .default, + require('@coinbase/cds-mobile/banner/__stories__/BannerLayout.stories').default, }, { - key: "BarChart", + key: 'BarChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/bar/__stories__/BarChart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/bar/__stories__/BarChart.stories').default, }, { - key: "Box", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Box.stories").default, + key: 'Box', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Box.stories').default, }, { - key: "BrowserBar", + key: 'BrowserBar', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/BrowserBar.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/BrowserBar.stories').default, }, { - key: "BrowserBarSearchInput", + key: 'BrowserBarSearchInput', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/BrowserBarSearchInput.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/BrowserBarSearchInput.stories').default, }, { - key: "Button", - getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/Button.stories") - .default, + key: 'Button', + getComponent: () => require('@coinbase/cds-mobile/buttons/__stories__/Button.stories').default, }, { - key: "ButtonGroup", + key: 'ButtonGroup', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/ButtonGroup.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/ButtonGroup.stories').default, }, { - key: "Card", - getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/Card.stories").default, + key: 'Card', + getComponent: () => require('@coinbase/cds-mobile/cards/__stories__/Card.stories').default, }, { - key: "Carousel", + key: 'Carousel', getComponent: () => - require("@coinbase/cds-mobile/carousel/__stories__/Carousel.stories") - .default, + require('@coinbase/cds-mobile/carousel/__stories__/Carousel.stories').default, }, { - key: "CarouselMedia", + key: 'CarouselMedia', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/CarouselMedia.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/CarouselMedia.stories').default, }, { - key: "CartesianChart", + key: 'CartesianChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/CartesianChart.stories") + require('@coinbase/cds-mobile-visualization/chart/__stories__/CartesianChart.stories') .default, }, { - key: "Chart", + key: 'Chart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/Chart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/__stories__/Chart.stories').default, }, { - key: "Checkbox", + key: 'Checkbox', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Checkbox.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/Checkbox.stories').default, }, { - key: "CheckboxCell", + key: 'CheckboxCell', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/CheckboxCell.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/CheckboxCell.stories').default, }, { - key: "Chip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/Chip.stories").default, + key: 'Chip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/Chip.stories').default, }, { - key: "Coachmark", + key: 'Coachmark', getComponent: () => - require("@coinbase/cds-mobile/coachmark/__stories__/Coachmark.stories") - .default, + require('@coinbase/cds-mobile/coachmark/__stories__/Coachmark.stories').default, }, { - key: "Collapsible", + key: 'Collapsible', getComponent: () => - require("@coinbase/cds-mobile/collapsible/__stories__/Collapsible.stories") - .default, + require('@coinbase/cds-mobile/collapsible/__stories__/Collapsible.stories').default, }, { - key: "ContainedAssetCard", + key: 'ContainedAssetCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/ContainedAssetCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/ContainedAssetCard.stories').default, }, { - key: "ContentCard", + key: 'ContentCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/ContentCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/ContentCard.stories').default, }, { - key: "ContentCell", + key: 'ContentCell', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ContentCell.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ContentCell.stories').default, }, { - key: "ContentCellFallback", + key: 'ContentCellFallback', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ContentCellFallback.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ContentCellFallback.stories').default, }, { - key: "ControlGroup", + key: 'ControlGroup', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/ControlGroup.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/ControlGroup.stories').default, }, { - key: "DateInput", - getComponent: () => - require("@coinbase/cds-mobile/dates/__stories__/DateInput.stories") - .default, + key: 'DateInput', + getComponent: () => require('@coinbase/cds-mobile/dates/__stories__/DateInput.stories').default, }, { - key: "DatePicker", + key: 'DatePicker', getComponent: () => - require("@coinbase/cds-mobile/dates/__stories__/DatePicker.stories") - .default, + require('@coinbase/cds-mobile/dates/__stories__/DatePicker.stories').default, }, { - key: "Divider", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Divider.stories") - .default, + key: 'Divider', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Divider.stories').default, }, { - key: "Dot", - getComponent: () => - require("@coinbase/cds-mobile/dots/__stories__/Dot.stories").default, + key: 'Dot', + getComponent: () => require('@coinbase/cds-mobile/dots/__stories__/Dot.stories').default, }, { - key: "DotMisc", - getComponent: () => - require("@coinbase/cds-mobile/dots/__stories__/DotMisc.stories").default, + key: 'DotMisc', + getComponent: () => require('@coinbase/cds-mobile/dots/__stories__/DotMisc.stories').default, }, { - key: "DrawerBottom", + key: 'DrawerBottom', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerBottom.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerBottom.stories').default, }, { - key: "DrawerFallback", + key: 'DrawerFallback', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerFallback.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerFallback.stories').default, }, { - key: "DrawerLeft", + key: 'DrawerLeft', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerLeft.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerLeft.stories').default, }, { - key: "DrawerMisc", + key: 'DrawerMisc', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerMisc.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerMisc.stories').default, }, { - key: "DrawerRight", + key: 'DrawerRight', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerRight.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerRight.stories').default, }, { - key: "DrawerScrollable", + key: 'DrawerScrollable', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerScrollable.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerScrollable.stories').default, }, { - key: "DrawerTop", + key: 'DrawerTop', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerTop.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerTop.stories').default, }, { - key: "FloatingAssetCard", + key: 'FloatingAssetCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/FloatingAssetCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/FloatingAssetCard.stories').default, }, { - key: "Frontier", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Frontier.stories") - .default, + key: 'Frontier', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Frontier.stories').default, }, { - key: "Group", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Group.stories").default, + key: 'Group', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Group.stories').default, }, { - key: "HeroSquare", + key: 'HeroSquare', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/HeroSquare.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/HeroSquare.stories').default, }, { - key: "HintMotion", + key: 'HintMotion', getComponent: () => - require("@coinbase/cds-mobile/motion/__stories__/HintMotion.stories") - .default, + require('@coinbase/cds-mobile/motion/__stories__/HintMotion.stories').default, }, { - key: "IconButton", + key: 'IconButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/IconButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/IconButton.stories').default, }, { - key: "IconCounterButton", + key: 'IconCounterButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/IconCounterButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/IconCounterButton.stories').default, }, { - key: "InputChip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/InputChip.stories") - .default, + key: 'InputChip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/InputChip.stories').default, }, { - key: "InputIcon", + key: 'InputIcon', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputIcon.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputIcon.stories').default, }, { - key: "InputIconButton", + key: 'InputIconButton', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputIconButton.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputIconButton.stories').default, }, { - key: "InputStack", + key: 'InputStack', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputStack.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputStack.stories').default, }, { - key: "Legend", + key: 'Legend', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories').default, }, { - key: "LinearGradient", + key: 'LinearGradient', getComponent: () => - require("@coinbase/cds-mobile/gradients/__stories__/LinearGradient.stories") - .default, + require('@coinbase/cds-mobile/gradients/__stories__/LinearGradient.stories').default, }, { - key: "LineChart", + key: 'LineChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/line/__stories__/LineChart.stories") + require('@coinbase/cds-mobile-visualization/chart/line/__stories__/LineChart.stories') .default, }, { - key: "Link", - getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/Link.stories") - .default, + key: 'Link', + getComponent: () => require('@coinbase/cds-mobile/typography/__stories__/Link.stories').default, }, { - key: "ListCell", - getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ListCell.stories") - .default, + key: 'ListCell', + getComponent: () => require('@coinbase/cds-mobile/cells/__stories__/ListCell.stories').default, }, { - key: "ListCellFallback", + key: 'ListCellFallback', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ListCellFallback.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ListCellFallback.stories').default, }, { - key: "Logo", - getComponent: () => - require("@coinbase/cds-mobile/icons/__stories__/Logo.stories").default, + key: 'Logo', + getComponent: () => require('@coinbase/cds-mobile/icons/__stories__/Logo.stories').default, }, { - key: "Lottie", + key: 'Lottie', getComponent: () => - require("@coinbase/cds-mobile/animation/__stories__/Lottie.stories") - .default, + require('@coinbase/cds-mobile/animation/__stories__/Lottie.stories').default, }, { - key: "LottieStatusAnimation", + key: 'LottieStatusAnimation', getComponent: () => - require("@coinbase/cds-mobile/animation/__stories__/LottieStatusAnimation.stories") - .default, + require('@coinbase/cds-mobile/animation/__stories__/LottieStatusAnimation.stories').default, }, { - key: "MediaChip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/MediaChip.stories") - .default, + key: 'MediaChip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/MediaChip.stories').default, }, { - key: "ModalBackButton", + key: 'ModalBackButton', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalBackButton.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalBackButton.stories').default, }, { - key: "ModalBasic", + key: 'ModalBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalBasic.stories').default, }, { - key: "ModalLong", + key: 'ModalLong', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalLong.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalLong.stories').default, }, { - key: "ModalPortal", + key: 'ModalPortal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalPortal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalPortal.stories').default, }, { - key: "MultiContentModule", + key: 'MultiContentModule', getComponent: () => - require("@coinbase/cds-mobile/multi-content-module/__stories__/MultiContentModule.stories") + require('@coinbase/cds-mobile/multi-content-module/__stories__/MultiContentModule.stories') .default, }, { - key: "NavBarIconButton", + key: 'NavBarIconButton', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavBarIconButton.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavBarIconButton.stories').default, }, { - key: "NavigationSubtitle", + key: 'NavigationSubtitle', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationSubtitle.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationSubtitle.stories').default, }, { - key: "NavigationTitle", + key: 'NavigationTitle', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationTitle.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationTitle.stories').default, }, { - key: "NavigationTitleSelect", + key: 'NavigationTitleSelect', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationTitleSelect.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationTitleSelect.stories').default, }, { - key: "NudgeCard", - getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/NudgeCard.stories") - .default, + key: 'NudgeCard', + getComponent: () => require('@coinbase/cds-mobile/cards/__stories__/NudgeCard.stories').default, }, { - key: "Numpad", - getComponent: () => - require("@coinbase/cds-mobile/numpad/__stories__/Numpad.stories").default, + key: 'Numpad', + getComponent: () => require('@coinbase/cds-mobile/numpad/__stories__/Numpad.stories').default, }, { - key: "Overlay", + key: 'Overlay', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/Overlay.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/Overlay.stories').default, }, { - key: "PageFooter", - getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageFooter.stories") - .default, + key: 'PageFooter', + getComponent: () => require('@coinbase/cds-mobile/page/__stories__/PageFooter.stories').default, }, { - key: "PageFooterInPage", + key: 'PageFooterInPage', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageFooterInPage.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageFooterInPage.stories').default, }, { - key: "PageHeader", - getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeader.stories") - .default, + key: 'PageHeader', + getComponent: () => require('@coinbase/cds-mobile/page/__stories__/PageHeader.stories').default, }, { - key: "PageHeaderInErrorEmptyState", + key: 'PageHeaderInErrorEmptyState', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeaderInErrorEmptyState.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageHeaderInErrorEmptyState.stories').default, }, { - key: "PageHeaderInPage", + key: 'PageHeaderInPage', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeaderInPage.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageHeaderInPage.stories').default, }, { - key: "Palette", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Palette.stories") - .default, + key: 'Palette', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Palette.stories').default, }, { - key: "PatternDisclosureHighFrictionBenefit", + key: 'PatternDisclosureHighFrictionBenefit', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionBenefit.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionBenefit.stories') .default, }, { - key: "PatternDisclosureHighFrictionRisk", + key: 'PatternDisclosureHighFrictionRisk', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionRisk.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionRisk.stories') .default, }, { - key: "PatternDisclosureLowFriction", + key: 'PatternDisclosureLowFriction', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureLowFriction.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureLowFriction.stories') .default, }, { - key: "PatternDisclosureMedFriction", + key: 'PatternDisclosureMedFriction', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureMedFriction.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureMedFriction.stories') .default, }, { - key: "PatternError", + key: 'PatternError', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternError.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/PatternError.stories').default, }, { - key: "PeriodSelector", + key: 'PeriodSelector', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/PeriodSelector.stories") + require('@coinbase/cds-mobile-visualization/chart/__stories__/PeriodSelector.stories') .default, }, { - key: "Pictogram", + key: 'Pictogram', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/Pictogram.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/Pictogram.stories').default, }, { - key: "PolarChart", + key: 'PolarChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/PolarChart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/__stories__/PolarChart.stories').default, }, { - key: "Pressable", + key: 'Pressable', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Pressable.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/Pressable.stories').default, }, { - key: "PressableOpacity", + key: 'PressableOpacity', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PressableOpacity.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/PressableOpacity.stories').default, }, { - key: "ProgressBar", + key: 'ProgressBar', getComponent: () => - require("@coinbase/cds-mobile/visualizations/__stories__/ProgressBar.stories") - .default, + require('@coinbase/cds-mobile/visualizations/__stories__/ProgressBar.stories').default, }, { - key: "ProgressCircle", + key: 'ProgressCircle', getComponent: () => - require("@coinbase/cds-mobile/visualizations/__stories__/ProgressCircle.stories") - .default, + require('@coinbase/cds-mobile/visualizations/__stories__/ProgressCircle.stories').default, }, { - key: "RadioCell", + key: 'RadioCell', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/RadioCell.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/RadioCell.stories').default, }, { - key: "RadioGroup", + key: 'RadioGroup', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/RadioGroup.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/RadioGroup.stories').default, }, { - key: "ReferenceLine", + key: 'ReferenceLine', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/line/__stories__/ReferenceLine.stories") + require('@coinbase/cds-mobile-visualization/chart/line/__stories__/ReferenceLine.stories') .default, }, { - key: "RemoteImage", + key: 'RemoteImage', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/RemoteImage.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/RemoteImage.stories').default, }, { - key: "RemoteImageGroup", + key: 'RemoteImageGroup', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/RemoteImageGroup.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/RemoteImageGroup.stories').default, }, { - key: "RollingNumber", + key: 'RollingNumber', getComponent: () => - require("@coinbase/cds-mobile/numbers/__stories__/RollingNumber.stories") - .default, + require('@coinbase/cds-mobile/numbers/__stories__/RollingNumber.stories').default, }, { - key: "SearchInput", + key: 'SearchInput', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/SearchInput.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/SearchInput.stories').default, }, { - key: "SectionHeader", + key: 'SectionHeader', getComponent: () => - require("@coinbase/cds-mobile/section-header/__stories__/SectionHeader.stories") - .default, + require('@coinbase/cds-mobile/section-header/__stories__/SectionHeader.stories').default, }, { - key: "SegmentedTabs", + key: 'SegmentedTabs', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/SegmentedTabs.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/SegmentedTabs.stories').default, }, { - key: "Select", - getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Select.stories") - .default, + key: 'Select', + getComponent: () => require('@coinbase/cds-mobile/controls/__stories__/Select.stories').default, }, { - key: "SelectChip", + key: 'SelectChip', getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/SelectChip.stories") - .default, + require('@coinbase/cds-mobile/chips/__stories__/SelectChip.stories').default, }, { - key: "SelectOption", + key: 'SelectOption', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/SelectOption.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/SelectOption.stories').default, }, { - key: "SlideButton", + key: 'SlideButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/SlideButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/SlideButton.stories').default, }, { - key: "Spacer", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Spacer.stories").default, + key: 'Spacer', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Spacer.stories').default, }, { - key: "Sparkline", + key: 'Sparkline', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/__stories__/Sparkline.stories") - .default, + require('@coinbase/cds-mobile-visualization/sparkline/__stories__/Sparkline.stories').default, }, { - key: "SparklineGradient", + key: 'SparklineGradient', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/__stories__/SparklineGradient.stories") + require('@coinbase/cds-mobile-visualization/sparkline/__stories__/SparklineGradient.stories') .default, }, { - key: "SparklineInteractive", + key: 'SparklineInteractive', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories") + require('@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories') .default, }, { - key: "SparklineInteractiveHeader", + key: 'SparklineInteractiveHeader', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories") + require('@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories') .default, }, { - key: "Spectrum", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Spectrum.stories") - .default, + key: 'Spectrum', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Spectrum.stories').default, }, { - key: "Spinner", - getComponent: () => - require("@coinbase/cds-mobile/loaders/__stories__/Spinner.stories") - .default, + key: 'Spinner', + getComponent: () => require('@coinbase/cds-mobile/loaders/__stories__/Spinner.stories').default, }, { - key: "SpotIcon", + key: 'SpotIcon', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotIcon.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotIcon.stories').default, }, { - key: "SpotRectangle", + key: 'SpotRectangle', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotRectangle.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotRectangle.stories').default, }, { - key: "SpotSquare", + key: 'SpotSquare', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotSquare.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotSquare.stories').default, }, { - key: "StepperHorizontal", + key: 'StepperHorizontal', getComponent: () => - require("@coinbase/cds-mobile/stepper/__stories__/StepperHorizontal.stories") - .default, + require('@coinbase/cds-mobile/stepper/__stories__/StepperHorizontal.stories').default, }, { - key: "StepperVertical", + key: 'StepperVertical', getComponent: () => - require("@coinbase/cds-mobile/stepper/__stories__/StepperVertical.stories") - .default, + require('@coinbase/cds-mobile/stepper/__stories__/StepperVertical.stories').default, }, { - key: "StickyFooter", + key: 'StickyFooter', getComponent: () => - require("@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooter.stories") - .default, + require('@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooter.stories').default, }, { - key: "StickyFooterWithTray", + key: 'StickyFooterWithTray', getComponent: () => - require("@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooterWithTray.stories") + require('@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooterWithTray.stories') .default, }, { - key: "Switch", - getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Switch.stories") - .default, + key: 'Switch', + getComponent: () => require('@coinbase/cds-mobile/controls/__stories__/Switch.stories').default, }, { - key: "TabbedChips", + key: 'TabbedChips', getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/TabbedChips.stories") - .default, + require('@coinbase/cds-mobile/chips/__stories__/TabbedChips.stories').default, }, { - key: "TabIndicator", + key: 'TabIndicator', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabIndicator.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/TabIndicator.stories').default, }, { - key: "TabLabel", - getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabLabel.stories").default, + key: 'TabLabel', + getComponent: () => require('@coinbase/cds-mobile/tabs/__stories__/TabLabel.stories').default, }, { - key: "TabNavigation", + key: 'TabNavigation', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabNavigation.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/TabNavigation.stories').default, }, { - key: "Tabs", - getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/Tabs.stories").default, + key: 'Tabs', + getComponent: () => require('@coinbase/cds-mobile/tabs/__stories__/Tabs.stories').default, }, { - key: "Tag", - getComponent: () => - require("@coinbase/cds-mobile/tag/__stories__/Tag.stories").default, + key: 'Tag', + getComponent: () => require('@coinbase/cds-mobile/tag/__stories__/Tag.stories').default, }, { - key: "Text", - getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/Text.stories") - .default, + key: 'Text', + getComponent: () => require('@coinbase/cds-mobile/typography/__stories__/Text.stories').default, }, { - key: "TextBody", + key: 'TextBody', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextBody.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextBody.stories').default, }, { - key: "TextCaption", + key: 'TextCaption', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextCaption.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextCaption.stories').default, }, { - key: "TextCore", + key: 'TextCore', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextCore.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextCore.stories').default, }, { - key: "TextDisplay1", + key: 'TextDisplay1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay1.stories').default, }, { - key: "TextDisplay2", + key: 'TextDisplay2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay2.stories').default, }, { - key: "TextDisplay3", + key: 'TextDisplay3', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay3.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay3.stories').default, }, { - key: "TextHeadline", + key: 'TextHeadline', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextHeadline.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextHeadline.stories').default, }, { - key: "TextInput", + key: 'TextInput', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/TextInput.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/TextInput.stories').default, }, { - key: "TextLabel1", + key: 'TextLabel1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLabel1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLabel1.stories').default, }, { - key: "TextLabel2", + key: 'TextLabel2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLabel2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLabel2.stories').default, }, { - key: "TextLegal", + key: 'TextLegal', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLegal.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLegal.stories').default, }, { - key: "TextTitle1", + key: 'TextTitle1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle1.stories').default, }, { - key: "TextTitle2", + key: 'TextTitle2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle2.stories').default, }, { - key: "TextTitle3", + key: 'TextTitle3', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle3.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle3.stories').default, }, { - key: "TextTitle4", + key: 'TextTitle4', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle4.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle4.stories').default, }, { - key: "ThemeProvider", + key: 'ThemeProvider', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/ThemeProvider.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/ThemeProvider.stories').default, }, { - key: "Toast", - getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/Toast.stories") - .default, + key: 'Toast', + getComponent: () => require('@coinbase/cds-mobile/overlays/__stories__/Toast.stories').default, }, { - key: "TooltipV2", + key: 'TooltipV2', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TooltipV2.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TooltipV2.stories').default, }, { - key: "TopNavBar", + key: 'TopNavBar', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/TopNavBar.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/TopNavBar.stories').default, }, { - key: "Tour", - getComponent: () => - require("@coinbase/cds-mobile/tour/__stories__/Tour.stories").default, + key: 'Tour', + getComponent: () => require('@coinbase/cds-mobile/tour/__stories__/Tour.stories').default, }, { - key: "TrayAction", + key: 'TrayAction', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayAction.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayAction.stories').default, }, { - key: "TrayBasic", + key: 'TrayBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayBasic.stories').default, }, { - key: "TrayFallback", + key: 'TrayFallback', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayFallback.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayFallback.stories').default, }, { - key: "TrayFeedCard", + key: 'TrayFeedCard', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayFeedCard.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayFeedCard.stories').default, }, { - key: "TrayInformational", + key: 'TrayInformational', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayInformational.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayInformational.stories').default, }, { - key: "TrayMessaging", + key: 'TrayMessaging', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayMessaging.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayMessaging.stories').default, }, { - key: "TrayMisc", + key: 'TrayMisc', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayMisc.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayMisc.stories').default, }, { - key: "TrayNavigation", + key: 'TrayNavigation', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayNavigation.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayNavigation.stories').default, }, { - key: "TrayPromotional", + key: 'TrayPromotional', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayPromotional.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayPromotional.stories').default, }, { - key: "TrayScrollable", + key: 'TrayScrollable', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayScrollable.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayScrollable.stories').default, }, { - key: "TrayTall", + key: 'TrayTall', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayTall.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayTall.stories').default, }, { - key: "TrayWithTitle", + key: 'TrayWithTitle', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayWithTitle.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayWithTitle.stories').default, }, { - key: "UpsellCard", + key: 'UpsellCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/UpsellCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/UpsellCard.stories').default, }, ]; diff --git a/packages/mobile-visualization/src/chart/CartesianChart.tsx b/packages/mobile-visualization/src/chart/CartesianChart.tsx index d48fbe9019..c6c3125942 100644 --- a/packages/mobile-visualization/src/chart/CartesianChart.tsx +++ b/packages/mobile-visualization/src/chart/CartesianChart.tsx @@ -36,13 +36,13 @@ type ChartCanvasProps = { }; const ChartCanvas = memo(({ children, style, onLayout }: ChartCanvasProps) => { - const ContextBridge = useChartContextBridge(); + const ContextBridge = useChartContextBridge(); - return ( + return ( - {children} - - ); + {children} + + ); }); export type LegendPosition = 'top' | 'bottom' | 'left' | 'right'; diff --git a/packages/mobile-visualization/src/chart/PolarChart.tsx b/packages/mobile-visualization/src/chart/PolarChart.tsx index f3ebf68b8b..ddaf8508cc 100644 --- a/packages/mobile-visualization/src/chart/PolarChart.tsx +++ b/packages/mobile-visualization/src/chart/PolarChart.tsx @@ -37,13 +37,13 @@ type ChartCanvasProps = { }; const ChartCanvas = memo(({ children, style, onLayout }: ChartCanvasProps) => { - const ContextBridge = useChartContextBridge(); + const ContextBridge = useChartContextBridge(); - return ( + return ( - {children} - - ); + {children} + + ); }); export type PolarChartBaseProps = Omit & { diff --git a/packages/mobile-visualization/src/chart/bar/BarChart.tsx b/packages/mobile-visualization/src/chart/bar/BarChart.tsx index e681477b15..20e8d2d0bc 100644 --- a/packages/mobile-visualization/src/chart/bar/BarChart.tsx +++ b/packages/mobile-visualization/src/chart/bar/BarChart.tsx @@ -7,7 +7,12 @@ import { type CartesianChartBaseProps, type CartesianChartProps, } from '../CartesianChart'; -import { type CartesianAxisConfigProps, defaultChartInset, defaultStackId, getChartInset } from '../utils'; +import { + type CartesianAxisConfigProps, + defaultChartInset, + defaultStackId, + getChartInset, +} from '../utils'; import { BarPlot, type BarPlotProps } from './BarPlot'; import type { BarSeries } from './BarStack'; diff --git a/packages/ui-mobile-playground/src/routes.ts b/packages/ui-mobile-playground/src/routes.ts index 6cb1369655..0b3492e3a1 100644 --- a/packages/ui-mobile-playground/src/routes.ts +++ b/packages/ui-mobile-playground/src/routes.ts @@ -4,1008 +4,835 @@ */ export const routes = [ { - key: "Accordion", + key: 'Accordion', getComponent: () => - require("@coinbase/cds-mobile/accordion/__stories__/Accordion.stories") - .default, + require('@coinbase/cds-mobile/accordion/__stories__/Accordion.stories').default, }, { - key: "AlertBasic", + key: 'AlertBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertBasic.stories').default, }, { - key: "AlertLongTitle", + key: 'AlertLongTitle', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertLongTitle.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertLongTitle.stories').default, }, { - key: "AlertOverModal", + key: 'AlertOverModal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertOverModal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertOverModal.stories').default, }, { - key: "AlertPortal", + key: 'AlertPortal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertPortal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertPortal.stories').default, }, { - key: "AlertSingleAction", + key: 'AlertSingleAction', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertSingleAction.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertSingleAction.stories').default, }, { - key: "AlertVerticalActions", + key: 'AlertVerticalActions', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertVerticalActions.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertVerticalActions.stories').default, }, { - key: "AlphaSelect", + key: 'AlphaSelect', getComponent: () => - require("@coinbase/cds-mobile/alpha/select/__stories__/AlphaSelect.stories") - .default, + require('@coinbase/cds-mobile/alpha/select/__stories__/AlphaSelect.stories').default, }, { - key: "AlphaSelectChip", + key: 'AlphaSelectChip', getComponent: () => - require("@coinbase/cds-mobile/alpha/select-chip/__stories__/AlphaSelectChip.stories") - .default, + require('@coinbase/cds-mobile/alpha/select-chip/__stories__/AlphaSelectChip.stories').default, }, { - key: "AlphaTabbedChips", + key: 'AlphaTabbedChips', getComponent: () => - require("@coinbase/cds-mobile/alpha/tabbed-chips/__stories__/AlphaTabbedChips.stories") + require('@coinbase/cds-mobile/alpha/tabbed-chips/__stories__/AlphaTabbedChips.stories') .default, }, { - key: "AnimatedCaret", + key: 'AnimatedCaret', getComponent: () => - require("@coinbase/cds-mobile/motion/__stories__/AnimatedCaret.stories") - .default, + require('@coinbase/cds-mobile/motion/__stories__/AnimatedCaret.stories').default, }, { - key: "AreaChart", + key: 'AreaChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/area/__stories__/AreaChart.stories") + require('@coinbase/cds-mobile-visualization/chart/area/__stories__/AreaChart.stories') .default, }, { - key: "Avatar", - getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/Avatar.stories").default, + key: 'Avatar', + getComponent: () => require('@coinbase/cds-mobile/media/__stories__/Avatar.stories').default, }, { - key: "AvatarButton", + key: 'AvatarButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/AvatarButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/AvatarButton.stories').default, }, { - key: "Axis", + key: 'Axis', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/axis/__stories__/Axis.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/axis/__stories__/Axis.stories').default, }, { - key: "Banner", - getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/Banner.stories").default, + key: 'Banner', + getComponent: () => require('@coinbase/cds-mobile/banner/__stories__/Banner.stories').default, }, { - key: "BannerActions", + key: 'BannerActions', getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/BannerActions.stories") - .default, + require('@coinbase/cds-mobile/banner/__stories__/BannerActions.stories').default, }, { - key: "BannerLayout", + key: 'BannerLayout', getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/BannerLayout.stories") - .default, + require('@coinbase/cds-mobile/banner/__stories__/BannerLayout.stories').default, }, { - key: "BarChart", + key: 'BarChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/bar/__stories__/BarChart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/bar/__stories__/BarChart.stories').default, }, { - key: "Box", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Box.stories").default, + key: 'Box', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Box.stories').default, }, { - key: "BrowserBar", + key: 'BrowserBar', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/BrowserBar.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/BrowserBar.stories').default, }, { - key: "BrowserBarSearchInput", + key: 'BrowserBarSearchInput', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/BrowserBarSearchInput.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/BrowserBarSearchInput.stories').default, }, { - key: "Button", - getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/Button.stories") - .default, + key: 'Button', + getComponent: () => require('@coinbase/cds-mobile/buttons/__stories__/Button.stories').default, }, { - key: "ButtonGroup", + key: 'ButtonGroup', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/ButtonGroup.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/ButtonGroup.stories').default, }, { - key: "Card", - getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/Card.stories").default, + key: 'Card', + getComponent: () => require('@coinbase/cds-mobile/cards/__stories__/Card.stories').default, }, { - key: "Carousel", + key: 'Carousel', getComponent: () => - require("@coinbase/cds-mobile/carousel/__stories__/Carousel.stories") - .default, + require('@coinbase/cds-mobile/carousel/__stories__/Carousel.stories').default, }, { - key: "CarouselMedia", + key: 'CarouselMedia', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/CarouselMedia.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/CarouselMedia.stories').default, }, { - key: "CartesianChart", + key: 'CartesianChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/CartesianChart.stories") + require('@coinbase/cds-mobile-visualization/chart/__stories__/CartesianChart.stories') .default, }, { - key: "Chart", + key: 'Chart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/Chart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/__stories__/Chart.stories').default, }, { - key: "Checkbox", + key: 'Checkbox', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Checkbox.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/Checkbox.stories').default, }, { - key: "CheckboxCell", + key: 'CheckboxCell', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/CheckboxCell.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/CheckboxCell.stories').default, }, { - key: "Chip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/Chip.stories").default, + key: 'Chip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/Chip.stories').default, }, { - key: "Coachmark", + key: 'Coachmark', getComponent: () => - require("@coinbase/cds-mobile/coachmark/__stories__/Coachmark.stories") - .default, + require('@coinbase/cds-mobile/coachmark/__stories__/Coachmark.stories').default, }, { - key: "Collapsible", + key: 'Collapsible', getComponent: () => - require("@coinbase/cds-mobile/collapsible/__stories__/Collapsible.stories") - .default, + require('@coinbase/cds-mobile/collapsible/__stories__/Collapsible.stories').default, }, { - key: "ContainedAssetCard", + key: 'ContainedAssetCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/ContainedAssetCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/ContainedAssetCard.stories').default, }, { - key: "ContentCard", + key: 'ContentCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/ContentCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/ContentCard.stories').default, }, { - key: "ContentCell", + key: 'ContentCell', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ContentCell.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ContentCell.stories').default, }, { - key: "ContentCellFallback", + key: 'ContentCellFallback', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ContentCellFallback.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ContentCellFallback.stories').default, }, { - key: "ControlGroup", + key: 'ControlGroup', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/ControlGroup.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/ControlGroup.stories').default, }, { - key: "DateInput", - getComponent: () => - require("@coinbase/cds-mobile/dates/__stories__/DateInput.stories") - .default, + key: 'DateInput', + getComponent: () => require('@coinbase/cds-mobile/dates/__stories__/DateInput.stories').default, }, { - key: "DatePicker", + key: 'DatePicker', getComponent: () => - require("@coinbase/cds-mobile/dates/__stories__/DatePicker.stories") - .default, + require('@coinbase/cds-mobile/dates/__stories__/DatePicker.stories').default, }, { - key: "Divider", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Divider.stories") - .default, + key: 'Divider', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Divider.stories').default, }, { - key: "Dot", - getComponent: () => - require("@coinbase/cds-mobile/dots/__stories__/Dot.stories").default, + key: 'Dot', + getComponent: () => require('@coinbase/cds-mobile/dots/__stories__/Dot.stories').default, }, { - key: "DotMisc", - getComponent: () => - require("@coinbase/cds-mobile/dots/__stories__/DotMisc.stories").default, + key: 'DotMisc', + getComponent: () => require('@coinbase/cds-mobile/dots/__stories__/DotMisc.stories').default, }, { - key: "DrawerBottom", + key: 'DrawerBottom', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerBottom.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerBottom.stories').default, }, { - key: "DrawerFallback", + key: 'DrawerFallback', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerFallback.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerFallback.stories').default, }, { - key: "DrawerLeft", + key: 'DrawerLeft', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerLeft.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerLeft.stories').default, }, { - key: "DrawerMisc", + key: 'DrawerMisc', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerMisc.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerMisc.stories').default, }, { - key: "DrawerRight", + key: 'DrawerRight', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerRight.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerRight.stories').default, }, { - key: "DrawerScrollable", + key: 'DrawerScrollable', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerScrollable.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerScrollable.stories').default, }, { - key: "DrawerTop", + key: 'DrawerTop', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerTop.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerTop.stories').default, }, { - key: "FloatingAssetCard", + key: 'FloatingAssetCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/FloatingAssetCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/FloatingAssetCard.stories').default, }, { - key: "Frontier", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Frontier.stories") - .default, + key: 'Frontier', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Frontier.stories').default, }, { - key: "Group", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Group.stories").default, + key: 'Group', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Group.stories').default, }, { - key: "HeroSquare", + key: 'HeroSquare', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/HeroSquare.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/HeroSquare.stories').default, }, { - key: "HintMotion", + key: 'HintMotion', getComponent: () => - require("@coinbase/cds-mobile/motion/__stories__/HintMotion.stories") - .default, + require('@coinbase/cds-mobile/motion/__stories__/HintMotion.stories').default, }, { - key: "IconButton", + key: 'IconButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/IconButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/IconButton.stories').default, }, { - key: "IconCounterButton", + key: 'IconCounterButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/IconCounterButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/IconCounterButton.stories').default, }, { - key: "InputChip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/InputChip.stories") - .default, + key: 'InputChip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/InputChip.stories').default, }, { - key: "InputIcon", + key: 'InputIcon', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputIcon.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputIcon.stories').default, }, { - key: "InputIconButton", + key: 'InputIconButton', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputIconButton.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputIconButton.stories').default, }, { - key: "InputStack", + key: 'InputStack', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputStack.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputStack.stories').default, }, { - key: "Legend", + key: 'Legend', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories').default, }, { - key: "LinearGradient", + key: 'LinearGradient', getComponent: () => - require("@coinbase/cds-mobile/gradients/__stories__/LinearGradient.stories") - .default, + require('@coinbase/cds-mobile/gradients/__stories__/LinearGradient.stories').default, }, { - key: "LineChart", + key: 'LineChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/line/__stories__/LineChart.stories") + require('@coinbase/cds-mobile-visualization/chart/line/__stories__/LineChart.stories') .default, }, { - key: "Link", - getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/Link.stories") - .default, + key: 'Link', + getComponent: () => require('@coinbase/cds-mobile/typography/__stories__/Link.stories').default, }, { - key: "ListCell", - getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ListCell.stories") - .default, + key: 'ListCell', + getComponent: () => require('@coinbase/cds-mobile/cells/__stories__/ListCell.stories').default, }, { - key: "ListCellFallback", + key: 'ListCellFallback', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ListCellFallback.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ListCellFallback.stories').default, }, { - key: "Logo", - getComponent: () => - require("@coinbase/cds-mobile/icons/__stories__/Logo.stories").default, + key: 'Logo', + getComponent: () => require('@coinbase/cds-mobile/icons/__stories__/Logo.stories').default, }, { - key: "Lottie", + key: 'Lottie', getComponent: () => - require("@coinbase/cds-mobile/animation/__stories__/Lottie.stories") - .default, + require('@coinbase/cds-mobile/animation/__stories__/Lottie.stories').default, }, { - key: "LottieStatusAnimation", + key: 'LottieStatusAnimation', getComponent: () => - require("@coinbase/cds-mobile/animation/__stories__/LottieStatusAnimation.stories") - .default, + require('@coinbase/cds-mobile/animation/__stories__/LottieStatusAnimation.stories').default, }, { - key: "MediaChip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/MediaChip.stories") - .default, + key: 'MediaChip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/MediaChip.stories').default, }, { - key: "ModalBackButton", + key: 'ModalBackButton', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalBackButton.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalBackButton.stories').default, }, { - key: "ModalBasic", + key: 'ModalBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalBasic.stories').default, }, { - key: "ModalLong", + key: 'ModalLong', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalLong.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalLong.stories').default, }, { - key: "ModalPortal", + key: 'ModalPortal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalPortal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalPortal.stories').default, }, { - key: "MultiContentModule", + key: 'MultiContentModule', getComponent: () => - require("@coinbase/cds-mobile/multi-content-module/__stories__/MultiContentModule.stories") + require('@coinbase/cds-mobile/multi-content-module/__stories__/MultiContentModule.stories') .default, }, { - key: "NavBarIconButton", + key: 'NavBarIconButton', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavBarIconButton.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavBarIconButton.stories').default, }, { - key: "NavigationSubtitle", + key: 'NavigationSubtitle', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationSubtitle.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationSubtitle.stories').default, }, { - key: "NavigationTitle", + key: 'NavigationTitle', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationTitle.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationTitle.stories').default, }, { - key: "NavigationTitleSelect", + key: 'NavigationTitleSelect', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationTitleSelect.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationTitleSelect.stories').default, }, { - key: "NudgeCard", - getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/NudgeCard.stories") - .default, + key: 'NudgeCard', + getComponent: () => require('@coinbase/cds-mobile/cards/__stories__/NudgeCard.stories').default, }, { - key: "Numpad", - getComponent: () => - require("@coinbase/cds-mobile/numpad/__stories__/Numpad.stories").default, + key: 'Numpad', + getComponent: () => require('@coinbase/cds-mobile/numpad/__stories__/Numpad.stories').default, }, { - key: "Overlay", + key: 'Overlay', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/Overlay.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/Overlay.stories').default, }, { - key: "PageFooter", - getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageFooter.stories") - .default, + key: 'PageFooter', + getComponent: () => require('@coinbase/cds-mobile/page/__stories__/PageFooter.stories').default, }, { - key: "PageFooterInPage", + key: 'PageFooterInPage', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageFooterInPage.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageFooterInPage.stories').default, }, { - key: "PageHeader", - getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeader.stories") - .default, + key: 'PageHeader', + getComponent: () => require('@coinbase/cds-mobile/page/__stories__/PageHeader.stories').default, }, { - key: "PageHeaderInErrorEmptyState", + key: 'PageHeaderInErrorEmptyState', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeaderInErrorEmptyState.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageHeaderInErrorEmptyState.stories').default, }, { - key: "PageHeaderInPage", + key: 'PageHeaderInPage', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeaderInPage.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageHeaderInPage.stories').default, }, { - key: "Palette", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Palette.stories") - .default, + key: 'Palette', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Palette.stories').default, }, { - key: "PatternDisclosureHighFrictionBenefit", + key: 'PatternDisclosureHighFrictionBenefit', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionBenefit.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionBenefit.stories') .default, }, { - key: "PatternDisclosureHighFrictionRisk", + key: 'PatternDisclosureHighFrictionRisk', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionRisk.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionRisk.stories') .default, }, { - key: "PatternDisclosureLowFriction", + key: 'PatternDisclosureLowFriction', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureLowFriction.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureLowFriction.stories') .default, }, { - key: "PatternDisclosureMedFriction", + key: 'PatternDisclosureMedFriction', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureMedFriction.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureMedFriction.stories') .default, }, { - key: "PatternError", + key: 'PatternError', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternError.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/PatternError.stories').default, }, { - key: "PeriodSelector", + key: 'PeriodSelector', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/PeriodSelector.stories") + require('@coinbase/cds-mobile-visualization/chart/__stories__/PeriodSelector.stories') .default, }, { - key: "Pictogram", + key: 'Pictogram', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/Pictogram.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/Pictogram.stories').default, }, { - key: "PolarChart", + key: 'PolarChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/PolarChart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/__stories__/PolarChart.stories').default, }, { - key: "Pressable", + key: 'Pressable', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Pressable.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/Pressable.stories').default, }, { - key: "PressableOpacity", + key: 'PressableOpacity', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PressableOpacity.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/PressableOpacity.stories').default, }, { - key: "ProgressBar", + key: 'ProgressBar', getComponent: () => - require("@coinbase/cds-mobile/visualizations/__stories__/ProgressBar.stories") - .default, + require('@coinbase/cds-mobile/visualizations/__stories__/ProgressBar.stories').default, }, { - key: "ProgressCircle", + key: 'ProgressCircle', getComponent: () => - require("@coinbase/cds-mobile/visualizations/__stories__/ProgressCircle.stories") - .default, + require('@coinbase/cds-mobile/visualizations/__stories__/ProgressCircle.stories').default, }, { - key: "RadioCell", + key: 'RadioCell', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/RadioCell.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/RadioCell.stories').default, }, { - key: "RadioGroup", + key: 'RadioGroup', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/RadioGroup.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/RadioGroup.stories').default, }, { - key: "ReferenceLine", + key: 'ReferenceLine', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/line/__stories__/ReferenceLine.stories") + require('@coinbase/cds-mobile-visualization/chart/line/__stories__/ReferenceLine.stories') .default, }, { - key: "RemoteImage", + key: 'RemoteImage', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/RemoteImage.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/RemoteImage.stories').default, }, { - key: "RemoteImageGroup", + key: 'RemoteImageGroup', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/RemoteImageGroup.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/RemoteImageGroup.stories').default, }, { - key: "RollingNumber", + key: 'RollingNumber', getComponent: () => - require("@coinbase/cds-mobile/numbers/__stories__/RollingNumber.stories") - .default, + require('@coinbase/cds-mobile/numbers/__stories__/RollingNumber.stories').default, }, { - key: "SearchInput", + key: 'SearchInput', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/SearchInput.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/SearchInput.stories').default, }, { - key: "SectionHeader", + key: 'SectionHeader', getComponent: () => - require("@coinbase/cds-mobile/section-header/__stories__/SectionHeader.stories") - .default, + require('@coinbase/cds-mobile/section-header/__stories__/SectionHeader.stories').default, }, { - key: "SegmentedTabs", + key: 'SegmentedTabs', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/SegmentedTabs.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/SegmentedTabs.stories').default, }, { - key: "Select", - getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Select.stories") - .default, + key: 'Select', + getComponent: () => require('@coinbase/cds-mobile/controls/__stories__/Select.stories').default, }, { - key: "SelectChip", + key: 'SelectChip', getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/SelectChip.stories") - .default, + require('@coinbase/cds-mobile/chips/__stories__/SelectChip.stories').default, }, { - key: "SelectOption", + key: 'SelectOption', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/SelectOption.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/SelectOption.stories').default, }, { - key: "SlideButton", + key: 'SlideButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/SlideButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/SlideButton.stories').default, }, { - key: "Spacer", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Spacer.stories").default, + key: 'Spacer', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Spacer.stories').default, }, { - key: "Sparkline", + key: 'Sparkline', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/__stories__/Sparkline.stories") - .default, + require('@coinbase/cds-mobile-visualization/sparkline/__stories__/Sparkline.stories').default, }, { - key: "SparklineGradient", + key: 'SparklineGradient', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/__stories__/SparklineGradient.stories") + require('@coinbase/cds-mobile-visualization/sparkline/__stories__/SparklineGradient.stories') .default, }, { - key: "SparklineInteractive", + key: 'SparklineInteractive', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories") + require('@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories') .default, }, { - key: "SparklineInteractiveHeader", + key: 'SparklineInteractiveHeader', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories") + require('@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories') .default, }, { - key: "Spectrum", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Spectrum.stories") - .default, + key: 'Spectrum', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Spectrum.stories').default, }, { - key: "Spinner", - getComponent: () => - require("@coinbase/cds-mobile/loaders/__stories__/Spinner.stories") - .default, + key: 'Spinner', + getComponent: () => require('@coinbase/cds-mobile/loaders/__stories__/Spinner.stories').default, }, { - key: "SpotIcon", + key: 'SpotIcon', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotIcon.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotIcon.stories').default, }, { - key: "SpotRectangle", + key: 'SpotRectangle', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotRectangle.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotRectangle.stories').default, }, { - key: "SpotSquare", + key: 'SpotSquare', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotSquare.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotSquare.stories').default, }, { - key: "StepperHorizontal", + key: 'StepperHorizontal', getComponent: () => - require("@coinbase/cds-mobile/stepper/__stories__/StepperHorizontal.stories") - .default, + require('@coinbase/cds-mobile/stepper/__stories__/StepperHorizontal.stories').default, }, { - key: "StepperVertical", + key: 'StepperVertical', getComponent: () => - require("@coinbase/cds-mobile/stepper/__stories__/StepperVertical.stories") - .default, + require('@coinbase/cds-mobile/stepper/__stories__/StepperVertical.stories').default, }, { - key: "StickyFooter", + key: 'StickyFooter', getComponent: () => - require("@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooter.stories") - .default, + require('@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooter.stories').default, }, { - key: "StickyFooterWithTray", + key: 'StickyFooterWithTray', getComponent: () => - require("@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooterWithTray.stories") + require('@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooterWithTray.stories') .default, }, { - key: "Switch", - getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Switch.stories") - .default, + key: 'Switch', + getComponent: () => require('@coinbase/cds-mobile/controls/__stories__/Switch.stories').default, }, { - key: "TabbedChips", + key: 'TabbedChips', getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/TabbedChips.stories") - .default, + require('@coinbase/cds-mobile/chips/__stories__/TabbedChips.stories').default, }, { - key: "TabIndicator", + key: 'TabIndicator', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabIndicator.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/TabIndicator.stories').default, }, { - key: "TabLabel", - getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabLabel.stories").default, + key: 'TabLabel', + getComponent: () => require('@coinbase/cds-mobile/tabs/__stories__/TabLabel.stories').default, }, { - key: "TabNavigation", + key: 'TabNavigation', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabNavigation.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/TabNavigation.stories').default, }, { - key: "Tabs", - getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/Tabs.stories").default, + key: 'Tabs', + getComponent: () => require('@coinbase/cds-mobile/tabs/__stories__/Tabs.stories').default, }, { - key: "Tag", - getComponent: () => - require("@coinbase/cds-mobile/tag/__stories__/Tag.stories").default, + key: 'Tag', + getComponent: () => require('@coinbase/cds-mobile/tag/__stories__/Tag.stories').default, }, { - key: "Text", - getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/Text.stories") - .default, + key: 'Text', + getComponent: () => require('@coinbase/cds-mobile/typography/__stories__/Text.stories').default, }, { - key: "TextBody", + key: 'TextBody', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextBody.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextBody.stories').default, }, { - key: "TextCaption", + key: 'TextCaption', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextCaption.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextCaption.stories').default, }, { - key: "TextCore", + key: 'TextCore', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextCore.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextCore.stories').default, }, { - key: "TextDisplay1", + key: 'TextDisplay1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay1.stories').default, }, { - key: "TextDisplay2", + key: 'TextDisplay2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay2.stories').default, }, { - key: "TextDisplay3", + key: 'TextDisplay3', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay3.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay3.stories').default, }, { - key: "TextHeadline", + key: 'TextHeadline', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextHeadline.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextHeadline.stories').default, }, { - key: "TextInput", + key: 'TextInput', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/TextInput.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/TextInput.stories').default, }, { - key: "TextLabel1", + key: 'TextLabel1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLabel1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLabel1.stories').default, }, { - key: "TextLabel2", + key: 'TextLabel2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLabel2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLabel2.stories').default, }, { - key: "TextLegal", + key: 'TextLegal', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLegal.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLegal.stories').default, }, { - key: "TextTitle1", + key: 'TextTitle1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle1.stories').default, }, { - key: "TextTitle2", + key: 'TextTitle2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle2.stories').default, }, { - key: "TextTitle3", + key: 'TextTitle3', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle3.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle3.stories').default, }, { - key: "TextTitle4", + key: 'TextTitle4', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle4.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle4.stories').default, }, { - key: "ThemeProvider", + key: 'ThemeProvider', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/ThemeProvider.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/ThemeProvider.stories').default, }, { - key: "Toast", - getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/Toast.stories") - .default, + key: 'Toast', + getComponent: () => require('@coinbase/cds-mobile/overlays/__stories__/Toast.stories').default, }, { - key: "TooltipV2", + key: 'TooltipV2', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TooltipV2.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TooltipV2.stories').default, }, { - key: "TopNavBar", + key: 'TopNavBar', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/TopNavBar.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/TopNavBar.stories').default, }, { - key: "Tour", - getComponent: () => - require("@coinbase/cds-mobile/tour/__stories__/Tour.stories").default, + key: 'Tour', + getComponent: () => require('@coinbase/cds-mobile/tour/__stories__/Tour.stories').default, }, { - key: "TrayAction", + key: 'TrayAction', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayAction.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayAction.stories').default, }, { - key: "TrayBasic", + key: 'TrayBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayBasic.stories').default, }, { - key: "TrayFallback", + key: 'TrayFallback', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayFallback.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayFallback.stories').default, }, { - key: "TrayFeedCard", + key: 'TrayFeedCard', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayFeedCard.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayFeedCard.stories').default, }, { - key: "TrayInformational", + key: 'TrayInformational', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayInformational.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayInformational.stories').default, }, { - key: "TrayMessaging", + key: 'TrayMessaging', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayMessaging.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayMessaging.stories').default, }, { - key: "TrayMisc", + key: 'TrayMisc', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayMisc.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayMisc.stories').default, }, { - key: "TrayNavigation", + key: 'TrayNavigation', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayNavigation.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayNavigation.stories').default, }, { - key: "TrayPromotional", + key: 'TrayPromotional', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayPromotional.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayPromotional.stories').default, }, { - key: "TrayScrollable", + key: 'TrayScrollable', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayScrollable.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayScrollable.stories').default, }, { - key: "TrayTall", + key: 'TrayTall', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayTall.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayTall.stories').default, }, { - key: "TrayWithTitle", + key: 'TrayWithTitle', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayWithTitle.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayWithTitle.stories').default, }, { - key: "UpsellCard", + key: 'UpsellCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/UpsellCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/UpsellCard.stories').default, }, ]; diff --git a/packages/ui-mobile-visreg/src/routes.ts b/packages/ui-mobile-visreg/src/routes.ts index 6cb1369655..0b3492e3a1 100644 --- a/packages/ui-mobile-visreg/src/routes.ts +++ b/packages/ui-mobile-visreg/src/routes.ts @@ -4,1008 +4,835 @@ */ export const routes = [ { - key: "Accordion", + key: 'Accordion', getComponent: () => - require("@coinbase/cds-mobile/accordion/__stories__/Accordion.stories") - .default, + require('@coinbase/cds-mobile/accordion/__stories__/Accordion.stories').default, }, { - key: "AlertBasic", + key: 'AlertBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertBasic.stories').default, }, { - key: "AlertLongTitle", + key: 'AlertLongTitle', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertLongTitle.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertLongTitle.stories').default, }, { - key: "AlertOverModal", + key: 'AlertOverModal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertOverModal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertOverModal.stories').default, }, { - key: "AlertPortal", + key: 'AlertPortal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertPortal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertPortal.stories').default, }, { - key: "AlertSingleAction", + key: 'AlertSingleAction', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertSingleAction.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertSingleAction.stories').default, }, { - key: "AlertVerticalActions", + key: 'AlertVerticalActions', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/AlertVerticalActions.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/AlertVerticalActions.stories').default, }, { - key: "AlphaSelect", + key: 'AlphaSelect', getComponent: () => - require("@coinbase/cds-mobile/alpha/select/__stories__/AlphaSelect.stories") - .default, + require('@coinbase/cds-mobile/alpha/select/__stories__/AlphaSelect.stories').default, }, { - key: "AlphaSelectChip", + key: 'AlphaSelectChip', getComponent: () => - require("@coinbase/cds-mobile/alpha/select-chip/__stories__/AlphaSelectChip.stories") - .default, + require('@coinbase/cds-mobile/alpha/select-chip/__stories__/AlphaSelectChip.stories').default, }, { - key: "AlphaTabbedChips", + key: 'AlphaTabbedChips', getComponent: () => - require("@coinbase/cds-mobile/alpha/tabbed-chips/__stories__/AlphaTabbedChips.stories") + require('@coinbase/cds-mobile/alpha/tabbed-chips/__stories__/AlphaTabbedChips.stories') .default, }, { - key: "AnimatedCaret", + key: 'AnimatedCaret', getComponent: () => - require("@coinbase/cds-mobile/motion/__stories__/AnimatedCaret.stories") - .default, + require('@coinbase/cds-mobile/motion/__stories__/AnimatedCaret.stories').default, }, { - key: "AreaChart", + key: 'AreaChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/area/__stories__/AreaChart.stories") + require('@coinbase/cds-mobile-visualization/chart/area/__stories__/AreaChart.stories') .default, }, { - key: "Avatar", - getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/Avatar.stories").default, + key: 'Avatar', + getComponent: () => require('@coinbase/cds-mobile/media/__stories__/Avatar.stories').default, }, { - key: "AvatarButton", + key: 'AvatarButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/AvatarButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/AvatarButton.stories').default, }, { - key: "Axis", + key: 'Axis', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/axis/__stories__/Axis.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/axis/__stories__/Axis.stories').default, }, { - key: "Banner", - getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/Banner.stories").default, + key: 'Banner', + getComponent: () => require('@coinbase/cds-mobile/banner/__stories__/Banner.stories').default, }, { - key: "BannerActions", + key: 'BannerActions', getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/BannerActions.stories") - .default, + require('@coinbase/cds-mobile/banner/__stories__/BannerActions.stories').default, }, { - key: "BannerLayout", + key: 'BannerLayout', getComponent: () => - require("@coinbase/cds-mobile/banner/__stories__/BannerLayout.stories") - .default, + require('@coinbase/cds-mobile/banner/__stories__/BannerLayout.stories').default, }, { - key: "BarChart", + key: 'BarChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/bar/__stories__/BarChart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/bar/__stories__/BarChart.stories').default, }, { - key: "Box", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Box.stories").default, + key: 'Box', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Box.stories').default, }, { - key: "BrowserBar", + key: 'BrowserBar', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/BrowserBar.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/BrowserBar.stories').default, }, { - key: "BrowserBarSearchInput", + key: 'BrowserBarSearchInput', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/BrowserBarSearchInput.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/BrowserBarSearchInput.stories').default, }, { - key: "Button", - getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/Button.stories") - .default, + key: 'Button', + getComponent: () => require('@coinbase/cds-mobile/buttons/__stories__/Button.stories').default, }, { - key: "ButtonGroup", + key: 'ButtonGroup', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/ButtonGroup.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/ButtonGroup.stories').default, }, { - key: "Card", - getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/Card.stories").default, + key: 'Card', + getComponent: () => require('@coinbase/cds-mobile/cards/__stories__/Card.stories').default, }, { - key: "Carousel", + key: 'Carousel', getComponent: () => - require("@coinbase/cds-mobile/carousel/__stories__/Carousel.stories") - .default, + require('@coinbase/cds-mobile/carousel/__stories__/Carousel.stories').default, }, { - key: "CarouselMedia", + key: 'CarouselMedia', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/CarouselMedia.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/CarouselMedia.stories').default, }, { - key: "CartesianChart", + key: 'CartesianChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/CartesianChart.stories") + require('@coinbase/cds-mobile-visualization/chart/__stories__/CartesianChart.stories') .default, }, { - key: "Chart", + key: 'Chart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/Chart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/__stories__/Chart.stories').default, }, { - key: "Checkbox", + key: 'Checkbox', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Checkbox.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/Checkbox.stories').default, }, { - key: "CheckboxCell", + key: 'CheckboxCell', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/CheckboxCell.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/CheckboxCell.stories').default, }, { - key: "Chip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/Chip.stories").default, + key: 'Chip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/Chip.stories').default, }, { - key: "Coachmark", + key: 'Coachmark', getComponent: () => - require("@coinbase/cds-mobile/coachmark/__stories__/Coachmark.stories") - .default, + require('@coinbase/cds-mobile/coachmark/__stories__/Coachmark.stories').default, }, { - key: "Collapsible", + key: 'Collapsible', getComponent: () => - require("@coinbase/cds-mobile/collapsible/__stories__/Collapsible.stories") - .default, + require('@coinbase/cds-mobile/collapsible/__stories__/Collapsible.stories').default, }, { - key: "ContainedAssetCard", + key: 'ContainedAssetCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/ContainedAssetCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/ContainedAssetCard.stories').default, }, { - key: "ContentCard", + key: 'ContentCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/ContentCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/ContentCard.stories').default, }, { - key: "ContentCell", + key: 'ContentCell', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ContentCell.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ContentCell.stories').default, }, { - key: "ContentCellFallback", + key: 'ContentCellFallback', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ContentCellFallback.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ContentCellFallback.stories').default, }, { - key: "ControlGroup", + key: 'ControlGroup', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/ControlGroup.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/ControlGroup.stories').default, }, { - key: "DateInput", - getComponent: () => - require("@coinbase/cds-mobile/dates/__stories__/DateInput.stories") - .default, + key: 'DateInput', + getComponent: () => require('@coinbase/cds-mobile/dates/__stories__/DateInput.stories').default, }, { - key: "DatePicker", + key: 'DatePicker', getComponent: () => - require("@coinbase/cds-mobile/dates/__stories__/DatePicker.stories") - .default, + require('@coinbase/cds-mobile/dates/__stories__/DatePicker.stories').default, }, { - key: "Divider", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Divider.stories") - .default, + key: 'Divider', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Divider.stories').default, }, { - key: "Dot", - getComponent: () => - require("@coinbase/cds-mobile/dots/__stories__/Dot.stories").default, + key: 'Dot', + getComponent: () => require('@coinbase/cds-mobile/dots/__stories__/Dot.stories').default, }, { - key: "DotMisc", - getComponent: () => - require("@coinbase/cds-mobile/dots/__stories__/DotMisc.stories").default, + key: 'DotMisc', + getComponent: () => require('@coinbase/cds-mobile/dots/__stories__/DotMisc.stories').default, }, { - key: "DrawerBottom", + key: 'DrawerBottom', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerBottom.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerBottom.stories').default, }, { - key: "DrawerFallback", + key: 'DrawerFallback', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerFallback.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerFallback.stories').default, }, { - key: "DrawerLeft", + key: 'DrawerLeft', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerLeft.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerLeft.stories').default, }, { - key: "DrawerMisc", + key: 'DrawerMisc', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerMisc.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerMisc.stories').default, }, { - key: "DrawerRight", + key: 'DrawerRight', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerRight.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerRight.stories').default, }, { - key: "DrawerScrollable", + key: 'DrawerScrollable', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerScrollable.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerScrollable.stories').default, }, { - key: "DrawerTop", + key: 'DrawerTop', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/DrawerTop.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/DrawerTop.stories').default, }, { - key: "FloatingAssetCard", + key: 'FloatingAssetCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/FloatingAssetCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/FloatingAssetCard.stories').default, }, { - key: "Frontier", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Frontier.stories") - .default, + key: 'Frontier', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Frontier.stories').default, }, { - key: "Group", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Group.stories").default, + key: 'Group', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Group.stories').default, }, { - key: "HeroSquare", + key: 'HeroSquare', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/HeroSquare.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/HeroSquare.stories').default, }, { - key: "HintMotion", + key: 'HintMotion', getComponent: () => - require("@coinbase/cds-mobile/motion/__stories__/HintMotion.stories") - .default, + require('@coinbase/cds-mobile/motion/__stories__/HintMotion.stories').default, }, { - key: "IconButton", + key: 'IconButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/IconButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/IconButton.stories').default, }, { - key: "IconCounterButton", + key: 'IconCounterButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/IconCounterButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/IconCounterButton.stories').default, }, { - key: "InputChip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/InputChip.stories") - .default, + key: 'InputChip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/InputChip.stories').default, }, { - key: "InputIcon", + key: 'InputIcon', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputIcon.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputIcon.stories').default, }, { - key: "InputIconButton", + key: 'InputIconButton', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputIconButton.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputIconButton.stories').default, }, { - key: "InputStack", + key: 'InputStack', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/InputStack.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/InputStack.stories').default, }, { - key: "Legend", + key: 'Legend', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/legend/__stories__/Legend.stories').default, }, { - key: "LinearGradient", + key: 'LinearGradient', getComponent: () => - require("@coinbase/cds-mobile/gradients/__stories__/LinearGradient.stories") - .default, + require('@coinbase/cds-mobile/gradients/__stories__/LinearGradient.stories').default, }, { - key: "LineChart", + key: 'LineChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/line/__stories__/LineChart.stories") + require('@coinbase/cds-mobile-visualization/chart/line/__stories__/LineChart.stories') .default, }, { - key: "Link", - getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/Link.stories") - .default, + key: 'Link', + getComponent: () => require('@coinbase/cds-mobile/typography/__stories__/Link.stories').default, }, { - key: "ListCell", - getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ListCell.stories") - .default, + key: 'ListCell', + getComponent: () => require('@coinbase/cds-mobile/cells/__stories__/ListCell.stories').default, }, { - key: "ListCellFallback", + key: 'ListCellFallback', getComponent: () => - require("@coinbase/cds-mobile/cells/__stories__/ListCellFallback.stories") - .default, + require('@coinbase/cds-mobile/cells/__stories__/ListCellFallback.stories').default, }, { - key: "Logo", - getComponent: () => - require("@coinbase/cds-mobile/icons/__stories__/Logo.stories").default, + key: 'Logo', + getComponent: () => require('@coinbase/cds-mobile/icons/__stories__/Logo.stories').default, }, { - key: "Lottie", + key: 'Lottie', getComponent: () => - require("@coinbase/cds-mobile/animation/__stories__/Lottie.stories") - .default, + require('@coinbase/cds-mobile/animation/__stories__/Lottie.stories').default, }, { - key: "LottieStatusAnimation", + key: 'LottieStatusAnimation', getComponent: () => - require("@coinbase/cds-mobile/animation/__stories__/LottieStatusAnimation.stories") - .default, + require('@coinbase/cds-mobile/animation/__stories__/LottieStatusAnimation.stories').default, }, { - key: "MediaChip", - getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/MediaChip.stories") - .default, + key: 'MediaChip', + getComponent: () => require('@coinbase/cds-mobile/chips/__stories__/MediaChip.stories').default, }, { - key: "ModalBackButton", + key: 'ModalBackButton', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalBackButton.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalBackButton.stories').default, }, { - key: "ModalBasic", + key: 'ModalBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalBasic.stories').default, }, { - key: "ModalLong", + key: 'ModalLong', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalLong.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalLong.stories').default, }, { - key: "ModalPortal", + key: 'ModalPortal', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/ModalPortal.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/ModalPortal.stories').default, }, { - key: "MultiContentModule", + key: 'MultiContentModule', getComponent: () => - require("@coinbase/cds-mobile/multi-content-module/__stories__/MultiContentModule.stories") + require('@coinbase/cds-mobile/multi-content-module/__stories__/MultiContentModule.stories') .default, }, { - key: "NavBarIconButton", + key: 'NavBarIconButton', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavBarIconButton.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavBarIconButton.stories').default, }, { - key: "NavigationSubtitle", + key: 'NavigationSubtitle', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationSubtitle.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationSubtitle.stories').default, }, { - key: "NavigationTitle", + key: 'NavigationTitle', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationTitle.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationTitle.stories').default, }, { - key: "NavigationTitleSelect", + key: 'NavigationTitleSelect', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/NavigationTitleSelect.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/NavigationTitleSelect.stories').default, }, { - key: "NudgeCard", - getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/NudgeCard.stories") - .default, + key: 'NudgeCard', + getComponent: () => require('@coinbase/cds-mobile/cards/__stories__/NudgeCard.stories').default, }, { - key: "Numpad", - getComponent: () => - require("@coinbase/cds-mobile/numpad/__stories__/Numpad.stories").default, + key: 'Numpad', + getComponent: () => require('@coinbase/cds-mobile/numpad/__stories__/Numpad.stories').default, }, { - key: "Overlay", + key: 'Overlay', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/Overlay.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/Overlay.stories').default, }, { - key: "PageFooter", - getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageFooter.stories") - .default, + key: 'PageFooter', + getComponent: () => require('@coinbase/cds-mobile/page/__stories__/PageFooter.stories').default, }, { - key: "PageFooterInPage", + key: 'PageFooterInPage', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageFooterInPage.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageFooterInPage.stories').default, }, { - key: "PageHeader", - getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeader.stories") - .default, + key: 'PageHeader', + getComponent: () => require('@coinbase/cds-mobile/page/__stories__/PageHeader.stories').default, }, { - key: "PageHeaderInErrorEmptyState", + key: 'PageHeaderInErrorEmptyState', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeaderInErrorEmptyState.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageHeaderInErrorEmptyState.stories').default, }, { - key: "PageHeaderInPage", + key: 'PageHeaderInPage', getComponent: () => - require("@coinbase/cds-mobile/page/__stories__/PageHeaderInPage.stories") - .default, + require('@coinbase/cds-mobile/page/__stories__/PageHeaderInPage.stories').default, }, { - key: "Palette", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Palette.stories") - .default, + key: 'Palette', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Palette.stories').default, }, { - key: "PatternDisclosureHighFrictionBenefit", + key: 'PatternDisclosureHighFrictionBenefit', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionBenefit.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionBenefit.stories') .default, }, { - key: "PatternDisclosureHighFrictionRisk", + key: 'PatternDisclosureHighFrictionRisk', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionRisk.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureHighFrictionRisk.stories') .default, }, { - key: "PatternDisclosureLowFriction", + key: 'PatternDisclosureLowFriction', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureLowFriction.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureLowFriction.stories') .default, }, { - key: "PatternDisclosureMedFriction", + key: 'PatternDisclosureMedFriction', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternDisclosureMedFriction.stories") + require('@coinbase/cds-mobile/system/__stories__/PatternDisclosureMedFriction.stories') .default, }, { - key: "PatternError", + key: 'PatternError', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PatternError.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/PatternError.stories').default, }, { - key: "PeriodSelector", + key: 'PeriodSelector', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/PeriodSelector.stories") + require('@coinbase/cds-mobile-visualization/chart/__stories__/PeriodSelector.stories') .default, }, { - key: "Pictogram", + key: 'Pictogram', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/Pictogram.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/Pictogram.stories').default, }, { - key: "PolarChart", + key: 'PolarChart', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/__stories__/PolarChart.stories") - .default, + require('@coinbase/cds-mobile-visualization/chart/__stories__/PolarChart.stories').default, }, { - key: "Pressable", + key: 'Pressable', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Pressable.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/Pressable.stories').default, }, { - key: "PressableOpacity", + key: 'PressableOpacity', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/PressableOpacity.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/PressableOpacity.stories').default, }, { - key: "ProgressBar", + key: 'ProgressBar', getComponent: () => - require("@coinbase/cds-mobile/visualizations/__stories__/ProgressBar.stories") - .default, + require('@coinbase/cds-mobile/visualizations/__stories__/ProgressBar.stories').default, }, { - key: "ProgressCircle", + key: 'ProgressCircle', getComponent: () => - require("@coinbase/cds-mobile/visualizations/__stories__/ProgressCircle.stories") - .default, + require('@coinbase/cds-mobile/visualizations/__stories__/ProgressCircle.stories').default, }, { - key: "RadioCell", + key: 'RadioCell', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/RadioCell.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/RadioCell.stories').default, }, { - key: "RadioGroup", + key: 'RadioGroup', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/RadioGroup.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/RadioGroup.stories').default, }, { - key: "ReferenceLine", + key: 'ReferenceLine', getComponent: () => - require("@coinbase/cds-mobile-visualization/chart/line/__stories__/ReferenceLine.stories") + require('@coinbase/cds-mobile-visualization/chart/line/__stories__/ReferenceLine.stories') .default, }, { - key: "RemoteImage", + key: 'RemoteImage', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/RemoteImage.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/RemoteImage.stories').default, }, { - key: "RemoteImageGroup", + key: 'RemoteImageGroup', getComponent: () => - require("@coinbase/cds-mobile/media/__stories__/RemoteImageGroup.stories") - .default, + require('@coinbase/cds-mobile/media/__stories__/RemoteImageGroup.stories').default, }, { - key: "RollingNumber", + key: 'RollingNumber', getComponent: () => - require("@coinbase/cds-mobile/numbers/__stories__/RollingNumber.stories") - .default, + require('@coinbase/cds-mobile/numbers/__stories__/RollingNumber.stories').default, }, { - key: "SearchInput", + key: 'SearchInput', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/SearchInput.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/SearchInput.stories').default, }, { - key: "SectionHeader", + key: 'SectionHeader', getComponent: () => - require("@coinbase/cds-mobile/section-header/__stories__/SectionHeader.stories") - .default, + require('@coinbase/cds-mobile/section-header/__stories__/SectionHeader.stories').default, }, { - key: "SegmentedTabs", + key: 'SegmentedTabs', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/SegmentedTabs.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/SegmentedTabs.stories').default, }, { - key: "Select", - getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Select.stories") - .default, + key: 'Select', + getComponent: () => require('@coinbase/cds-mobile/controls/__stories__/Select.stories').default, }, { - key: "SelectChip", + key: 'SelectChip', getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/SelectChip.stories") - .default, + require('@coinbase/cds-mobile/chips/__stories__/SelectChip.stories').default, }, { - key: "SelectOption", + key: 'SelectOption', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/SelectOption.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/SelectOption.stories').default, }, { - key: "SlideButton", + key: 'SlideButton', getComponent: () => - require("@coinbase/cds-mobile/buttons/__stories__/SlideButton.stories") - .default, + require('@coinbase/cds-mobile/buttons/__stories__/SlideButton.stories').default, }, { - key: "Spacer", - getComponent: () => - require("@coinbase/cds-mobile/layout/__stories__/Spacer.stories").default, + key: 'Spacer', + getComponent: () => require('@coinbase/cds-mobile/layout/__stories__/Spacer.stories').default, }, { - key: "Sparkline", + key: 'Sparkline', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/__stories__/Sparkline.stories") - .default, + require('@coinbase/cds-mobile-visualization/sparkline/__stories__/Sparkline.stories').default, }, { - key: "SparklineGradient", + key: 'SparklineGradient', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/__stories__/SparklineGradient.stories") + require('@coinbase/cds-mobile-visualization/sparkline/__stories__/SparklineGradient.stories') .default, }, { - key: "SparklineInteractive", + key: 'SparklineInteractive', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories") + require('@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories') .default, }, { - key: "SparklineInteractiveHeader", + key: 'SparklineInteractiveHeader', getComponent: () => - require("@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories") + require('@coinbase/cds-mobile-visualization/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories') .default, }, { - key: "Spectrum", - getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/Spectrum.stories") - .default, + key: 'Spectrum', + getComponent: () => require('@coinbase/cds-mobile/system/__stories__/Spectrum.stories').default, }, { - key: "Spinner", - getComponent: () => - require("@coinbase/cds-mobile/loaders/__stories__/Spinner.stories") - .default, + key: 'Spinner', + getComponent: () => require('@coinbase/cds-mobile/loaders/__stories__/Spinner.stories').default, }, { - key: "SpotIcon", + key: 'SpotIcon', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotIcon.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotIcon.stories').default, }, { - key: "SpotRectangle", + key: 'SpotRectangle', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotRectangle.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotRectangle.stories').default, }, { - key: "SpotSquare", + key: 'SpotSquare', getComponent: () => - require("@coinbase/cds-mobile/illustrations/__stories__/SpotSquare.stories") - .default, + require('@coinbase/cds-mobile/illustrations/__stories__/SpotSquare.stories').default, }, { - key: "StepperHorizontal", + key: 'StepperHorizontal', getComponent: () => - require("@coinbase/cds-mobile/stepper/__stories__/StepperHorizontal.stories") - .default, + require('@coinbase/cds-mobile/stepper/__stories__/StepperHorizontal.stories').default, }, { - key: "StepperVertical", + key: 'StepperVertical', getComponent: () => - require("@coinbase/cds-mobile/stepper/__stories__/StepperVertical.stories") - .default, + require('@coinbase/cds-mobile/stepper/__stories__/StepperVertical.stories').default, }, { - key: "StickyFooter", + key: 'StickyFooter', getComponent: () => - require("@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooter.stories") - .default, + require('@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooter.stories').default, }, { - key: "StickyFooterWithTray", + key: 'StickyFooterWithTray', getComponent: () => - require("@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooterWithTray.stories") + require('@coinbase/cds-mobile/sticky-footer/__stories__/StickyFooterWithTray.stories') .default, }, { - key: "Switch", - getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/Switch.stories") - .default, + key: 'Switch', + getComponent: () => require('@coinbase/cds-mobile/controls/__stories__/Switch.stories').default, }, { - key: "TabbedChips", + key: 'TabbedChips', getComponent: () => - require("@coinbase/cds-mobile/chips/__stories__/TabbedChips.stories") - .default, + require('@coinbase/cds-mobile/chips/__stories__/TabbedChips.stories').default, }, { - key: "TabIndicator", + key: 'TabIndicator', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabIndicator.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/TabIndicator.stories').default, }, { - key: "TabLabel", - getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabLabel.stories").default, + key: 'TabLabel', + getComponent: () => require('@coinbase/cds-mobile/tabs/__stories__/TabLabel.stories').default, }, { - key: "TabNavigation", + key: 'TabNavigation', getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/TabNavigation.stories") - .default, + require('@coinbase/cds-mobile/tabs/__stories__/TabNavigation.stories').default, }, { - key: "Tabs", - getComponent: () => - require("@coinbase/cds-mobile/tabs/__stories__/Tabs.stories").default, + key: 'Tabs', + getComponent: () => require('@coinbase/cds-mobile/tabs/__stories__/Tabs.stories').default, }, { - key: "Tag", - getComponent: () => - require("@coinbase/cds-mobile/tag/__stories__/Tag.stories").default, + key: 'Tag', + getComponent: () => require('@coinbase/cds-mobile/tag/__stories__/Tag.stories').default, }, { - key: "Text", - getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/Text.stories") - .default, + key: 'Text', + getComponent: () => require('@coinbase/cds-mobile/typography/__stories__/Text.stories').default, }, { - key: "TextBody", + key: 'TextBody', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextBody.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextBody.stories').default, }, { - key: "TextCaption", + key: 'TextCaption', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextCaption.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextCaption.stories').default, }, { - key: "TextCore", + key: 'TextCore', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextCore.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextCore.stories').default, }, { - key: "TextDisplay1", + key: 'TextDisplay1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay1.stories').default, }, { - key: "TextDisplay2", + key: 'TextDisplay2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay2.stories').default, }, { - key: "TextDisplay3", + key: 'TextDisplay3', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextDisplay3.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextDisplay3.stories').default, }, { - key: "TextHeadline", + key: 'TextHeadline', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextHeadline.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextHeadline.stories').default, }, { - key: "TextInput", + key: 'TextInput', getComponent: () => - require("@coinbase/cds-mobile/controls/__stories__/TextInput.stories") - .default, + require('@coinbase/cds-mobile/controls/__stories__/TextInput.stories').default, }, { - key: "TextLabel1", + key: 'TextLabel1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLabel1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLabel1.stories').default, }, { - key: "TextLabel2", + key: 'TextLabel2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLabel2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLabel2.stories').default, }, { - key: "TextLegal", + key: 'TextLegal', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextLegal.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextLegal.stories').default, }, { - key: "TextTitle1", + key: 'TextTitle1', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle1.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle1.stories').default, }, { - key: "TextTitle2", + key: 'TextTitle2', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle2.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle2.stories').default, }, { - key: "TextTitle3", + key: 'TextTitle3', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle3.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle3.stories').default, }, { - key: "TextTitle4", + key: 'TextTitle4', getComponent: () => - require("@coinbase/cds-mobile/typography/__stories__/TextTitle4.stories") - .default, + require('@coinbase/cds-mobile/typography/__stories__/TextTitle4.stories').default, }, { - key: "ThemeProvider", + key: 'ThemeProvider', getComponent: () => - require("@coinbase/cds-mobile/system/__stories__/ThemeProvider.stories") - .default, + require('@coinbase/cds-mobile/system/__stories__/ThemeProvider.stories').default, }, { - key: "Toast", - getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/Toast.stories") - .default, + key: 'Toast', + getComponent: () => require('@coinbase/cds-mobile/overlays/__stories__/Toast.stories').default, }, { - key: "TooltipV2", + key: 'TooltipV2', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TooltipV2.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TooltipV2.stories').default, }, { - key: "TopNavBar", + key: 'TopNavBar', getComponent: () => - require("@coinbase/cds-mobile/navigation/__stories__/TopNavBar.stories") - .default, + require('@coinbase/cds-mobile/navigation/__stories__/TopNavBar.stories').default, }, { - key: "Tour", - getComponent: () => - require("@coinbase/cds-mobile/tour/__stories__/Tour.stories").default, + key: 'Tour', + getComponent: () => require('@coinbase/cds-mobile/tour/__stories__/Tour.stories').default, }, { - key: "TrayAction", + key: 'TrayAction', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayAction.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayAction.stories').default, }, { - key: "TrayBasic", + key: 'TrayBasic', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayBasic.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayBasic.stories').default, }, { - key: "TrayFallback", + key: 'TrayFallback', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayFallback.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayFallback.stories').default, }, { - key: "TrayFeedCard", + key: 'TrayFeedCard', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayFeedCard.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayFeedCard.stories').default, }, { - key: "TrayInformational", + key: 'TrayInformational', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayInformational.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayInformational.stories').default, }, { - key: "TrayMessaging", + key: 'TrayMessaging', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayMessaging.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayMessaging.stories').default, }, { - key: "TrayMisc", + key: 'TrayMisc', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayMisc.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayMisc.stories').default, }, { - key: "TrayNavigation", + key: 'TrayNavigation', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayNavigation.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayNavigation.stories').default, }, { - key: "TrayPromotional", + key: 'TrayPromotional', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayPromotional.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayPromotional.stories').default, }, { - key: "TrayScrollable", + key: 'TrayScrollable', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayScrollable.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayScrollable.stories').default, }, { - key: "TrayTall", + key: 'TrayTall', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayTall.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayTall.stories').default, }, { - key: "TrayWithTitle", + key: 'TrayWithTitle', getComponent: () => - require("@coinbase/cds-mobile/overlays/__stories__/TrayWithTitle.stories") - .default, + require('@coinbase/cds-mobile/overlays/__stories__/TrayWithTitle.stories').default, }, { - key: "UpsellCard", + key: 'UpsellCard', getComponent: () => - require("@coinbase/cds-mobile/cards/__stories__/UpsellCard.stories") - .default, + require('@coinbase/cds-mobile/cards/__stories__/UpsellCard.stories').default, }, ]; diff --git a/packages/web-visualization/src/chart/bar/BarChart.tsx b/packages/web-visualization/src/chart/bar/BarChart.tsx index 44336c05a6..0370fe8f81 100644 --- a/packages/web-visualization/src/chart/bar/BarChart.tsx +++ b/packages/web-visualization/src/chart/bar/BarChart.tsx @@ -6,7 +6,12 @@ import { type CartesianChartBaseProps, type CartesianChartProps, } from '../CartesianChart'; -import { type CartesianAxisConfigProps, defaultChartInset, defaultStackId, getChartInset } from '../utils'; +import { + type CartesianAxisConfigProps, + defaultChartInset, + defaultStackId, + getChartInset, +} from '../utils'; import { BarPlot, type BarPlotProps } from './BarPlot'; import type { BarSeries } from './BarStack'; From 312920ba87565b43186dce6789857f6316529b3a Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Tue, 9 Dec 2025 11:02:06 -0500 Subject: [PATCH 11/13] Fix legend import --- .../components/graphs/Legend/_webExamples.mdx | 139 +++++++++++------- packages/web-visualization/src/chart/index.ts | 1 + .../legend/__stories__/Legend.stories.tsx | 99 ++++++------- .../src/chart/legend/{index.tsx => index.ts} | 0 4 files changed, 130 insertions(+), 109 deletions(-) rename packages/web-visualization/src/chart/legend/{index.tsx => index.ts} (100%) diff --git a/apps/docs/docs/components/graphs/Legend/_webExamples.mdx b/apps/docs/docs/components/graphs/Legend/_webExamples.mdx index 5c5d9e9f77..2c97726e68 100644 --- a/apps/docs/docs/components/graphs/Legend/_webExamples.mdx +++ b/apps/docs/docs/components/graphs/Legend/_webExamples.mdx @@ -140,7 +140,7 @@ function ShapeVariants() { showXAxis showYAxis height={{ base: 200, tablet: 250, desktop: 300 }} - legend={} + legend legendPosition="left" series={[ { @@ -230,7 +230,7 @@ function LayoutDirection() { } + legend legendPosition="right" series={series} /> @@ -319,7 +319,7 @@ function CustomLegendItem() { showYAxis height={{ base: 200, tablet: 250, desktop: 300 }} legend={} - legendPosition="bottom" + legendPosition="top" series={series} xAxis={{ data: timeLabels, @@ -441,7 +441,7 @@ function InteractiveLegend() { showYAxis height={{ base: 300, tablet: 350, desktop: 400 }} legend={} - legendPosition="bottom" + legendPosition="top" series={series} xAxis={{ data: months, @@ -508,7 +508,7 @@ function DonutChartLegend() { You can pass a custom ReactNode as `legendShape` for fully custom indicators. ```jsx live -function CustomLegendShapes() { +function CustomLegendShapes () { const months = [ 'Jan', 'Feb', @@ -531,13 +531,16 @@ function CustomLegendShapes() { const forecastRevenue = [null, null, null, null, null, null, null, null, null, 580, 620, 680]; const numberFormatter = useCallback( - (value) => `$${new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value)}k`, + (value: number) => + `$${new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value)}k`, [], ); // Pattern settings for dotted fill const patternSize = 4; const dotSize = 1; + const patternId = useId(); + const maskId = useId(); const legendPatternId = useId(); // Custom legend indicator that matches the dotted bar pattern @@ -572,55 +575,81 @@ function CustomLegendShapes() { ); + // Custom bar component that renders bars with dotted pattern fill + const DottedBarComponent = memo((props) => { + const { dataX, x, y } = props; + // Create unique IDs per bar so patterns are scoped to each bar + const uniqueMaskId = `${maskId}-${dataX}`; + const uniquePatternId = `${patternId}-${dataX}`; + return ( + <> + + {/* Pattern positioned relative to this bar's origin */} + + + + + + + + + + + + + ); + }); + return ( - - - Annual Revenue - - - - - - - + ); -} +}; ``` diff --git a/packages/web-visualization/src/chart/index.ts b/packages/web-visualization/src/chart/index.ts index e4c321a585..6a029ecb33 100644 --- a/packages/web-visualization/src/chart/index.ts +++ b/packages/web-visualization/src/chart/index.ts @@ -7,6 +7,7 @@ export * from './ChartProvider'; export * from './ChartTooltip'; export * from './DonutChart'; export * from './gradient/index'; +export * from './legend/index'; export * from './line/index'; export * from './Path'; export * from './PeriodSelector'; diff --git a/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx b/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx index b38aafeba5..c54303ed6b 100644 --- a/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx +++ b/packages/web-visualization/src/chart/legend/__stories__/Legend.stories.tsx @@ -4,7 +4,7 @@ import { Box, HStack, VStack } from '@coinbase/cds-web/layout'; import { Text } from '@coinbase/cds-web/typography'; import { XAxis, YAxis } from '../../axis'; -import { type BarComponentProps, BarPlot, type BarSeries, DefaultBar } from '../../bar'; +import { BarChart, type BarComponentProps, BarPlot, type BarSeries, DefaultBar } from '../../bar'; import { CartesianChart } from '../../CartesianChart'; import { useCartesianChartContext } from '../../ChartProvider'; import { ChartTooltip } from '../../ChartTooltip'; @@ -689,7 +689,7 @@ const LegendShapes = () => { ); // Custom bar component that renders bars with dotted pattern fill - const DottedBarComponent = memo(function DottedBarComponent(props) { + const DottedBarComponent = memo((props) => { const { dataX, x, y } = props; // Create unique IDs per bar so patterns are scoped to each bar const uniqueMaskId = `${maskId}-${dataX}`; @@ -721,58 +721,49 @@ const LegendShapes = () => { }); return ( - - - - Annual Revenue - - - - - - - + + ); }; diff --git a/packages/web-visualization/src/chart/legend/index.tsx b/packages/web-visualization/src/chart/legend/index.ts similarity index 100% rename from packages/web-visualization/src/chart/legend/index.tsx rename to packages/web-visualization/src/chart/legend/index.ts From d80452721b52d78787a2a37dc19c3dfbddc8dbcb Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Tue, 9 Dec 2025 14:54:02 -0500 Subject: [PATCH 12/13] Clean up chart tooltip --- .../src/chart/ChartTooltip.tsx | 220 ++++++------------ .../src/chart/DefaultChartTooltipItem.tsx | 95 ++++++++ packages/web-visualization/src/chart/index.ts | 1 + .../src/chart/legend/DefaultLegendItem.tsx | 2 +- 4 files changed, 165 insertions(+), 153 deletions(-) create mode 100644 packages/web-visualization/src/chart/DefaultChartTooltipItem.tsx diff --git a/packages/web-visualization/src/chart/ChartTooltip.tsx b/packages/web-visualization/src/chart/ChartTooltip.tsx index 88d14bc634..da7dd531c3 100644 --- a/packages/web-visualization/src/chart/ChartTooltip.tsx +++ b/packages/web-visualization/src/chart/ChartTooltip.tsx @@ -1,24 +1,22 @@ -import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; -import { Box, Divider, HStack, VStack, type VStackProps } from '@coinbase/cds-web/layout'; +import React, { useEffect, useMemo } from 'react'; +import { + Divider, + VStack, + type VStackBaseProps, + type VStackDefaultElement, + type VStackProps, +} from '@coinbase/cds-web/layout'; import { Portal } from '@coinbase/cds-web/overlays/Portal'; import { tooltipContainerId } from '@coinbase/cds-web/overlays/PortalProvider'; import { Text } from '@coinbase/cds-web/typography'; import { flip, offset, shift, useFloating, type VirtualElement } from '@floating-ui/react-dom'; -import { css } from '@linaria/core'; -import { DefaultLegendShape } from './legend/DefaultLegendShape'; - -const legendMediaWrapperCss = css` - height: 24px; - display: flex; - align-items: center; - justify-content: center; -`; -import type { LegendShape } from './utils/chart'; +import type { LegendShapeComponent } from './legend/DefaultLegendShape'; import { useCartesianChartContext } from './ChartProvider'; +import { type ChartTooltipItemComponent, DefaultChartTooltipItem } from './DefaultChartTooltipItem'; import { useScrubberContext } from './utils'; -export type ChartTooltipProps = VStackProps<'div'> & { +export type ChartTooltipBaseProps = VStackBaseProps & { /** * Label text displayed at the top of the tooltip. * Can be a static string, a custom ReactNode, or a function that receives the current dataIndex. @@ -37,20 +35,38 @@ export type ChartTooltipProps = VStackProps<'div'> & { * String results will automatically be wrapped in Text with font="label2". */ valueFormatter?: (value: number) => React.ReactNode; + /** + * Custom component to render each tooltip item. + * @default DefaultChartTooltipItem + */ + ItemComponent?: ChartTooltipItemComponent; + /** + * Custom component to render the legend shape within each item. + * Only used when ItemComponent is DefaultChartTooltipItem. + * @default DefaultLegendShape + */ + ShapeComponent?: LegendShapeComponent; }; +export type ChartTooltipProps = VStackProps & ChartTooltipBaseProps; + export const ChartTooltip = ({ label, seriesIds, valueFormatter, + ItemComponent = DefaultChartTooltipItem, + ShapeComponent, + background = 'bgElevation2', + borderRadius = 400, + elevation = 2, gap = 1, minWidth = 320, + paddingX = 2, + paddingY = 1.5, ...props }: ChartTooltipProps) => { - const { ref, series, getSeriesData, getXAxis } = useCartesianChartContext(); + const { ref, series, getXAxis } = useCartesianChartContext(); const { scrubberPosition, enableScrubbing } = useScrubberContext(); - const [legendMediaWidth, setLegendMediaWidth] = useState(0); - const legendMediaRefs = useRef>(new Map()); const isTooltipVisible = enableScrubbing && scrubberPosition !== undefined; @@ -99,163 +115,63 @@ export const ChartTooltip = ({ return () => element.removeEventListener('mousemove', handleMouseMove); }, [enableScrubbing, refs, ref]); - const { resolvedLabel, seriesItems } = useMemo(() => { - if (scrubberPosition === undefined) { - return { resolvedLabel: null, seriesItems: [] as TooltipSeriesItem[] }; - } + const filteredSeries = useMemo(() => { + if (seriesIds === undefined) return series; + return series.filter((s) => seriesIds.includes(s.id)); + }, [series, seriesIds]); - // Resolve label - let resolvedLabel: React.ReactNode; + const resolvedLabel = useMemo(() => { + if (scrubberPosition === undefined) return; + + let resolved: React.ReactNode; if (label !== undefined) { - resolvedLabel = typeof label === 'function' ? label(scrubberPosition) : label; + resolved = typeof label === 'function' ? label(scrubberPosition) : label; } else { // Default to x-axis data value const xAxis = getXAxis(); if (xAxis?.data && xAxis.data[scrubberPosition] !== undefined) { - resolvedLabel = xAxis.data[scrubberPosition]; + resolved = xAxis.data[scrubberPosition]; } else { - resolvedLabel = scrubberPosition; - } - } - - // Wrap string label in Text - if (typeof resolvedLabel === 'string' || typeof resolvedLabel === 'number') { - resolvedLabel = {resolvedLabel}; - } - - // Filter series - const filteredSeries = seriesIds ? series.filter((s) => seriesIds.includes(s.id)) : series; - - // Resolve series data - const seriesItems: TooltipSeriesItem[] = []; - filteredSeries.forEach((s) => { - const data = getSeriesData(s.id); - const dataPoint = data?.[scrubberPosition]; - let value: number | undefined; - - if (dataPoint && dataPoint !== null) { - const [start, end] = dataPoint; - value = end - start; - } else if (s.data) { - const rawPoint = s.data[scrubberPosition]; - if (rawPoint !== undefined && rawPoint !== null) { - value = Array.isArray(rawPoint) ? (rawPoint[1] ?? undefined) : (rawPoint as number); - } - } - - if (value === undefined || value === null || Number.isNaN(value)) return; - - let formattedValue: React.ReactNode = value; - if (valueFormatter) { - formattedValue = valueFormatter(value); + resolved = scrubberPosition; } - - if (formattedValue === null || formattedValue === undefined) { - return; - } - - if (typeof formattedValue === 'string' || typeof formattedValue === 'number') { - formattedValue = {formattedValue}; - } - - seriesItems.push({ - id: s.id, - label: s.label, - color: s.color, - shape: s.legendShape, - value: formattedValue, - }); - }); - - return { - resolvedLabel: resolvedLabel ?? null, - seriesItems, - }; - }, [scrubberPosition, label, seriesIds, series, getSeriesData, getXAxis, valueFormatter]); - - // Measure legend media widths and find the maximum - useLayoutEffect(() => { - if (seriesItems.length === 0) { - setLegendMediaWidth(0); - return; - } - - let maxWidth = 0; - legendMediaRefs.current.forEach((el) => { - if (el) { - const width = el.scrollWidth; - if (width > maxWidth) { - maxWidth = width; - } - } - }); - - if (maxWidth !== legendMediaWidth) { - setLegendMediaWidth(maxWidth); } - }, [seriesItems, legendMediaWidth]); + return resolved; + }, [scrubberPosition, label, getXAxis]); - // Create a ref callback for each legend media item - const setLegendMediaRef = useCallback( - (id: string) => (el: HTMLDivElement | null) => { - if (el) { - legendMediaRefs.current.set(id, el); - } else { - legendMediaRefs.current.delete(id); - } - }, - [], - ); - - if (!isTooltipVisible || (!resolvedLabel && seriesItems.length === 0)) { - return null; - } + if (!isTooltipVisible) return; return ( - {resolvedLabel} - - {seriesItems.length > 0 && ( - - {seriesItems.map((item) => ( - - - 0 ? { width: legendMediaWidth } : undefined} - > - - - {item.label ?? item.id} - - {item.value} - - ))} - - )} + {resolvedLabel && + (typeof resolvedLabel === 'string' || typeof resolvedLabel === 'number' ? ( + {resolvedLabel} + ) : ( + resolvedLabel + ))} + {resolvedLabel && filteredSeries.length > 0 && } + {filteredSeries.length > 0 && + filteredSeries.map((s) => ( + + ))} ); }; - -type TooltipSeriesItem = { - id: string; - label?: string; - color?: string; - shape?: LegendShape; - value: React.ReactNode; -}; diff --git a/packages/web-visualization/src/chart/DefaultChartTooltipItem.tsx b/packages/web-visualization/src/chart/DefaultChartTooltipItem.tsx new file mode 100644 index 0000000000..0235356b2c --- /dev/null +++ b/packages/web-visualization/src/chart/DefaultChartTooltipItem.tsx @@ -0,0 +1,95 @@ +import { memo, useMemo } from 'react'; +import type { SharedProps } from '@coinbase/cds-common/types'; +import { HStack } from '@coinbase/cds-web/layout'; +import { Text } from '@coinbase/cds-web/typography'; + +import { DefaultLegendItem, type LegendItemComponent } from './legend/DefaultLegendItem'; +import type { LegendShapeComponent } from './legend/DefaultLegendShape'; +import { useCartesianChartContext } from './ChartProvider'; +import type { CartesianSeries } from './utils'; + +export type ChartTooltipItemBaseProps = SharedProps & { + /** + * The series to display. + */ + series: CartesianSeries; + /** + * The current scrubber position (data index). + */ + scrubberPosition: number; + /** + * Formatter function for series values. + * Receives the numeric series value and should return a ReactNode. + * String results will automatically be wrapped in Text. + */ + valueFormatter?: (value: number) => React.ReactNode; + /** + * Custom component to render the legend item (shape + label). + * @default DefaultLegendItem + */ + LegendItemComponent?: LegendItemComponent; + /** + * Custom component to render the legend shape. + * Only used when LegendItemComponent is DefaultLegendItem. + * @default DefaultLegendShape + */ + ShapeComponent?: LegendShapeComponent; +}; + +export type ChartTooltipItemProps = ChartTooltipItemBaseProps; + +export type ChartTooltipItemComponent = React.FC; + +export const DefaultChartTooltipItem = memo( + ({ + series, + scrubberPosition, + valueFormatter, + LegendItemComponent = DefaultLegendItem, + ShapeComponent, + testID, + }) => { + const { getSeriesData } = useCartesianChartContext(); + + const formattedValue: React.ReactNode = useMemo(() => { + const data = getSeriesData(series.id); + const dataPoint = data?.[scrubberPosition]; + let value: number | undefined; + + if (dataPoint && dataPoint !== null) { + const [start, end] = dataPoint; + value = end - start; + } else if (series.data) { + const rawPoint = series.data[scrubberPosition]; + if (rawPoint !== undefined && rawPoint !== null) { + value = Array.isArray(rawPoint) ? rawPoint.at(-1) : rawPoint; + } + } + + if (value === undefined || value === null || Number.isNaN(value)) return; + + return valueFormatter ? valueFormatter(value) : value; + }, [series.id, series.data, scrubberPosition, getSeriesData, valueFormatter]); + + if (formattedValue === undefined) return; + + return ( + + + {typeof formattedValue === 'string' || typeof formattedValue === 'number' ? ( + + {formattedValue} + + ) : ( + formattedValue + )} + + ); + }, +); diff --git a/packages/web-visualization/src/chart/index.ts b/packages/web-visualization/src/chart/index.ts index 6a029ecb33..d88bf4e2aa 100644 --- a/packages/web-visualization/src/chart/index.ts +++ b/packages/web-visualization/src/chart/index.ts @@ -5,6 +5,7 @@ export * from './bar/index'; export * from './CartesianChart'; export * from './ChartProvider'; export * from './ChartTooltip'; +export * from './DefaultChartTooltipItem'; export * from './DonutChart'; export * from './gradient/index'; export * from './legend/index'; diff --git a/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx b/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx index e2535a4cb6..1b227916a5 100644 --- a/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx +++ b/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx @@ -135,7 +135,7 @@ export const DefaultLegendItem = memo( /> {isStringLabel ? ( - + {label} ) : ( From e0fb5b92aeedac831fb2c0c1452a0cba5a5e59c4 Mon Sep 17 00:00:00 2001 From: Hunter Copp Date: Tue, 9 Dec 2025 19:20:43 -0500 Subject: [PATCH 13/13] Add classNames and styles --- .../src/chart/legend/DefaultLegendItem.tsx | 7 +- .../src/chart/legend/Legend.tsx | 41 ++++- .../src/chart/ChartTooltip.tsx | 121 +++++++++++++- .../src/chart/DefaultChartTooltipItem.tsx | 158 ++++++++++++++---- .../src/chart/legend/DefaultLegendItem.tsx | 16 +- .../src/chart/legend/Legend.tsx | 74 +++++++- 6 files changed, 368 insertions(+), 49 deletions(-) diff --git a/packages/mobile-visualization/src/chart/legend/DefaultLegendItem.tsx b/packages/mobile-visualization/src/chart/legend/DefaultLegendItem.tsx index 728a35182c..12c8a6b109 100644 --- a/packages/mobile-visualization/src/chart/legend/DefaultLegendItem.tsx +++ b/packages/mobile-visualization/src/chart/legend/DefaultLegendItem.tsx @@ -66,9 +66,10 @@ export type LegendItemProps = LegendItemBaseProps & { */ shape?: StyleProp; /** - * Custom styles for the text element. + * Custom styles for the label element. + * @note not applied when label is a ReactNode. */ - text?: StyleProp; + label?: StyleProp; }; }; @@ -96,7 +97,7 @@ export const DefaultLegendItem = memo(function DefaultLegendIte {typeof label === 'string' ? ( - {label} + {label} ) : ( label )} diff --git a/packages/mobile-visualization/src/chart/legend/Legend.tsx b/packages/mobile-visualization/src/chart/legend/Legend.tsx index 45959b6b4e..c9fc11d4ef 100644 --- a/packages/mobile-visualization/src/chart/legend/Legend.tsx +++ b/packages/mobile-visualization/src/chart/legend/Legend.tsx @@ -1,5 +1,5 @@ import { forwardRef, memo, useMemo } from 'react'; -import type { View } from 'react-native'; +import type { StyleProp, View, ViewStyle } from 'react-native'; import type { SharedProps } from '@coinbase/cds-common/types'; import { Box, type BoxProps } from '@coinbase/cds-mobile/layout'; @@ -27,7 +27,35 @@ export type LegendBaseProps = SharedProps & { ShapeComponent?: LegendShapeComponent; }; -export type LegendProps = BoxProps & LegendBaseProps; +export type LegendProps = BoxProps & + LegendBaseProps & { + /** + * Custom styles for the component parts. + */ + styles?: { + /** + * Custom styles for the root element. + */ + root?: StyleProp; + /** + * Custom styles for each item element. + */ + item?: StyleProp; + /** + * Custom styles for the shape wrapper element within each item. + */ + itemShapeWrapper?: StyleProp; + /** + * Custom styles for the shape element within each item. + */ + itemShape?: StyleProp; + /** + * Custom styles for the label element within each item. + * @note not applied when label is a ReactNode. + */ + itemLabel?: StyleProp; + }; + }; export const Legend = memo( forwardRef(function Legend( @@ -40,6 +68,8 @@ export const Legend = memo( seriesIds, ItemComponent = DefaultLegendItem, ShapeComponent, + style, + styles, ...props }, ref, @@ -61,6 +91,7 @@ export const Legend = memo( flexWrap={flexWrap} gap={gap} justifyContent={justifyContent} + style={[style, styles?.root]} {...props} > {filteredSeries.map((s) => ( @@ -71,6 +102,12 @@ export const Legend = memo( label={s.label ?? s.id} seriesId={s.id} shape={s.legendShape} + styles={{ + root: styles?.item, + shapeWrapper: styles?.itemShapeWrapper, + shape: styles?.itemShape, + label: styles?.itemLabel, + }} /> ))} diff --git a/packages/web-visualization/src/chart/ChartTooltip.tsx b/packages/web-visualization/src/chart/ChartTooltip.tsx index da7dd531c3..eef34d80dd 100644 --- a/packages/web-visualization/src/chart/ChartTooltip.tsx +++ b/packages/web-visualization/src/chart/ChartTooltip.tsx @@ -48,7 +48,95 @@ export type ChartTooltipBaseProps = VStackBaseProps & { ShapeComponent?: LegendShapeComponent; }; -export type ChartTooltipProps = VStackProps & ChartTooltipBaseProps; +export type ChartTooltipProps = VStackProps & + ChartTooltipBaseProps & { + /** + * Custom class names for the component parts. + */ + classNames?: { + /** + * Custom class name for the root element. + */ + root?: string; + /** + * Custom class name for the label element. + * @note not applied when label is a ReactNode. + */ + label?: string; + /** + * Custom class name for the divider element. + */ + divider?: string; + /** + * Custom class name for each item element. + */ + item?: string; + /** + * Custom class name for the legend item element within each item. + */ + itemLegendItem?: string; + /** + * Custom class name for the value element within each item. + */ + itemValue?: string; + /** + * Custom class name for the shape wrapper element within each item. + */ + itemShapeWrapper?: string; + /** + * Custom class name for the shape element within each item. + */ + itemShape?: string; + /** + * Custom class name for the label element within each item. + * @note not applied when label is a ReactNode. + */ + itemLabel?: string; + }; + /** + * Custom styles for the component parts. + */ + styles?: { + /** + * Custom styles for the root element. + */ + root?: React.CSSProperties; + /** + * Custom styles for the label element. + * @note not applied when label is a ReactNode. + */ + label?: React.CSSProperties; + /** + * Custom styles for the divider element. + */ + divider?: React.CSSProperties; + /** + * Custom styles for each item element. + */ + item?: React.CSSProperties; + /** + * Custom styles for the legend item element within each item. + */ + itemLegendItem?: React.CSSProperties; + /** + * Custom styles for the value element within each item. + */ + itemValue?: React.CSSProperties; + /** + * Custom styles for the shape wrapper element within each item. + */ + itemShapeWrapper?: React.CSSProperties; + /** + * Custom styles for the shape element within each item. + */ + itemShape?: React.CSSProperties; + /** + * Custom styles for the label element within each item. + * @note not applied when label is a ReactNode. + */ + itemLabel?: React.CSSProperties; + }; + }; export const ChartTooltip = ({ label, @@ -63,6 +151,10 @@ export const ChartTooltip = ({ minWidth = 320, paddingX = 2, paddingY = 1.5, + className, + classNames, + style, + styles, ...props }: ChartTooltipProps) => { const { ref, series, getXAxis } = useCartesianChartContext(); @@ -146,28 +238,49 @@ export const ChartTooltip = ({ ref={refs.setFloating} background={background} borderRadius={borderRadius} + className={classNames?.root ?? className} elevation={elevation} gap={gap} minWidth={minWidth} paddingX={paddingX} paddingY={paddingY} - style={floatingStyles} + style={{ ...floatingStyles, ...style, ...styles?.root }} {...props} > {resolvedLabel && (typeof resolvedLabel === 'string' || typeof resolvedLabel === 'number' ? ( - {resolvedLabel} + + {resolvedLabel} + ) : ( resolvedLabel ))} - {resolvedLabel && filteredSeries.length > 0 && } + {resolvedLabel && filteredSeries.length > 0 && ( + + )} {filteredSeries.length > 0 && filteredSeries.map((s) => ( ))} diff --git a/packages/web-visualization/src/chart/DefaultChartTooltipItem.tsx b/packages/web-visualization/src/chart/DefaultChartTooltipItem.tsx index 0235356b2c..6ee4460952 100644 --- a/packages/web-visualization/src/chart/DefaultChartTooltipItem.tsx +++ b/packages/web-visualization/src/chart/DefaultChartTooltipItem.tsx @@ -1,6 +1,11 @@ import { memo, useMemo } from 'react'; import type { SharedProps } from '@coinbase/cds-common/types'; -import { HStack } from '@coinbase/cds-web/layout'; +import { + HStack, + type HStackBaseProps, + type HStackDefaultElement, + type HStackProps, +} from '@coinbase/cds-web/layout'; import { Text } from '@coinbase/cds-web/typography'; import { DefaultLegendItem, type LegendItemComponent } from './legend/DefaultLegendItem'; @@ -8,46 +13,118 @@ import type { LegendShapeComponent } from './legend/DefaultLegendShape'; import { useCartesianChartContext } from './ChartProvider'; import type { CartesianSeries } from './utils'; -export type ChartTooltipItemBaseProps = SharedProps & { - /** - * The series to display. - */ - series: CartesianSeries; - /** - * The current scrubber position (data index). - */ - scrubberPosition: number; - /** - * Formatter function for series values. - * Receives the numeric series value and should return a ReactNode. - * String results will automatically be wrapped in Text. - */ - valueFormatter?: (value: number) => React.ReactNode; - /** - * Custom component to render the legend item (shape + label). - * @default DefaultLegendItem - */ - LegendItemComponent?: LegendItemComponent; - /** - * Custom component to render the legend shape. - * Only used when LegendItemComponent is DefaultLegendItem. - * @default DefaultLegendShape - */ - ShapeComponent?: LegendShapeComponent; -}; +export type ChartTooltipItemBaseProps = Omit & + SharedProps & { + /** + * The series to display. + */ + series: CartesianSeries; + /** + * The current scrubber position (data index). + */ + scrubberPosition: number; + /** + * Formatter function for series values. + * Receives the numeric series value and should return a ReactNode. + * String results will automatically be wrapped in Text. + */ + valueFormatter?: (value: number) => React.ReactNode; + /** + * Custom component to render the legend item (shape + label). + * @default DefaultLegendItem + */ + LegendItemComponent?: LegendItemComponent; + /** + * Custom component to render the legend shape. + * Only used when LegendItemComponent is DefaultLegendItem. + * @default DefaultLegendShape + */ + ShapeComponent?: LegendShapeComponent; + }; -export type ChartTooltipItemProps = ChartTooltipItemBaseProps; +export type ChartTooltipItemProps = Omit, 'children'> & + ChartTooltipItemBaseProps & { + /** + * Custom class names for the component parts. + */ + classNames?: { + /** + * Custom class name for the root element. + */ + root?: string; + /** + * Custom class name for the legend item element. + */ + legendItem?: string; + /** + * Custom class name for the value element. + * @note not applied when value is a ReactNode. + */ + value?: string; + /** + * Custom class name for the shape wrapper element. + */ + shapeWrapper?: string; + /** + * Custom class name for the shape element. + */ + shape?: string; + /** + * Custom class name for the label element. + * @note not applied when label is a ReactNode. + */ + label?: string; + }; + /** + * Custom styles for the component parts. + */ + styles?: { + /** + * Custom styles for the root element. + */ + root?: React.CSSProperties; + /** + * Custom styles for the legend item element. + */ + legendItem?: React.CSSProperties; + /** + * Custom styles for the value element. + * @note not applied when value is a ReactNode. + */ + value?: React.CSSProperties; + /** + * Custom styles for the shape wrapper element. + */ + shapeWrapper?: React.CSSProperties; + /** + * Custom styles for the shape element. + */ + shape?: React.CSSProperties; + /** + * Custom styles for the label element. + * @note not applied when label is a ReactNode. + */ + label?: React.CSSProperties; + }; + }; export type ChartTooltipItemComponent = React.FC; export const DefaultChartTooltipItem = memo( ({ + alignItems = 'center', + justifyContent = 'space-between', series, scrubberPosition, valueFormatter, LegendItemComponent = DefaultLegendItem, ShapeComponent, + className, + classNames, + style, + styles, testID, + ...props }) => { const { getSeriesData } = useCartesianChartContext(); @@ -74,16 +151,35 @@ export const DefaultChartTooltipItem = memo( if (formattedValue === undefined) return; return ( - + {typeof formattedValue === 'string' || typeof formattedValue === 'number' ? ( - + {formattedValue} ) : ( diff --git a/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx b/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx index 1b227916a5..450f7d9e50 100644 --- a/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx +++ b/packages/web-visualization/src/chart/legend/DefaultLegendItem.tsx @@ -73,9 +73,10 @@ export type LegendItemProps = Omit, 'children' */ shape?: string; /** - * Custom class name for the text element. + * Custom class name for the label element. + * @note not applied when label is a ReactNode. */ - text?: string; + label?: string; }; /** * Custom styles for the component parts. @@ -94,9 +95,10 @@ export type LegendItemProps = Omit, 'children' */ shape?: React.CSSProperties; /** - * Custom styles for the text element. + * Custom styles for the label element. + * @note not applied when label is a ReactNode. */ - text?: React.CSSProperties; + label?: React.CSSProperties; }; }; @@ -116,8 +118,6 @@ export const DefaultLegendItem = memo( testID, ...props }: LegendItemProps) => { - const isStringLabel = typeof label === 'string'; - return ( - {isStringLabel ? ( - + {typeof label === 'string' ? ( + {label} ) : ( diff --git a/packages/web-visualization/src/chart/legend/Legend.tsx b/packages/web-visualization/src/chart/legend/Legend.tsx index 281ae5e1ce..95fdf5527f 100644 --- a/packages/web-visualization/src/chart/legend/Legend.tsx +++ b/packages/web-visualization/src/chart/legend/Legend.tsx @@ -30,7 +30,61 @@ export type LegendBaseProps = BoxBaseProps & { ShapeComponent?: LegendShapeComponent; }; -export type LegendProps = BoxProps & LegendBaseProps; +export type LegendProps = BoxProps & + LegendBaseProps & { + /** + * Custom class names for the component parts. + */ + classNames?: { + /** + * Custom class name for the root element. + */ + root?: string; + /** + * Custom class name for each item element. + */ + item?: string; + /** + * Custom class name for the shape wrapper element within each item. + */ + itemShapeWrapper?: string; + /** + * Custom class name for the shape element within each item. + */ + itemShape?: string; + /** + * Custom class name for the label element within each item. + * @note not applied when label is a ReactNode. + */ + itemLabel?: string; + }; + /** + * Custom styles for the component parts. + */ + styles?: { + /** + * Custom styles for the root element. + */ + root?: React.CSSProperties; + /** + * Custom styles for each item element. + */ + item?: React.CSSProperties; + /** + * Custom styles for the shape wrapper element within each item. + */ + itemShapeWrapper?: React.CSSProperties; + /** + * Custom styles for the shape element within each item. + */ + itemShape?: React.CSSProperties; + /** + * Custom styles for the label element within each item. + * @note not applied when label is a ReactNode. + */ + itemLabel?: React.CSSProperties; + }; + }; export const Legend = memo( forwardRef( @@ -44,6 +98,10 @@ export const Legend = memo( seriesIds, ItemComponent = DefaultLegendItem, ShapeComponent, + className, + classNames, + style, + styles, ...props }, ref, @@ -61,20 +119,34 @@ export const Legend = memo( {filteredSeries.map((s) => ( ))}