Skip to content

v0.11.0

Choose a tag to compare

@joshstovall joshstovall released this 03 Jul 19:09
· 15 commits to main since this release

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 + setCrosshairuseTooltipAggregatorChartPopover), 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/framecreateFrame and the ChartFrame / CartesianFrame / RadialFrame types 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 pluggable HitTester interface (createHitTester plus PointSeriesHitTester, BandCategoryHitTester, CellGridHitTester, AngularSliceHitTester, RadarAxisHitTester). hit() returns the nearest single target; the optional slice() 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/normalizePointer is the single coordinate-truth function that emits container / plot / page spaces (plus inside-plot flags) for every web and native event. useChartPointer is the one cross-platform input path (web PointerEvents vs. native Responder, hidden behind one hook), ChartGestureSurface is a declarative full-bleed overlay around it, and ChartActiveTooltip is the single themed tooltip (web-portaled, native absolute). Optional two-finger pinch is provided by useOptionalPinch (touchDistance / touchCenter / resolveGestureHandler), which uses react-native-gesture-handler only 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 full customTooltip control, all through ChartActiveTooltip.
  • 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 enableCrosshair guide 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

  • LineChartzoom — scroll-to-zoom, drag-to-pan, Shift-drag box-zoom, and double-tap reset, showcasing enablePanZoom / enableWheelZoom / enableBrushZoom / resetOnDoubleTap / zoomMode / minZoom.
  • RadialBarChartgoal-progress and satisfaction-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. Supports value / defaultValue / onChange, format ('hex' | 'rgb' | 'hsl'), withAlpha, swatches / withSwatches, showPreview, showInput, clearable, and the same positioning API as AutoComplete (placement, flip, shift, offset, keyboardAvoidance).
  • DataList — a description / key-value list with compound parts DataList.Item, DataList.ItemLabel, and DataList.ItemValue. Render from a data={[{ label, value }]} shorthand or by composing children. Supports orientation ('horizontal' | 'vertical'), withDivider, size, spacing, labelWidth, and color overrides.
  • Menu.Sub (MenuSub) — nested flyout submenus inside a Menu.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.
  • GradientText is now exported from the package root. It fills glyphs with a multi-stop linear gradient (native via @react-native-masked-view + optional expo-linear-gradient, with a graceful fallback when the gradient dependency is absent). Props include colors (required, ≥2), locations, angle, start / end, and position.

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 live backgrounds.surface rather 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) — an rgba() 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 clearing min contrast, 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

  • DataTable gained a large set of props: row grouping and aggregation (groupBy, groupsDefaultExpanded, renderGroupHeader, per-column aggregate / aggregateFormat, showFooterTotals, footerLabel), server-side pagination (manualPagination + paginationProps), CSV export (exportable, exportFileName, onExport), column reordering (enableColumnReordering, columnOrder, onColumnOrderChange), and an ariaLabel.
  • Toast viewport-offset API — new exports useToastViewportOffset / setToastViewportOffset and type ToastViewportOffset ({ top, bottom, left, right }), plus ToastProvider props offset (static) and defaultVariant. Because the ToastProvider usually 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 — new variant prop: 'filled' | 'outline' | 'ios' | 'android'.
  • AutoComplete — new renderValue (a rich single-select value overlay), caretHidden, and highlightQuery; freeSolo now works together with multiSelect.
  • Chip — new dot / dotColor props for a leading status dot.
  • Accordion — per-item color override so one accordion can mix accents.
  • Pagination — the page-size changer is now wired up in the toolbar (showSizeChanger, pageSizeOptions, onPageSizeChange).
  • Buttoncolor now accepts any palette token or raw hex across filled / light / subtle / outline / gradient (and ghost/link text); the variant union is documented to include secondary and none.
  • ClearButton — new iconSize and stroke props 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

  • Text merges nested style arrays correctly. It now uses StyleSheet.flatten instead of Object.assign, so nested arrays (e.g. a Chip or Badge passing [textStyles, textStyle]) merge recursively — fixing dropped color / fontSize that 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>.
  • Toast filled variants stay legible. The foreground now uses readableTextOn(fill), so text and icons remain readable on bright fills (success green, warning yellow) instead of unreadable white.
  • Loader web animations fixed. The web renderers were rewritten to use CSS animationKeyframes (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 document pointer-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 top placement now only falls back to bottom (never rotating to left/right); only auto still considers all four sides.
  • useDeviceInfo stops 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.
  • getComponentSize no longer crashes on numeric/arbitrary size tokens — it falls back to the md size instead of returning undefined.
  • DatePickerInput uses a combobox role on web instead of button, avoiding an illegal <button> nested inside a <button>.
  • Tooltip now positions with the fixed strategy on web / portal on native.
  • Table forwards passthrough props (role, aria-*, accessibility props, id) to its underlying View, letting DataTable attach grid semantics.
  • Highlight keeps the surrounding text color (marker background only) instead of recoloring the text; Spotlight matches now use AutoComplete's primary-bold treatment instead of an amber fill.
  • TextArea conditional renders were converted from && to ternaries, avoiding a stray falsy render for the label / counter / helper / error blocks.
  • Slider outline variant no longer crashes. The outline thumb referenced a non-existent theme.colors.dark palette; it now uses the scheme-aware backgrounds.surface token. Slider also 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 a ToastShellOffsetBridge.

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:

  • multiTooltip moved from a per-chart prop to shared store config (store.config.multiTooltip, alongside liveTooltip and aggregatorMaxSeries). If you set it per-chart, move it to the interaction store.
  • RidgeChart and ViolinChart tooltips now report a per-distribution summary (median) instead of the density at the exact hovered value.