refactor: reorganize Storybook into Kits, Patterns, and Experimental sections#3824
refactor: reorganize Storybook into Kits, Patterns, and Experimental sections#3824eliseo-juan wants to merge 8 commits intomainfrom
Conversation
✅ No New Circular DependenciesNo new circular dependencies detected. Current count: 0 |
📦 Alpha Package Version PublishedUse Use |
🔍 Visual review for your branch is published 🔍Here are the links to: |
There was a problem hiding this comment.
Pull request overview
Reorganizes @factorialco/f0-react Storybook navigation by introducing a new Kits top-level section (with chart code moved into src/kits/) and moving higher-level UI examples into Patterns, while keeping Components and Experimental separated and preserving backwards-compatible exports.
Changes:
- Add
kitsas a new build entry (src/kits.ts) and introducesrc/kits/*as the canonical location for chart-related code and stories. - Implement/relocate chart kits (Recharts charts, ECharts-based
F0DataChart, and WidgetCharts) and update Storybook titles/sorting to match the new navigation. - Keep legacy import paths working via
@deprecatedre-export barrels in previous locations.
Reviewed changes
Copilot reviewed 177 out of 177 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react/vite.config.ts | Adds kits entry to the library build. |
| packages/react/src/kits.ts | New public entrypoint exporting kits. |
| packages/react/src/kits/exports.ts | Barrel exporting Kits charts, F0DataChart, and WidgetCharts. |
| packages/react/src/kits/WidgetCharts/ChartContainer.tsx | Shared container wrapper for widget charts. |
| packages/react/src/kits/WidgetCharts/exports.tsx | Public WidgetCharts exports under Kits. |
| packages/react/src/kits/WidgetCharts/storybook-utils.tsx | Shared story args/decorator for widget stories. |
| packages/react/src/kits/WidgetCharts/AreaChartWidget/index.tsx | Kits widget wrapper for AreaChart. |
| packages/react/src/kits/WidgetCharts/AreaChartWidget/stories/AreaChartWidget.stories.tsx | Story under Kits/Widgets/.... |
| packages/react/src/kits/WidgetCharts/BarChartWidget/index.tsx | Kits widget wrapper for BarChart. |
| packages/react/src/kits/WidgetCharts/BarChartWidget/stories/BarChartWidget.stories.tsx | Story under Kits/Widgets/.... |
| packages/react/src/kits/WidgetCharts/LineChartWidget/index.tsx | Kits widget wrapper for LineChart. |
| packages/react/src/kits/WidgetCharts/LineChartWidget/stories/LineChartWidget.stories.tsx | Story under Kits/Widgets/.... |
| packages/react/src/kits/WidgetCharts/PieChartWidget/index.tsx | Kits widget wrapper for PieChart. |
| packages/react/src/kits/WidgetCharts/PieChartWidget/stories/PieChartWidget.stories.tsx | Story under Kits/Widgets/.... |
| packages/react/src/kits/WidgetCharts/VerticalBarChartWidget/index.tsx | Kits widget wrapper for VerticalBarChart. |
| packages/react/src/kits/WidgetCharts/VerticalBarChartWidget/stories/VerticalBarChartWidget.stories.tsx | Story under Kits/Widgets/.... |
| packages/react/src/kits/WidgetCharts/SummariesWidget/index.tsx | Adds summaries-only widget wrapper. |
| packages/react/src/kits/WidgetCharts/SummariesWidget/stories/SummariesWidget.stories.tsx | Story under Kits/Widgets/.... |
| packages/react/src/kits/WidgetCharts/RadialProgressWidget/index.tsx | Adds radial progress widget using chart kit. |
| packages/react/src/kits/WidgetCharts/RadialProgressWidget/stories/RadialProgressWidget.stories.tsx | Story under Kits/Widgets/.... |
| packages/react/src/kits/F0DataChart/index.ts | New Kits entry for F0DataChart exports/types. |
| packages/react/src/kits/F0DataChart/F0DataChart.tsx | Switch-based dispatcher for chart types. |
| packages/react/src/kits/F0DataChart/utils/colors.ts | New chart color token system + palette helpers. |
| packages/react/src/kits/F0DataChart/utils/formatters.ts | Adds formatting helpers (e.g., percent). |
| packages/react/src/kits/F0DataChart/utils/useEChartsInstance.ts | ECharts lifecycle management hook. |
| packages/react/src/kits/F0DataChart/utils/useLegendInteraction.ts | Plotly-like legend interactions. |
| packages/react/src/kits/F0DataChart/utils/useContainerSize.ts | ResizeObserver-based size tracking hook. |
| packages/react/src/kits/F0DataChart/utils/useChartTheme.ts | Dark-mode aware theme resolution hook. |
| packages/react/src/kits/F0DataChart/utils/useAxisLabelTooltip.ts | Overlay tooltip for canvas axis labels. |
| packages/react/src/kits/F0DataChart/components/BarChart/BarChart.tsx | ECharts Bar implementation wiring hooks. |
| packages/react/src/kits/F0DataChart/components/LineChart/LineChart.tsx | ECharts Line implementation wiring hooks. |
| packages/react/src/kits/F0DataChart/components/LineChart/useLineChartOptions.ts | Builds ECharts options for line charts. |
| packages/react/src/kits/F0DataChart/components/PieChart/PieChart.tsx | ECharts Pie implementation wiring hooks. |
| packages/react/src/kits/F0DataChart/components/PieChart/usePieChartOptions.ts | Builds ECharts options for pie charts. |
| packages/react/src/kits/F0DataChart/components/RadarChart/RadarChart.tsx | ECharts Radar implementation wiring hooks. |
| packages/react/src/kits/F0DataChart/components/RadarChart/useRadarChartOptions.ts | Builds ECharts options for radar charts. |
| packages/react/src/kits/F0DataChart/components/FunnelChart/FunnelChart.tsx | Funnel chart with overlay labels/conversion. |
| packages/react/src/kits/F0DataChart/components/GaugeChart/GaugeChart.tsx | ECharts gauge wiring. |
| packages/react/src/kits/F0DataChart/components/GaugeChart/useGaugeChartOptions.ts | Builds ECharts options for gauge charts. |
| packages/react/src/kits/F0DataChart/components/HeatmapChart/HeatmapChart.tsx | ECharts heatmap wiring. |
| packages/react/src/kits/F0DataChart/components/HeatmapChart/useHeatmapChartOptions.ts | Builds ECharts options for heatmap charts. |
| packages/react/src/kits/F0DataChart/tests/F0DataChart.test.tsx | Adds component smoke tests with ECharts mocked. |
| packages/react/src/kits/F0DataChart/tests/colors.test.ts | Adds unit tests for palette/color helpers. |
| packages/react/src/kits/F0DataChart/tests/formatters.test.ts | Adds unit tests for formatters. |
| packages/react/src/kits/F0DataChart/tests/options.test.ts | Adds unit tests for label interval helper. |
| packages/react/src/kits/F0DataChart/stories/decorators.tsx | Shared decorators for Kits F0DataChart stories. |
| packages/react/src/kits/F0DataChart/stories/Skeletons.stories.tsx | Kits story grouping for skeleton variants. |
| packages/react/src/kits/F0DataChart/stories/Bar.stories.tsx | Story moved under Kits/F0DataChart/.... |
| packages/react/src/kits/F0DataChart/stories/Line.stories.tsx | Story moved under Kits/F0DataChart/.... |
| packages/react/src/kits/F0DataChart/stories/Pie.stories.tsx | Story moved under Kits/F0DataChart/.... |
| packages/react/src/kits/F0DataChart/stories/Radar.stories.tsx | Story moved under Kits/F0DataChart/.... |
| packages/react/src/kits/F0DataChart/stories/Gauge.stories.tsx | Story moved under Kits/F0DataChart/.... |
| packages/react/src/kits/F0DataChart/stories/Heatmap.stories.tsx | Story moved under Kits/F0DataChart/.... |
| packages/react/src/kits/F0DataChart/stories/Funnel.stories.tsx | Story moved under Kits/F0DataChart/.... |
| packages/react/src/kits/Charts/utils/types.ts | Shared chart prop/types moved under Kits. |
| packages/react/src/kits/Charts/utils/muncher.ts | Shared data preparation helper. |
| packages/react/src/kits/Charts/utils/bar.ts | Vertical bar data preparation helper. |
| packages/react/src/kits/Charts/utils/colors.tsx | Shared chart color helpers. |
| packages/react/src/kits/Charts/utils/elements.tsx | Shared Recharts element prop builders. |
| packages/react/src/kits/Charts/utils/forwardRef.ts | Shared generic forwardRef helper. |
| packages/react/src/kits/Charts/exports.ts | Kits chart exports + metadata wrappers. |
| packages/react/src/kits/Charts/AreaChart/stories/AreaChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/BarChart/stories/BarChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/CategoryBarChart/index.tsx | Kits CategoryBarChart implementation. |
| packages/react/src/kits/Charts/CategoryBarChart/stories/CategoryBarChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/ComboChart/stories/ComboChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/LineChart/index.tsx | Kits LineChart implementation. |
| packages/react/src/kits/Charts/LineChart/stories/LineChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/PieChart/index.tsx | Kits PieChart implementation. |
| packages/react/src/kits/Charts/PieChart/stories/PieChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/ProgressChart/index.tsx | Kits progress bar chart implementation. |
| packages/react/src/kits/Charts/ProgressChart/stories/ProgressChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/ProgressBarDuo/index.tsx | Kits ProgressBarDuo implementation. |
| packages/react/src/kits/Charts/ProgressBarDuo/stories/ProgressBarDuo.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/RadialProgressChart/index.tsx | Kits RadialProgressChart implementation. |
| packages/react/src/kits/Charts/RadialProgressChart/stories/RadialProgressChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/RadarChart/index.tsx | Kits RadarChart implementation. |
| packages/react/src/kits/Charts/RadarChart/stories/RadarChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/VerticalBarChart/stories/VerticalBarChart.stories.tsx | Story moved under Kits/Charts/.... |
| packages/react/src/kits/Charts/F0Chart/index.ts | Kits F0Chart experimental wrapper. |
| packages/react/src/kits/Charts/F0Chart/F0Chart.tsx | Kits F0Chart implementation. |
| packages/react/src/kits/Charts/F0Chart/stories/decorators.tsx | Decorator for F0Chart stories under Kits. |
| packages/react/src/kits/Charts/F0Chart/stories/index.stories.tsx | Base F0Chart story under Kits. |
| packages/react/src/kits/Charts/F0Chart/stories/area.stories.tsx | Area example under Kits/Charts/F0Chart/.... |
| packages/react/src/kits/Charts/F0Chart/stories/bar.stories.tsx | Bar example under Kits/Charts/F0Chart/.... |
| packages/react/src/kits/Charts/F0Chart/stories/barHorizontal.stories.tsx | Horizontal bar example under Kits. |
| packages/react/src/kits/Charts/F0Chart/stories/funnel.stories.tsx | Funnel example under Kits. |
| packages/react/src/kits/Charts/F0Chart/stories/line.stories.tsx | Line example under Kits. |
| packages/react/src/kits/Charts/F0Chart/stories/pie.stories.tsx | Pie example under Kits. |
| packages/react/src/kits/Charts/F0Chart/stories/radar.stories.tsx | Radar example under Kits. |
| packages/react/src/experimental/Widgets/Charts/exports.tsx | Deprecated re-export pointing to Kits WidgetCharts. |
| packages/react/src/experimental/Widgets/Charts/AreaChartWidget/index.stories.tsx | Story title moved to Kits/Widgets/.... |
| packages/react/src/experimental/Widgets/Charts/BarChartWidget/index.stories.tsx | Story title moved to Kits/Widgets/.... |
| packages/react/src/experimental/Widgets/Charts/LineChartWidget/index.stories.tsx | Story title moved to Kits/Widgets/.... |
| packages/react/src/experimental/Widgets/Charts/PieChartWidget/index.stories.tsx | Story title moved to Kits/Widgets/.... |
| packages/react/src/experimental/Widgets/Charts/RadialProgressWidget/index.stories.tsx | Story title moved to Kits/Widgets/.... |
| packages/react/src/experimental/Widgets/Charts/SummariesWidget/index.stories.tsx | Story title moved to Kits/Widgets/.... |
| packages/react/src/experimental/Widgets/Charts/VerticalBarChartWidget/index.stories.tsx | Story title moved to Kits/Widgets/.... |
| packages/react/src/experimental/Widgets/Content/ProgressBarDuo/index.tsx | Deprecated re-export pointing to Kits ProgressBarDuo. |
| packages/react/src/experimental/Widgets/Content/ProgressBarDuo/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/experimental/Charts/exports.ts | Deprecated re-export pointing to Kits Charts RadarChart. |
| packages/react/src/experimental/Charts/RadarChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/exports.ts | Deprecated re-export pointing to Kits Charts exports. |
| packages/react/src/components/Charts/AreaChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/BarChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/CategoryBarChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/ComboChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/LineChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/PieChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/ProgressChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/RadialProgressChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/VerticalBarChart/index.stories.tsx | Story title moved to Kits/Charts/.... |
| packages/react/src/components/Charts/F0Chart/stories/decorators.tsx | Story group moved under Kits/Charts/F0Chart/.... |
| packages/react/src/components/Charts/F0Chart/stories/index.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/Charts/F0Chart/stories/area.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/Charts/F0Chart/stories/bar.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/Charts/F0Chart/stories/barHorizontal.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/Charts/F0Chart/stories/funnel.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/Charts/F0Chart/stories/line.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/Charts/F0Chart/stories/pie.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/Charts/F0Chart/stories/radar.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/F0DataChart/index.ts | Deprecated re-export pointing to Kits F0DataChart. |
| packages/react/src/components/F0DataChart/stories/decorators.tsx | Story group moved under Kits/F0DataChart/.... |
| packages/react/src/components/F0DataChart/stories/Skeletons.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/F0DataChart/stories/Bar.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/F0DataChart/stories/Line.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/F0DataChart/stories/Pie.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/F0DataChart/stories/Radar.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/F0DataChart/stories/Gauge.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/F0DataChart/stories/Heatmap.stories.tsx | Story title moved under Kits. |
| packages/react/src/components/F0DataChart/stories/Funnel.stories.tsx | Story title moved under Kits. |
| packages/react/src/experimental/OneDateNavigator/stories/OneDateNavigator.stories.tsx | Story moved under Patterns/DateNavigator. |
| packages/react/src/experimental/OneDataCollection/components/ActionBar/index.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/visualizations/collection/Table/components/SortAndHideList/stories/SortAndHideList.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/index.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/summary.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/grouping.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/full-height.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/empty-states.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/callbacks/callbacks.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/navigation-filters/navigation-filters.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/filters/filters.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/filters/per-visualization-filters.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/actions/item-actions.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/actions/collection-actions.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/total-items-summary/total-items-summary.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/temporary-deprecated/temporary-deprecated.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/visualizations/list/list.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/visualizations/table/table.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/visualizations/card/card.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/visualizations/editable-table/editable-table.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/OneDataCollection/stories/visualizations/kanban.stories.tsx | Story moved under Patterns/Data Collection/.... |
| packages/react/src/experimental/Navigation/ApplicationFrame/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/Carousel/index.stories.tsx | Story moved under Patterns/Navigation/Carousel/.... |
| packages/react/src/experimental/Navigation/Carousel/DynamicCarousel/index.stories.tsx | Story moved under Patterns/Navigation/Carousel/.... |
| packages/react/src/experimental/Navigation/DaytimePage/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/Dropdown/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/F0TableOfContent/stories/F0TableOfContent.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/Header/Breadcrumbs/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/Header/Breadcrumbs/internal/BreadcrumbSelect/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/Header/PageHeader/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/Page/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/Tabs/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/src/experimental/Navigation/VirtualList/index.stories.tsx | Story moved under Patterns/Navigation/.... |
| packages/react/.storybook/main.ts | Adds Kits story directory and renames experimental title prefix. |
| packages/react/.storybook/preview.tsx | Updates theme decorator implementation and Storybook top-level ordering. |
| packages/react/.oxlintrc.json | Exempts **/kits.ts from barrel-import restriction. |
| if (allWereVisible && wasVisible) { | ||
| // Isolate: show only the clicked one | ||
| const next: Record<string, boolean> = {} | ||
| for (const name of allNames) { | ||
| next[name] = name === clickedName | ||
| } | ||
| prevSelectedRef.current = next | ||
| chart!.dispatchAction({ type: "legendSelect", name: clickedName }) | ||
| for (const name of allNames) { | ||
| if (name !== clickedName) { | ||
| chart!.dispatchAction({ type: "legendUnSelect", name }) | ||
| } | ||
| } | ||
| return |
There was a problem hiding this comment.
useLegendInteraction updates prevSelectedRef.current and then calls chart.dispatchAction(...) inside the legendselectchanged handler. ECharts dispatch actions also emit legendselectchanged, so this can re-enter the handler with prevVisibleCount === 1 and immediately trigger the “show all” branch, breaking the intended isolate behavior (and potentially causing event loops). Consider suppressing events for internal actions (e.g., silent: true if supported) or using an isProgrammaticRef guard to ignore handler invocations caused by your own dispatches, and only update prevSelectedRef after the programmatic actions complete.
| ].join("; ") | ||
| container!.style.position = "relative" | ||
| container!.appendChild(overlay) | ||
| return overlay |
There was a problem hiding this comment.
useAxisLabelTooltip forcibly sets container.style.position = "relative" when creating the overlay but never restores the previous position value. This can inadvertently change layout/styling for consumers that rely on a different positioning context. Store the prior inline position value (and possibly only set it when empty) and restore it in the effect cleanup.
| const values = data.map((item) => item.value) | ||
| const sum = values.reduce((acc, value) => { | ||
| return acc + value | ||
| }) | ||
|
|
||
| if (sum === 0) { | ||
| preparedData.push({ | ||
| label: "-", | ||
| value: 1, | ||
| fill: "hsl(var(--neutral-2))", | ||
| }) | ||
| } | ||
|
|
||
| return ( | ||
| <ChartContainer | ||
| config={dataConfig} | ||
| ref={ref} | ||
| aspect={aspect} | ||
| data-chromatic="ignore" | ||
| style={{ height: 380 }} | ||
| > | ||
| <PieChartPrimitive accessibilityLayer margin={{ left: 0, right: 0 }}> | ||
| {sum !== 0 && ( | ||
| <ChartTooltip | ||
| isAnimationActive={false} | ||
| content={<ChartTooltipContent yAxisFormatter={tickFormatter} />} | ||
| /> | ||
| )} | ||
| <Pie | ||
| isAnimationActive={false} | ||
| nameKey={"label"} | ||
| legendType="circle" | ||
| dataKey={"value"} | ||
| data={preparedData} | ||
| innerRadius={120} | ||
| outerRadius={135} | ||
| paddingAngle={2.5} | ||
| > | ||
| {preparedData.map((entry, index) => { | ||
| const value = tickFormatter | ||
| ? tickFormatter(String(entry.value)) | ||
| : entry.value | ||
| return ( | ||
| <Cell | ||
| key={`cell-${index}`} | ||
| fill={entry.fill} | ||
| aria-label={`${entry.label}: ${value} (${((entry.value / sum) * 100).toFixed(0)}%)`} | ||
| /> | ||
| ) |
There was a problem hiding this comment.
In the zero-sum case, sum stays 0 but a placeholder slice is pushed to preparedData. The subsequent aria-label computation uses (entry.value / sum) * 100 and calls .toFixed(0) on a non-finite number, which will throw at runtime. Also, values.reduce(...) has no initial value, which will throw when data is empty. Use an initial accumulator for reduce, and compute percentages against a non-zero denominator (e.g., safeSum = sum === 0 ? 1 : sum) or branch the label formatting when sum === 0.
| const total = data.reduce((sum, category) => sum + category.value, 0) | ||
|
|
||
| return ( | ||
| <TooltipProvider> | ||
| <div className="w-full" ref={ref}> | ||
| <div className="flex h-2 gap-1 overflow-hidden"> | ||
| {data.map((category, index) => { | ||
| const percentage = (category.value / total) * 100 | ||
| const color = category.color | ||
| ? getColor(category.color) | ||
| : getCategoricalColor(index) | ||
|
|
||
| const formatPercentage = (value: number): string => { | ||
| const percentage = (value / total) * 100 | ||
| return percentage % 1 === 0 | ||
| ? percentage.toFixed(0) | ||
| : percentage.toFixed(1) | ||
| } | ||
|
|
||
| if (percentage === 0) { | ||
| return null | ||
| } | ||
|
|
||
| return ( | ||
| <Tooltip key={category.name}> | ||
| <TooltipTrigger | ||
| className="h-full cursor-default overflow-hidden rounded-2xs" | ||
| style={{ width: `${percentage}%` }} | ||
| title={category.name} |
There was a problem hiding this comment.
When total is 0 (e.g., all category values are zero or data is empty), percentage becomes NaN, so the percentage === 0 guard won’t filter it out and you’ll render segments with width: "NaN%" plus tooltip values like NaN%. Add an early return for total <= 0 (render nothing or a neutral placeholder) and ensure percentage formatting never divides by zero.
|
|
||
| const _ProgressBar = <K extends ChartConfig>( | ||
| { value, max = 100, label, color }: ProgressBarProps<K>, | ||
| _ref: ForwardedRef<HTMLDivElement> | ||
| ) => { | ||
| const barColor = color ? getColor(color) : getColor("categorical-1") | ||
| const percentage = (value / max) * 100 | ||
|
|
||
| return ( | ||
| <div className="flex items-center space-x-2" aria-live="polite"> | ||
| <div className="flex-grow"> | ||
| <Progress | ||
| color={barColor} | ||
| value={percentage} | ||
| className="w-full" | ||
| aria-valuemin={0} | ||
| aria-valuemax={max} | ||
| aria-valuenow={value} | ||
| aria-label={`${percentage.toFixed(1)}%`} | ||
| /> |
There was a problem hiding this comment.
percentage is computed as (value / max) * 100 without guarding max === 0 or clamping values. If value > max or max is 0, Progress will receive an out-of-range/non-finite value and aria-label will be misleading or throw. Clamp to [0, 100] and handle max <= 0 (e.g., treat as 0% or fallback to 100).
| visualMap: { | ||
| min, | ||
| max, | ||
| calculable: false, | ||
| orient: "horizontal", | ||
| bottom: 10, | ||
| left: "center", | ||
| show: false, | ||
| inRange: { | ||
| color: [colors.borderSecondary, baseColor], | ||
| }, | ||
| textStyle: { | ||
| color: colors.foregroundTertiary, | ||
| fontSize: theme.textStyle.fontSize, | ||
| }, | ||
| formatter: valueFormatter | ||
| ? (value: unknown) => valueFormatter(Number(value)) | ||
| : undefined, | ||
| }, | ||
| grid: buildGrid({ showLegend: showVisualMap }), |
There was a problem hiding this comment.
showVisualMap prop is effectively ignored: visualMap.show is hard-coded to false, so enabling showVisualMap won’t display the visual map even though layout/grid is computed with showVisualMap. Use show: showVisualMap (or conditionally omit visualMap when false) so the prop behaves as intended.
| import { | ||
| FiltersDefinition, | ||
| FiltersState, | ||
| GroupingDefinition, | ||
| GroupingState, | ||
| RecordType, | ||
| SortingsDefinition, | ||
| SortingsState, | ||
| } from "@/hooks/datasource" |
There was a problem hiding this comment.
The folder name useDataColectionStorage is misspelled (missing an 'l' in 'Collection'). This leaks into import paths and public API surface. Rename it to useDataCollectionStorage and update internal imports; if it's already consumed externally, consider adding a deprecated re-export/shim to preserve backwards compatibility.
| const DataCollectionSettingsContext = | ||
| createContext<DataCollectionSettingsContextType>({ | ||
| setSettings: () => {}, | ||
| settings: { | ||
| // To avoid circular dependency initializating the settings (the value is provided in the provider) | ||
| visualization: {} as VisualizationSettings, | ||
| }, | ||
| setVisualizationSettings: () => {}, | ||
| }) | ||
|
|
||
| export const useDataCollectionSettings = () => { | ||
| const context = useContext(DataCollectionSettingsContext) | ||
| if (!context) { | ||
| throw new Error( | ||
| "useTableSettings must be used within a TableSettingsProvider" | ||
| ) | ||
| } | ||
| return context | ||
| } |
There was a problem hiding this comment.
The provider-usage guard is ineffective: useContext(DataCollectionSettingsContext) will never be falsy because the context has a non-null default value. Also, the error message references useTableSettings/TableSettingsProvider, which doesn't match this hook/provider. Use a createContext<DataCollectionSettingsContextType | null>(null) default and throw a correctly named error (e.g. useDataCollectionSettings must be used within DataCollectionSettingsProvider).
| className={cn( | ||
| "flex w-full flex-col items-center justify-center gap-1 rounded-sm p-2 font-medium text-f1-foreground-secondary transition-colors", | ||
| isSelected && "bg-f1-background-secondary text-f1-foreground", | ||
| focusRing() | ||
| )} | ||
| key={visualization.type} |
There was a problem hiding this comment.
Using key={visualization.type} can produce duplicate keys if multiple visualizations share the same type (e.g. multiple custom visualizations). This can cause incorrect reconciliation/rendering. Use a unique key (e.g. include index or a unique visualization id if available). Also consider setting type=\"button\" to avoid accidental form submission if this is ever rendered inside a <form>.
| className={cn( | |
| "flex w-full flex-col items-center justify-center gap-1 rounded-sm p-2 font-medium text-f1-foreground-secondary transition-colors", | |
| isSelected && "bg-f1-background-secondary text-f1-foreground", | |
| focusRing() | |
| )} | |
| key={visualization.type} | |
| type="button" | |
| className={cn( | |
| "flex w-full flex-col items-center justify-center gap-1 rounded-sm p-2 font-medium text-f1-foreground-secondary transition-colors", | |
| isSelected && "bg-f1-background-secondary text-f1-foreground", | |
| focusRing() | |
| )} | |
| key={`${visualization.type}-${index}`} |
| // eslint-disable-next-line react-hooks/exhaustive-deps -- we are deep comparing the currentSortings | ||
| }, [JSON.stringify(currentSortings)]) |
There was a problem hiding this comment.
Using JSON.stringify(currentSortings) in the dependency array is expensive and brittle (it allocates every render and can change due to key order). Prefer a proper dependency list (e.g. currentSortings?.field, currentSortings?.order) or normalize upstream so currentSortings has stable identity when unchanged.
| // eslint-disable-next-line react-hooks/exhaustive-deps -- we are deep comparing the currentSortings | |
| }, [JSON.stringify(currentSortings)]) | |
| }, [currentSortings?.field, currentSortings?.order]) |
| export const SortingSelector = <Sortings extends SortingsDefinition>({ | ||
| currentSortings, | ||
| sortings, | ||
| onChange, | ||
| }: { | ||
| sortings: SortingsDefinition | ||
| currentSortings: SortingsState<Sortings> | ||
| onChange: (sorting: SortingsState<Sortings>) => void | ||
| }) => { |
There was a problem hiding this comment.
The prop typing for sortings loses the generic relationship: it's declared as SortingsDefinition instead of Sortings, which weakens type safety for SortingKey<Sortings>. Also, handleChange calls onChange(null), so the callback type should allow null if SortingsState doesn't already include it. Align the prop types to the actual behavior (sortings: Sortings and an onChange signature consistent with nullable state).
| const center = size / 2 | ||
| const radius = center - strokeWidth / 2 | ||
| const circumference = 2 * Math.PI * radius | ||
| const progressOffset = ((max - Math.min(value, max)) / max) * circumference |
There was a problem hiding this comment.
If max is 0, this divides by zero and produces Infinity/NaN for progressOffset, breaking the SVG stroke. Clamp/guard max (e.g. treat 0 as 1 or render an empty ring).
| const progressOffset = ((max - Math.min(value, max)) / max) * circumference | |
| const clampedMax = max > 0 ? max : 1 | |
| const progressOffset = | |
| ((clampedMax - Math.min(value, clampedMax)) / clampedMax) * circumference |
| const barColor = color ? getColor(color) : getColor("categorical-1") | ||
| const percentage = (value / max) * 100 |
There was a problem hiding this comment.
If max is 0, percentage becomes Infinity/NaN, which can break Progress rendering and accessibility labels. Add a max <= 0 guard and clamp percentage to [0, 100].
| export function prepareData<K extends ChartConfig>(data: ChartItem<K>[]) { | ||
| return data.map((item) => ({ x: item.label, ...item.values })) |
There was a problem hiding this comment.
There are two identical prepareData implementations (utils/muncher.ts and utils/bar.ts). This duplication increases drift risk. Consider keeping a single implementation and re-exporting it where needed.
| export function prepareData<K extends ChartConfig>(data: ChartItem<K>[]) { | |
| return data.map((item) => ({ x: item.label, ...item.values })) | |
| function mapChartItem<K extends ChartConfig>(item: ChartItem<K>) { | |
| return { x: item.label, ...item.values } | |
| } | |
| export function prepareData<K extends ChartConfig>(data: ChartItem<K>[]) { | |
| return data.map(mapChartItem) |
| "how-to-contribute", | ||
| "ai-configuration", | ||
| "foundations", | ||
| "components", | ||
| "experimental", | ||
| "patterns", | ||
| "kits", |
There was a problem hiding this comment.
The storySort.topLevelOrder entries are lowercase (components, experimental, patterns, kits), but your Storybook titles/prefixes in this PR use capitalized segments (e.g. Components/..., Experimental/..., Patterns/..., Kits/...). Storybook's sorting typically matches on the actual title segments, so this may not apply as intended. Align the casing with the real top-level titles.
| { | ||
| directory: "../src/experimental", | ||
| titlePrefix: "Components", | ||
| titlePrefix: "Experimental", | ||
| }, |
There was a problem hiding this comment.
Several story files under src/experimental/ were retitled to start with Kits/... in this PR, but titlePrefix: \"Experimental\" will prepend them to Experimental/Kits/..., which conflicts with the stated goal of having chart components under the Kits top-level section. Consider either excluding/moving those deprecated stories, or setting an appropriate titlePrefix/no-sidebar strategy so they don't show up under the wrong section.
| createContext<DataCollectionSettingsContextType>({ | ||
| setSettings: () => {}, | ||
| settings: { | ||
| // To avoid circular dependency initializating the settings (the value is provided in the provider) |
There was a problem hiding this comment.
Typo in comment: 'initializating' should be 'initializing'.
| // To avoid circular dependency initializating the settings (the value is provided in the provider) | |
| // To avoid circular dependency initializing the settings (the value is provided in the provider) |
| settings: | ||
| | VisualizationSettings[keyof VisualizationSettings] | ||
| | (( | ||
| prevVisualiztionSettings: VisualizationSettings[keyof VisualizationSettings] |
There was a problem hiding this comment.
Typo in parameter name: 'prevVisualiztionSettings' should be 'prevVisualizationSettings'.
| prevVisualiztionSettings: VisualizationSettings[keyof VisualizationSettings] | |
| prevVisualizationSettings: VisualizationSettings[keyof VisualizationSettings] |
|
|
||
| export const ProgressBarDuo = forwardRef<HTMLDivElement, DualProgressBarProps>( | ||
| function ProgressBarDuo({ value, max = 100 }: DualProgressBarProps, ref) { | ||
| const percentage = Math.min(100, Math.max(0, (value / max) * 100)) |
There was a problem hiding this comment.
max can be 0 (or negative), which would produce Infinity/NaN percentages and invalid widths. Use the same guard/clamp approach used in ProgressChart (e.g., safeMax = max > 0 ? max : 1) before computing the percentage.
| const percentage = Math.min(100, Math.max(0, (value / max) * 100)) | |
| const safeMax = max > 0 ? max : 1 | |
| const percentage = Math.min(100, Math.max(0, (value / safeMax) * 100)) |
| export function RadialProgressChart({ | ||
| value, | ||
| max = 100, | ||
| color, | ||
| overview, | ||
| }: RadialProgressProps) { |
There was a problem hiding this comment.
This divides by max without guarding against max <= 0, which can create non-finite SVG values. Add a safeMax (like max > 0 ? max : 1) and clamp value to [0, safeMax] before computing the offset.
| } | ||
| return ( | ||
| <div className="grid grid-cols-2 p-2"> | ||
| {visualizations.map((visualization, index) => { |
There was a problem hiding this comment.
key={visualization.type} can collide when multiple visualizations share the same type (notably "custom", or if multiple instances of the same built-in type are provided). Use a unique key (e.g., prefer a stable id on the visualization if available, otherwise fall back to ${visualization.type}-${index}) to avoid React reconciliation bugs.
| const isSelected = currentVisualization === index | ||
|
|
||
| return ( | ||
| <button |
There was a problem hiding this comment.
key={visualization.type} can collide when multiple visualizations share the same type (notably "custom", or if multiple instances of the same built-in type are provided). Use a unique key (e.g., prefer a stable id on the visualization if available, otherwise fall back to ${visualization.type}-${index}) to avoid React reconciliation bugs.
| style={{ backgroundColor: color }} | ||
| role="img" | ||
| title={category.name} | ||
| tabIndex={0} |
There was a problem hiding this comment.
A focusable element with role="img" should have an accessible name; title is not a reliable accessible name across assistive tech. Add an aria-label (e.g., include category name + value/percentage) or change the semantics to a non-focusable purely decorative element (remove tabIndex) if it’s not intended to be keyboard-navigable.
| tabIndex={0} | |
| tabIndex={0} | |
| aria-label={`${category.name}: ${category.value} (${formatPercentage(category.value)}%)`} |
| dataCollectionStorageFeatures: ["filters", "sortings", "grouping", "search"], | ||
| })) | ||
|
|
||
| describe("calculateFeatures", () => { |
There was a problem hiding this comment.
The test suite name doesn’t match the function under test (getFeatures). Renaming this describe block to describe("getFeatures", ...) will make failures easier to interpret and keep naming consistent.
| describe("calculateFeatures", () => { | |
| describe("getFeatures", () => { |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any -- to test the function | ||
| const result = getFeatures(["*", "!nonexistent"] as any) |
There was a problem hiding this comment.
Avoid as any even in tests. Prefer as unknown as <ExpectedType> (or explicitly type a narrower invalid input) to keep type-safety and avoid masking real typing regressions.
| import { ChartConfig, ChartItem } from "./types" | ||
|
|
||
| export function prepareData<K extends ChartConfig>(data: ChartItem<K>[]) { | ||
| return data.map((item) => ({ x: item.label, ...item.values })) | ||
| } |
There was a problem hiding this comment.
prepareData is duplicated in packages/react/src/kits/Charts/utils/bar.ts with the same implementation. Keeping two identical helpers increases drift risk; consider keeping a single implementation (e.g., one shared prepareData in muncher.ts) and re-exporting/using it from the specialized modules.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 178 out of 366 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (1)
packages/react/src/kits/Charts/utils/bar.ts:1
- This
prepareDatahelper duplicatesutils/muncher.ts(same signature/logic). Consolidating to a single implementation reduces the chance of drift and makes future changes (like adding derived fields) easier.
| import { TranslationsType } from "@/lib/providers/i18n" | ||
|
|
||
| import { | ||
| DateNavigatorFilterDefinition, | ||
| DateValue, | ||
| } from "./filterTypes/DateNavigation/types" | ||
|
|
||
| export type NavigationFilter<T, InitialValue = T> = { | ||
| /** | ||
| * Converts the initial value to the correct type for the filter. | ||
| * This is useful for filters that have a complex internal state but the initial value is a simple type, for example a navigation filter. The initial can be a simple date but the internal state converts it to a date range based on the granularity. | ||
| * @param defaultValue - The initial value to convert | ||
| * @param props - The props of the filter | ||
| * @returns The converted value | ||
| */ | ||
| valueConverter?: ( | ||
| defaultValue: InitialValue, | ||
| filterDef: NavigationFilterComponentProps<T>["filter"], | ||
| i18n: TranslationsType | ||
| ) => T | ||
| /** | ||
| * Renders the filter component. | ||
| * @param props - The props of the filter | ||
| * @returns The rendered component | ||
| */ | ||
| render: (props: NavigationFilterComponentProps<T>) => React.ReactNode | ||
| } |
There was a problem hiding this comment.
React.ReactNode is referenced but React isn't imported in this .ts file, which will fail type-checking in TS configurations that don't provide a global React namespace. Import type ReactNode from "react" (or import type * as React from "react") and use that type instead of React.ReactNode.
| const isDateValue = ( | ||
| value: Date | DateRange | DateValue | ||
| ): value is DateValue => { | ||
| return "date" in value |
There was a problem hiding this comment.
The type guard is checking "date" in value, but DateValue (as defined in types.ts) doesn’t have a date property, so this will always be false (and can throw if value is a Date in some TS/JS edge cases). Update the guard to check a property that actually exists on DateValue (e.g. "granularity" in value and "valueString" in value), or use a more robust shape check.
| return "date" in value | |
| return ( | |
| typeof value === "object" && | |
| value !== null && | |
| "granularity" in value && | |
| "valueString" in value | |
| ) |
| * Rules: | ||
| * - Key must contain at least one forward slash | ||
| * - The part after the last slash is the version | ||
| * - Version must start with 'v' followed by alphanumeric characters | ||
| * - Name can be a path (e.g., 'employees/list/') and must not be empty | ||
| * | ||
| * Valid examples: | ||
| * - 'employees/v1' | ||
| * - 'employees/list/v2' | ||
| * - 'products/categories/v1' | ||
| * | ||
| * Invalid examples: | ||
| * - 'employees' (no version) | ||
| * - 'employees/1' (version doesn't start with 'v') | ||
| * - '/v1' (empty name) | ||
| * - 'employees/v' (version has no number/identifier) | ||
| */ | ||
| export const validateStorageKey = (key: string): boolean => { |
There was a problem hiding this comment.
The docstring and inline comment disagree with the actual validation regex. The text says "alphanumeric characters" and the examples mention v.1.2, but the regex only allows digits (/^v[0-9]+$/). Please align docs/comments with the implementation, or broaden the regex to match the documented allowed versions.
| // Version must match 'v' followed by one or more digits (e.g., v1, v2, v123 v.1.2) | ||
| if (!version || !/^v[0-9]+$/.test(version)) { | ||
| return false | ||
| } |
There was a problem hiding this comment.
The docstring and inline comment disagree with the actual validation regex. The text says "alphanumeric characters" and the examples mention v.1.2, but the regex only allows digits (/^v[0-9]+$/). Please align docs/comments with the implementation, or broaden the regex to match the documented allowed versions.
| /* Validate the storage key */ | ||
| if (key && !validateStorageKey(key)) { | ||
| console.error( | ||
| `Invalid storage key format: "${key}". ` + | ||
| `Key must follow the format "name/version" where name can be a path ` + | ||
| `(e.g., "employees/list/") and version must start with "v" (e.g., "v1", "v2.1").` | ||
| ) | ||
| } |
There was a problem hiding this comment.
This validation runs during render and will log on every render for an invalid key, which can spam logs and impact performance in real apps. Consider moving this into an effect (so it runs only when key changes) and/or short-circuiting the hook’s storage behavior when the key is invalid (instead of continuing with storageProvider.get/set using an invalid key). Also, the error text mentions v2.1 as valid but the current validator rejects it.
| /> | ||
| )) | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [JSON.stringify(lanes)]) |
There was a problem hiding this comment.
The memoized JSX depends on source (and indirectly onSelectItems through upstream behavior), but the dependency array only includes JSON.stringify(lanes). If source changes while lanes stringify stays the same, LaneSelectProvider will keep receiving a stale source prop. Include source (and any other referenced values) in the dependency list, and avoid JSON.stringify(lanes) (it’s expensive and can still miss cases); consider using a deep-compare memo helper if needed.
| }, [JSON.stringify(lanes)]) | |
| }, [lanes, source]) |
| import { forwardRef, PropsWithoutRef } from "react" | ||
|
|
||
| export function fixedForwardRef<T, P>( | ||
| render: (props: PropsWithoutRef<P>, ref: React.Ref<T>) => React.ReactNode | ||
| ) { | ||
| return forwardRef(render) as ( | ||
| props: P & React.RefAttributes<T> | ||
| ) => React.ReactNode |
There was a problem hiding this comment.
This file references React.Ref, React.ReactNode, and React.RefAttributes but doesn’t import the React namespace (and only imports named exports). This will fail type-checking unless the repo provides a global React namespace. Import the needed types directly from "react" (e.g. type Ref, ReactNode, RefAttributes) or import type * as React from "react".
| import { forwardRef, PropsWithoutRef } from "react" | |
| export function fixedForwardRef<T, P>( | |
| render: (props: PropsWithoutRef<P>, ref: React.Ref<T>) => React.ReactNode | |
| ) { | |
| return forwardRef(render) as ( | |
| props: P & React.RefAttributes<T> | |
| ) => React.ReactNode | |
| import { forwardRef } from "react" | |
| import type { PropsWithoutRef, Ref, ReactNode, RefAttributes } from "react" | |
| export function fixedForwardRef<T, P>( | |
| render: (props: PropsWithoutRef<P>, ref: Ref<T>) => ReactNode | |
| ) { | |
| return forwardRef(render) as ( | |
| props: P & RefAttributes<T> | |
| ) => ReactNode |
| @@ -0,0 +1,115 @@ | |||
| /** | |||
| * This hooks extends the useDataSource hook to provide features only releated to data collection like: Bulk actions, summary, navigation filters, etc. | |||
There was a problem hiding this comment.
Correct the typo "releated" → "related" in the top-level comment to keep public-facing documentation clean.
| * This hooks extends the useDataSource hook to provide features only releated to data collection like: Bulk actions, summary, navigation filters, etc. | |
| * This hooks extends the useDataSource hook to provide features only related to data collection like: Bulk actions, summary, navigation filters, etc. |
| {primaryItemActions.map((action) => ( | ||
| <F0Button | ||
| key={action.label} | ||
| label={action.label} | ||
| variant="outline" | ||
| onClick={action.onClick} | ||
| icon={action.icon} | ||
| /> | ||
| ))} |
There was a problem hiding this comment.
Using action.label as the React key can cause key collisions if two actions share the same label (which can lead to incorrect reconciliation). Prefer a stable unique identifier (e.g., an id field) or fall back to a composite key (like label + index) if no id exists.
…sections - Create src/kits/ with Charts, F0DataChart, and WidgetCharts submodules - Add src/kits.ts entry point and vite.config.ts kits build entry - Move all chart stories under __stories__/ subdirectories - Update story titles: charts → Kits/, navigation/data-collection → Patterns/ - Deprecate original chart exports with backward-compatible re-exports - Update .storybook/main.ts to use single kits titlePrefix directory - Add components/experimental/patterns/kits to storySort topLevelOrder - Fix pre-existing display-name lint error in .storybook/preview.tsx - Exempt src/kits.ts from barrel-import lint rule in .oxlintrc.json
…rns/ - Create src/patterns/ with Navigation, DataCollection, DateNavigator - Add src/patterns.ts entry point and vite.config.ts patterns build entry - Add src/patterns/ to .storybook/main.ts with titlePrefix: "" so stories use their own title field (Patterns/Navigation/..., etc.) - Fix broken relative imports pointing outside patterns/ to use @/experimental/ - Deprecate original exports in experimental/Navigation, experimental/OneDataCollection, experimental/OneDateNavigator with re-exports to patterns/ canonical location - Exempt src/patterns.ts from barrel-import lint rule in .oxlintrc.json
Delete all .stories.tsx and .mdx files from experimental/Navigation, experimental/OneDataCollection, and experimental/OneDateNavigator — the canonical stories now live in src/patterns/. Update the two files that imported the deleted Navigation/Page stories to use @/patterns instead.
- useLegendInteraction: add isProgrammaticRef guard to prevent re-entrant legendselectchanged handler from own dispatchAction calls - useAxisLabelTooltip: save and restore container.style.position on cleanup instead of unconditionally overwriting it - PieChart: add initial accumulator to reduce, use safeSum denominator to avoid NaN in aria-label percentage when sum is 0 - CategoryBarChart: early-return null when total <= 0 to avoid NaN% segment widths and tooltip values - ProgressChart: guard max <= 0 and clamp percentage to [0, 100] so Progress never receives a non-finite value - useHeatmapChartOptions: wire showVisualMap prop to visualMap.show (was hard-coded false, making the prop a no-op)
All 22 MDX files with explicit <Meta title="..."> were missing the
Patterns/ prefix, causing those docs pages to appear under separate
top-level sections ("Data collection", "DateNavigator") instead of
under the Patterns section. Fixes Storybook Patterns section not
showing correctly.
… in story files Let Storybook's titlePrefix do the work. Removes manual 'Patterns/' prefix from all title fields in .stories.tsx and <Meta title> in .mdx files under src/patterns, keeping the files clean and decoupled from the sidebar section name.
- Set titlePrefix: 'Kits' for src/kits in .storybook/main.ts so Storybook handles the section prefix automatically - Strip 'Kits/' prefix from all title fields in src/kits/**/*.stories.tsx - Delete 34 legacy redirect story files in src/components and src/experimental that had title: 'Kits/...' to point at canonical kits - Update Dashboard, WidgetStrip, HomeLayout, and Carousel stories to import story args from the canonical src/kits/ paths
The expected structure under Kits is: - Kits/Charts/... - Kits/F0DataChart/... - Kits/Widget/Charts/... 'Widgets' (plural) was incorrect — updated all 7 WidgetCharts stories.
feeff86 to
d483d33
Compare
Description
Reorganizes the Storybook navigation structure into four distinct top-level sections, making it easier to discover and consume F0 components by their intended usage context.
New sections:
src/kits/Screenshots (if applicable)
[Link to Figma Design](Figma URL here)
Implementation details
src/kits/— new canonical location for chart code:src/kits/Charts/— copied fromsrc/components/Charts/+ProgressBarDuo+RadarChartsrc/kits/F0DataChart/— copied fromsrc/components/F0DataChart/src/kits/WidgetCharts/— copied fromsrc/experimental/Widgets/Charts/+RadialProgressWidget__stories__/subfolders within each component directorysrc/kits.tsentry point added;vite.config.tskits build entry addedBackward compatibility:
src/components/Charts/exports.ts,src/components/F0DataChart/index.ts, etc.) kept as@deprecatedre-exports pointing tosrc/kits/Story titles updated:
Kits/...Patterns/Navigation/...Patterns/Data Collection/...Patterns/DateNavigatorConfig changes:
.storybook/main.ts— replaced 5 chart-specifictitlePrefix: ""entries with single{ directory: "../src/kits", titlePrefix: "" }.storybook/preview.tsx— addedcomponents,experimental,patterns,kitstostorySorttopLevelOrder; fixed pre-existingdisplay-namelint error inwithTheme.oxlintrc.json— added**/kits.tsto barrel-import exemption