v0.11.0
0.11.0
A large feature release. Two headline efforts land here: @platform-blocks/charts gets a new unified interaction engine (every chart now shares one pointer, hit-test, and tooltip pipeline), and @platform-blocks/ui gains a new theming color-role system plus several new components. There are a handful of breaking changes — all removals of superseded APIs — collected under Migration notes at the end.
Charts: a unified interaction engine
Previously every chart re-implemented pointer handling roughly its own way (~20 variations), tooltips flowed through a two-renderer pipeline (registerSeries + setCrosshair → useTooltipAggregator → ChartPopover), and touch/hover support was uneven — several charts were web-onMouseMove-only. This release replaces all of that with one frame + hit-test + pointer architecture that every chart type now shares. See packages/charts/src/interaction/TOOLTIP_MIGRATION.md for the full design.
Three new layers do the work:
core/frame—createFrameand theChartFrame/CartesianFrame/RadialFrametypes are a memoizable, non-React source of truth for the plot rect, scales, and pixel↔data transforms. This establishes a single container-origin pixel convention across the whole library, so charts no longer hand-roll padding math.core/hittest— one pluggableHitTesterinterface (createHitTesterplusPointSeriesHitTester,BandCategoryHitTester,CellGridHitTester,AngularSliceHitTester,RadarAxisHitTester).hit()returns the nearest single target; the optionalslice()returns every series' target at a given x/angle for multi-series tooltips. This replaces the four overlapping nearest-point routines and the per-chart inline hit-test loops.interaction/—normalizePointeris the single coordinate-truth function that emits container / plot / page spaces (plus inside-plot flags) for every web and native event.useChartPointeris the one cross-platform input path (web PointerEvents vs. native Responder, hidden behind one hook),ChartGestureSurfaceis a declarative full-bleed overlay around it, andChartActiveTooltipis the single themed tooltip (web-portaled, native absolute). Optional two-finger pinch is provided byuseOptionalPinch(touchDistance/touchCenter/resolveGestureHandler), which usesreact-native-gesture-handleronly if installed and never hard-depends on it.
What you get from this:
- Consistent, richer tooltips across every chart type — single-point and multi-series "slice" tooltips, with
renderHeader,renderEntry,sortEntries,filterEntry,maxEntries, and fullcustomTooltipcontrol, all throughChartActiveTooltip. - Native touch hover on charts that were previously web-only — Histogram, Heatmap, Radar, Funnel, Marimekko, and Sankey now respond to touch as well as mouse.
- The
enableCrosshairguide line is engine-driven from the active target's pixel position rather than a per-chart crosshair state.
RadialBarChart was expanded onto the new angular hit-test archetype and gained centerLabel / centerSubLabel props (text in the empty center of the ring). DonutChart's center-label focus (focusedSliceId) now actually tracks the hovered slice instead of only ever resetting. GaugeChart moved from the feedback category into charts.
New chart demos
LineChart→zoom— scroll-to-zoom, drag-to-pan, Shift-drag box-zoom, and double-tap reset, showcasingenablePanZoom/enableWheelZoom/enableBrushZoom/resetOnDoubleTap/zoomMode/minZoom.RadialBarChart→goal-progressandsatisfaction-gauge.
Charts test coverage
The tests/components/* suite was entirely broken under pnpm (the Jest transformIgnorePatterns never matched @testing-library/react-native, so no component test could run). That's fixed with a pnpm-aware pattern, and ~19 new chart component test suites were added (BarChart, BubbleChart, CandlestickChart, ComboChart, FunnelChart, GroupedBarChart, HeatmapChart, HistogramChart, MarimekkoChart, NetworkChart, PieChart, RadarChart, RadialBarChart, RidgeChart, SankeyChart, StackedAreaChart, StackedBarChart, ViolinChart) alongside new unit tests for the frame, hit-test, pointer-normalization, and geometry primitives.
New UI components
ColorInput— a full form field for picking a color: a labeled trigger with a live color preview plus hex / RGB / HSL text entry in a positioned dropdown. Supportsvalue/defaultValue/onChange,format('hex' | 'rgb' | 'hsl'),withAlpha,swatches/withSwatches,showPreview,showInput,clearable, and the same positioning API asAutoComplete(placement,flip,shift,offset,keyboardAvoidance).DataList— a description / key-value list with compound partsDataList.Item,DataList.ItemLabel, andDataList.ItemValue. Render from adata={[{ label, value }]}shorthand or by composing children. Supportsorientation('horizontal' | 'vertical'),withDivider,size,spacing,labelWidth, and color overrides.Menu.Sub(MenuSub) — nested flyout submenus inside aMenu.Dropdown. Opens on hover on web (press elsewhere), nested overlays unmount as a chain, and selecting a leaf item closes the whole chain. Props:label,startSection,disabled,color,w,maxH.GradientTextis now exported from the package root. It fills glyphs with a multi-stop linear gradient (native via@react-native-masked-view+ optionalexpo-linear-gradient, with a graceful fallback when the gradient dependency is absent). Props includecolors(required, ≥2),locations,angle,start/end, andposition.
New theming primitives
Two new modules are exported for building theme-aware, legible color variants — the same machinery Chip and Button now use internally.
variantRoles resolves a component-agnostic variant vocabulary so a light Chip, a light Badge, and any other component render identically on any theme:
resolveVariantRoles(theme, { variant, color, gradientStops })returns{ fill, border, text }for a variant ('filled' | 'outline' | 'light' | 'subtle' | 'gradient'). Because palettes invert between light and dark schemes, tinted variants are built by alpha-compositing the strong color over the livebackgrounds.surfacerather than hardcoding palette indices, and the text color is chosen by measured WCAG contrast against the real composited background — so it stays legible on custom themes and arbitrary hex colors.CORE_COLORS— the six token names (primary,secondary,success,warning,error,gray) treated as palettes; anything else is a raw color.- Types:
VariantRole,VariantRoles,ResolveVariantOptions.
colorUtils provides the underlying contrast helpers:
withAlpha(hex, alpha)— anrgba()wash from a solid hex.readableTextOn(fill)— the text color for a solid fill; prefers white on saturated fills and only falls back to dark when white would drop below 3:1 (keeps white on iOS blue, fixes orange/yellow).contrastRatio(a, b)— WCAG contrast ratio.composite(fg, bg, alpha)— Porter–Duff "over" to an opaque hex, so contrast can be measured against a real tint result.pickReadable(candidates, surface, min)— the first candidate clearingmincontrast, else the highest-contrast one.
A new variantContrast test walks every theme × color × variant combination and asserts the resolved text clears a legible contrast ratio, guarding against regressions.
PlatformBlocksThemeProvider (and PlatformBlocksThemeProviderProps) is now exported from the package root. It was previously an internal-only symbol; consumers can now wrap their app or nest scoped themes directly, with granular sub-contexts (useThemeVisuals, useThemeLayout) so components subscribe only to the slice they need.
Additions
DataTablegained a large set of props: row grouping and aggregation (groupBy,groupsDefaultExpanded,renderGroupHeader, per-columnaggregate/aggregateFormat,showFooterTotals,footerLabel), server-side pagination (manualPagination+paginationProps), CSV export (exportable,exportFileName,onExport), column reordering (enableColumnReordering,columnOrder,onColumnOrderChange), and anariaLabel.Toastviewport-offset API — new exportsuseToastViewportOffset/setToastViewportOffsetand typeToastViewportOffset({ top, bottom, left, right }), plusToastProviderpropsoffset(static) anddefaultVariant. Because theToastProviderusually sits above the app shell, it can't read header/safe-area layout from context; a component inside the shell can now publish the header height and safe-area insets so toast stacks position clear of the chrome (centered stacks shift to stay centered between side navs).Switch— newvariantprop:'filled' | 'outline' | 'ios' | 'android'.AutoComplete— newrenderValue(a rich single-select value overlay),caretHidden, andhighlightQuery;freeSolonow works together withmultiSelect.Chip— newdot/dotColorprops for a leading status dot.Accordion— per-itemcoloroverride so one accordion can mix accents.Pagination— the page-size changer is now wired up in the toolbar (showSizeChanger,pageSizeOptions,onPageSizeChange).Button—colornow accepts any palette token or raw hex across filled / light / subtle / outline / gradient (and ghost/link text); thevariantunion is documented to includesecondaryandnone.ClearButton— newiconSizeandstrokeprops to override the computed close-glyph size and stroke width.Gallery— toggles for the metadata panel, thumbnail strip, download button, keyboard/swipe navigation, backdrop opacity, and animation duration.
Fixes
Textmerges nested style arrays correctly. It now usesStyleSheet.flatteninstead ofObject.assign, so nested arrays (e.g. a Chip or Badge passing[textStyles, textStyle]) merge recursively — fixing droppedcolor/fontSizethat could produce white-on-white text on web. Inline-vs-block detection was also refined so a<Text>containing block children renders a<div>rather than an illegal nested<p>.Toastfilled variants stay legible. The foreground now usesreadableTextOn(fill), so text and icons remain readable on bright fills (success green, warning yellow) instead of unreadable white.Loaderweb animations fixed. The web renderers were rewritten to use CSSanimationKeyframes(react-native-web) instead of Reanimated, fixing broken/janky oval, bars, and dots spinners on web.- Overlay outside-click on web. The full-screen transparent backdrop used to swallow outside clicks, so clicking a different trigger while a popover was open only closed the popover and wasted the click. Web now uses a non-blocking
documentpointer-capture listener, so clicking another trigger closes the open overlay and activates the new trigger in one click — while correctly ignoring clicks on the overlay's own content, its anchor, and any overlay stacked above it (submenus no longer close their parents). Native still uses the tap-catcher backdrop. - Directional overlay flips stay on-axis. An explicit
topplacement now only falls back tobottom(never rotating toleft/right); onlyautostill considers all four sides. useDeviceInfostops causing redundant re-renders. Input-state updates now return the same object reference when nothing changed, so routine pointer/mouse events don't re-render every consumer.getComponentSizeno longer crashes on numeric/arbitrary size tokens — it falls back to themdsize instead of returningundefined.DatePickerInputuses acomboboxrole on web instead ofbutton, avoiding an illegal<button>nested inside a<button>.Tooltipnow positions with thefixedstrategy on web /portalon native.Tableforwards passthrough props (role,aria-*, accessibility props,id) to its underlying View, lettingDataTableattach grid semantics.Highlightkeeps the surrounding text color (marker background only) instead of recoloring the text;Spotlightmatches now use AutoComplete's primary-bold treatment instead of an amber fill.TextAreaconditional renders were converted from&&to ternaries, avoiding a stray falsy render for the label / counter / helper / error blocks.Slideroutlinevariant no longer crashes. The outline thumb referenced a non-existenttheme.colors.darkpalette; it now uses the scheme-awarebackgrounds.surfacetoken.Slideralso now supports tap-to-jump — pressing anywhere on the rail moves the thumb to that value (previously a plain tap only started a drag).
Docs site
- Chart component pages gained interactive playgrounds (
playground: true), and the docs app wires the new Toast viewport-offset API through aToastShellOffsetBridge.
Migration notes
AppStoreButton and its family were removed (AppStoreButton, GooglePlayButton, AppleAppStoreButton, MacAppStoreButton, MicrosoftStoreButton, AmazonAppstoreButton, FDroidButton, and AppStoreButtonProps). Use AppStoreBadge, which renders official store badges via a brand prop and ships convenience wrappers:
| Removed | Replacement |
|---|---|
AppStoreButton / AppleAppStoreButton |
AppStoreDownloadBadge (brand="app-store") |
GooglePlayButton |
GooglePlayDownloadBadge |
MicrosoftStoreButton |
MicrosoftStoreDownloadBadge |
AmazonAppstoreButton |
AmazonAppstoreBadge |
| any of the above | <AppStoreBadge brand="…" primaryText=… secondaryText=… /> |
There is no direct badge for the old MacAppStoreButton or FDroidButton — those consumers should use <AppStoreBadge> with a custom brand and text.
Rating dropped the deprecated allowHalf prop. Use allowFraction instead (precision now derives from it: allowFraction ? 0.1 : 1).
ColorPicker was narrowed to a compact preset-swatch popover (swatches, size, columns, accessibilityLabel). Its full text-entry / format / alpha / positioning role moved to the new ColorInput — switch to ColorInput if you relied on those props.
Charts — removed exports:
| Removed | Replacement |
|---|---|
ChartPopover |
ChartActiveTooltip (auto-mounted by ChartBase / ChartsProvider; customize via render / renderEntry / renderHeader / filterEntry / sortEntries / maxEntries) |
useNearestPoint |
core/hittest (PointSeriesHitTester) via useChartPointer({ tester }) |
useTooltipAggregator |
the hit-tester slice() + ChartActiveTooltip |
Charts — behavior changes to be aware of:
multiTooltipmoved from a per-chart prop to shared store config (store.config.multiTooltip, alongsideliveTooltipandaggregatorMaxSeries). If you set it per-chart, move it to the interaction store.RidgeChartandViolinCharttooltips now report a per-distribution summary (median) instead of the density at the exact hovered value.