@startsimpli/ui-native 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +172 -2
  2. package/package.json +50 -4
  3. package/src/__tests__/charts-math.test.ts +73 -0
  4. package/src/__tests__/styles-dashboard.test.ts +92 -0
  5. package/src/__tests__/styles-primitives.test.ts +114 -0
  6. package/src/__tests__/styles.test.ts +101 -0
  7. package/src/__tests__/theme.test.ts +23 -0
  8. package/src/__tests__/tokens.test.ts +33 -0
  9. package/src/charts-math.ts +106 -0
  10. package/src/components/AccentContext.tsx +22 -0
  11. package/src/components/Alert.tsx +27 -0
  12. package/src/components/Avatar.tsx +42 -0
  13. package/src/components/Badge.tsx +18 -0
  14. package/src/components/Button.tsx +40 -0
  15. package/src/components/Card.tsx +30 -0
  16. package/src/components/Checkbox.tsx +31 -0
  17. package/src/components/Chip.tsx +26 -0
  18. package/src/components/CompareChart.tsx +56 -0
  19. package/src/components/DataTable.tsx +89 -0
  20. package/src/components/DemographicBars.tsx +50 -0
  21. package/src/components/DonutChart.tsx +57 -0
  22. package/src/components/DonutLegend.tsx +49 -0
  23. package/src/components/DualRangeControl.tsx +31 -0
  24. package/src/components/EmptyState.tsx +63 -0
  25. package/src/components/ErrorBoundary.tsx +52 -0
  26. package/src/components/Flag.tsx +26 -0
  27. package/src/components/GradientBox.tsx +48 -0
  28. package/src/components/HeroBanner.tsx +160 -0
  29. package/src/components/Input.tsx +33 -0
  30. package/src/components/Label.tsx +9 -0
  31. package/src/components/ListRow.tsx +44 -0
  32. package/src/components/MetricRow.tsx +48 -0
  33. package/src/components/MultiLineLegend.tsx +30 -0
  34. package/src/components/Progress.tsx +18 -0
  35. package/src/components/Screen.tsx +38 -0
  36. package/src/components/SegmentedControl.tsx +31 -0
  37. package/src/components/Separator.tsx +13 -0
  38. package/src/components/ShareBar.tsx +32 -0
  39. package/src/components/Skeleton.tsx +29 -0
  40. package/src/components/SkeletonList.tsx +23 -0
  41. package/src/components/Sparkline.tsx +38 -0
  42. package/src/components/Spinner.tsx +8 -0
  43. package/src/components/StatTile.tsx +69 -0
  44. package/src/components/Switch.tsx +17 -0
  45. package/src/components/TabBar.tsx +50 -0
  46. package/src/components/Text.tsx +13 -0
  47. package/src/components/ThemeProvider.tsx +25 -0
  48. package/src/components/Timeline.tsx +47 -0
  49. package/src/components/TrendCard.tsx +39 -0
  50. package/src/components/TrendChart.tsx +68 -0
  51. package/src/index.ts +64 -0
  52. package/src/styles.ts +479 -0
  53. package/src/theme.ts +46 -0
  54. package/src/tokens.ts +145 -0
@@ -0,0 +1,32 @@
1
+ import { View, type StyleProp, type ViewStyle } from 'react-native';
2
+ import { useTheme } from './ThemeProvider';
3
+ import { shareBarTrackStyle } from '../styles';
4
+
5
+ export interface ShareSegment {
6
+ value: number;
7
+ color: string;
8
+ /** Optional, for accessibility / legends rendered by the caller. */
9
+ label?: string;
10
+ }
11
+
12
+ export interface ShareBarProps {
13
+ segments: ShareSegment[];
14
+ /** Bar thickness in px. */
15
+ height?: number;
16
+ style?: StyleProp<ViewStyle>;
17
+ }
18
+
19
+ /** Horizontal segmented proportion bar (e.g. audience share by platform). Each
20
+ * segment's width is its value as a fraction of the total. */
21
+ export function ShareBar({ segments, height = 10, style }: ShareBarProps) {
22
+ const theme = useTheme();
23
+ const total = segments.reduce((sum, s) => sum + Math.max(0, s.value), 0);
24
+ return (
25
+ <View style={[shareBarTrackStyle(theme, height), style]} accessibilityRole="image">
26
+ {total > 0 &&
27
+ segments.map((s, i) => (
28
+ <View key={`${s.label ?? i}`} style={{ flex: Math.max(0, s.value) / total, backgroundColor: s.color }} />
29
+ ))}
30
+ </View>
31
+ );
32
+ }
@@ -0,0 +1,29 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { Animated, type DimensionValue, type ViewStyle } from 'react-native';
3
+ import { useTheme } from './ThemeProvider';
4
+ import { skeletonStyle } from '../styles';
5
+
6
+ export interface SkeletonProps {
7
+ width?: DimensionValue;
8
+ height?: number;
9
+ style?: ViewStyle;
10
+ }
11
+
12
+ /** Pulsing placeholder for loading content. Mirrors @startsimpli/ui skeleton. */
13
+ export function Skeleton({ width = '100%', height = 16, style }: SkeletonProps) {
14
+ const theme = useTheme();
15
+ const opacity = useRef(new Animated.Value(0.5)).current;
16
+
17
+ useEffect(() => {
18
+ const loop = Animated.loop(
19
+ Animated.sequence([
20
+ Animated.timing(opacity, { toValue: 1, duration: 700, useNativeDriver: true }),
21
+ Animated.timing(opacity, { toValue: 0.4, duration: 700, useNativeDriver: true }),
22
+ ])
23
+ );
24
+ loop.start();
25
+ return () => loop.stop();
26
+ }, [opacity]);
27
+
28
+ return <Animated.View style={[skeletonStyle(theme), { width, height, opacity }, style]} />;
29
+ }
@@ -0,0 +1,23 @@
1
+ import { View } from 'react-native';
2
+ import { Skeleton } from './Skeleton';
3
+ import { useTheme } from './ThemeProvider';
4
+
5
+ export interface SkeletonListProps {
6
+ /** Number of placeholder blocks. */
7
+ count?: number;
8
+ /** Height of each block. */
9
+ height?: number;
10
+ gap?: number;
11
+ }
12
+
13
+ /** A column of card-shaped Skeleton placeholders for loading data screens. */
14
+ export function SkeletonList({ count = 3, height = 84, gap }: SkeletonListProps) {
15
+ const theme = useTheme();
16
+ return (
17
+ <View style={{ gap: gap ?? theme.spacing.md }}>
18
+ {Array.from({ length: count }).map((_, i) => (
19
+ <Skeleton key={i} width="100%" height={height} />
20
+ ))}
21
+ </View>
22
+ );
23
+ }
@@ -0,0 +1,38 @@
1
+ import Svg, { Path } from 'react-native-svg';
2
+ import { linePath, areaPath } from '../charts-math';
3
+
4
+ export interface SparklineProps {
5
+ data: number[];
6
+ width?: number;
7
+ height?: number;
8
+ /** Stroke (and area fill) color. Defaults to a neutral; pass a brand color. */
9
+ color?: string;
10
+ strokeWidth?: number;
11
+ /** Draw a faint filled area under the line. */
12
+ fill?: boolean;
13
+ }
14
+
15
+ /** Tiny inline trend line (no axes), for placing next to a metric. */
16
+ export function Sparkline({
17
+ data,
18
+ width = 72,
19
+ height = 24,
20
+ color = '#3B82F6',
21
+ strokeWidth = 2,
22
+ fill = true,
23
+ }: SparklineProps) {
24
+ const box = { width, height, pad: strokeWidth };
25
+ return (
26
+ <Svg width={width} height={height}>
27
+ {fill && <Path d={areaPath(data, box)} fill={color} opacity={0.15} />}
28
+ <Path
29
+ d={linePath(data, box)}
30
+ stroke={color}
31
+ strokeWidth={strokeWidth}
32
+ fill="none"
33
+ strokeLinecap="round"
34
+ strokeLinejoin="round"
35
+ />
36
+ </Svg>
37
+ );
38
+ }
@@ -0,0 +1,8 @@
1
+ import { ActivityIndicator, type ActivityIndicatorProps } from 'react-native';
2
+ import { useTheme } from './ThemeProvider';
3
+
4
+ /** Activity indicator defaulting to the brand color. Mirrors @startsimpli/ui page-loader. */
5
+ export function Spinner({ color, ...rest }: ActivityIndicatorProps) {
6
+ const theme = useTheme();
7
+ return <ActivityIndicator color={color ?? theme.colors.primary} {...rest} />;
8
+ }
@@ -0,0 +1,69 @@
1
+ import { type ReactNode } from 'react';
2
+ import { View, Text as RNText, type ViewProps } from 'react-native';
3
+ import { useTheme } from './ThemeProvider';
4
+ import { useAccent } from './AccentContext';
5
+ import {
6
+ statTileStyle,
7
+ statValueStyle,
8
+ statLabelStyle,
9
+ statDeltaColor,
10
+ type StatDelta,
11
+ } from '../styles';
12
+
13
+ export interface StatTileProps extends ViewProps {
14
+ /** Pre-formatted metric value, e.g. "1.2M" or "$48,000". */
15
+ value: string;
16
+ /** Caption below the value. */
17
+ label: string;
18
+ /** Optional pre-formatted change, e.g. "+12%". */
19
+ delta?: string;
20
+ /** Drives the delta color (up = primary, down = destructive). */
21
+ deltaDirection?: StatDelta;
22
+ /** Optional leading glyph/icon node (kept icon-library-agnostic). */
23
+ icon?: ReactNode;
24
+ }
25
+
26
+ /** Dashboard metric tile: big value, caption, optional trend + icon. */
27
+ export function StatTile({
28
+ value,
29
+ label,
30
+ delta,
31
+ deltaDirection = 'neutral',
32
+ icon,
33
+ style,
34
+ ...rest
35
+ }: StatTileProps) {
36
+ const theme = useTheme();
37
+ const accent = useAccent();
38
+ return (
39
+ <View
40
+ style={[
41
+ statTileStyle(theme),
42
+ // A 3px accent stripe at the top ties every tile to the screen's
43
+ // active accent — picks up the artist colour automatically.
44
+ { borderTopWidth: 3, borderTopColor: accent },
45
+ style,
46
+ ]}
47
+ {...rest}
48
+ >
49
+ {(icon || delta) && (
50
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
51
+ <View>{icon}</View>
52
+ {delta ? (
53
+ <RNText
54
+ style={{
55
+ fontSize: theme.fontSizes.xs,
56
+ fontWeight: theme.fontWeights.semibold as never,
57
+ color: statDeltaColor(theme, deltaDirection),
58
+ }}
59
+ >
60
+ {delta}
61
+ </RNText>
62
+ ) : null}
63
+ </View>
64
+ )}
65
+ <RNText style={statValueStyle(theme)}>{value}</RNText>
66
+ <RNText style={statLabelStyle(theme)}>{label}</RNText>
67
+ </View>
68
+ );
69
+ }
@@ -0,0 +1,17 @@
1
+ import { Switch as RNSwitch, type SwitchProps as RNSwitchProps } from 'react-native';
2
+ import { useTheme } from './ThemeProvider';
3
+
4
+ export type SwitchProps = RNSwitchProps;
5
+
6
+ /** Themed toggle (uses the platform Switch with brand track color). */
7
+ export function Switch({ value, ...rest }: SwitchProps) {
8
+ const theme = useTheme();
9
+ return (
10
+ <RNSwitch
11
+ value={value}
12
+ trackColor={{ false: theme.colors.input, true: theme.colors.primary }}
13
+ ios_backgroundColor={theme.colors.input}
14
+ {...rest}
15
+ />
16
+ );
17
+ }
@@ -0,0 +1,50 @@
1
+ import { type ReactNode } from 'react';
2
+ import { View, Pressable, Text as RNText, type StyleProp, type ViewStyle } from 'react-native';
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
4
+ import { useTheme } from './ThemeProvider';
5
+ import { tabBarStyle, tabItemStyle, tabLabelStyle } from '../styles';
6
+
7
+ export interface TabItem {
8
+ key: string;
9
+ label: string;
10
+ /**
11
+ * Optional icon renderer. Receives the active state and theme-resolved color
12
+ * so the app can supply any icon library (or emoji) without ui-native taking
13
+ * an icon dependency.
14
+ */
15
+ icon?: (state: { active: boolean; color: string }) => ReactNode;
16
+ }
17
+
18
+ export interface TabBarProps {
19
+ items: TabItem[];
20
+ activeKey: string;
21
+ onChange: (key: string) => void;
22
+ style?: StyleProp<ViewStyle>;
23
+ }
24
+
25
+ /** Bottom tab bar. Pads for the home-indicator safe area; active tab uses the
26
+ * primary color. Icon-library-agnostic — pass an `icon` renderer per item. */
27
+ export function TabBar({ items, activeKey, onChange, style }: TabBarProps) {
28
+ const theme = useTheme();
29
+ const insets = useSafeAreaInsets();
30
+ return (
31
+ <View style={[tabBarStyle(theme), { paddingBottom: insets.bottom + theme.spacing.xs }, style]}>
32
+ {items.map((item) => {
33
+ const active = item.key === activeKey;
34
+ const color = active ? theme.colors.primary : theme.colors.mutedForeground;
35
+ return (
36
+ <Pressable
37
+ key={item.key}
38
+ accessibilityRole="tab"
39
+ accessibilityState={{ selected: active }}
40
+ onPress={() => onChange(item.key)}
41
+ style={tabItemStyle(theme)}
42
+ >
43
+ {item.icon?.({ active, color })}
44
+ <RNText style={tabLabelStyle(theme, active)}>{item.label}</RNText>
45
+ </Pressable>
46
+ );
47
+ })}
48
+ </View>
49
+ );
50
+ }
@@ -0,0 +1,13 @@
1
+ import { Text as RNText, type TextProps as RNTextProps } from 'react-native';
2
+ import { useTheme } from './ThemeProvider';
3
+ import { textStyle, type TextVariant } from '../styles';
4
+
5
+ export interface TextProps extends RNTextProps {
6
+ variant?: TextVariant;
7
+ }
8
+
9
+ /** Themed text. variant controls size/weight/color (heading/body/muted/caption). */
10
+ export function Text({ variant = 'body', style, ...rest }: TextProps) {
11
+ const theme = useTheme();
12
+ return <RNText style={[textStyle(theme, variant), style]} {...rest} />;
13
+ }
@@ -0,0 +1,25 @@
1
+ import { createContext, useContext, type ReactNode } from 'react';
2
+ import { useColorScheme } from 'react-native';
3
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
4
+ import { lightTheme, darkTheme, type Theme } from '../theme';
5
+
6
+ const ThemeContext = createContext<Theme>(lightTheme);
7
+
8
+ /**
9
+ * Provides a Theme to the primitives (and a SafeAreaProvider so Screen can read
10
+ * insets). With no `theme` prop it follows the OS light/dark setting; pass
11
+ * `theme` to pin or customize.
12
+ */
13
+ export function ThemeProvider({ theme, children }: { theme?: Theme; children: ReactNode }) {
14
+ const scheme = useColorScheme();
15
+ const resolved = theme ?? (scheme === 'dark' ? darkTheme : lightTheme);
16
+ return (
17
+ <ThemeContext.Provider value={resolved}>
18
+ <SafeAreaProvider>{children}</SafeAreaProvider>
19
+ </ThemeContext.Provider>
20
+ );
21
+ }
22
+
23
+ export function useTheme(): Theme {
24
+ return useContext(ThemeContext);
25
+ }
@@ -0,0 +1,47 @@
1
+ import { type ReactNode } from 'react';
2
+ import { View, Text as RNText } from 'react-native';
3
+ import { useTheme } from './ThemeProvider';
4
+ import { timelineDotStyle, timelineConnectorStyle, listRowTitleStyle, listRowSubtitleStyle } from '../styles';
5
+
6
+ export interface TimelineItem {
7
+ key: string;
8
+ title: string;
9
+ /** Secondary line under the title (e.g. a date). */
10
+ meta?: string;
11
+ /** Marker dot color (defaults to theme.primary). */
12
+ color?: string;
13
+ /** Right-aligned content (e.g. an amount or badge). */
14
+ trailing?: ReactNode;
15
+ }
16
+
17
+ export interface TimelineProps {
18
+ items: TimelineItem[];
19
+ }
20
+
21
+ /** Vertical timeline: colored marker dots joined by a connector rail, each with
22
+ * a title/meta and optional trailing content. Generic — events, payouts, etc. */
23
+ export function Timeline({ items }: TimelineProps) {
24
+ const theme = useTheme();
25
+ return (
26
+ <View>
27
+ {items.map((item, i) => {
28
+ const last = i === items.length - 1;
29
+ return (
30
+ <View key={item.key} style={{ flexDirection: 'row', gap: theme.spacing.md }}>
31
+ <View style={{ width: 12, alignItems: 'center' }}>
32
+ <View style={timelineDotStyle(theme, item.color)} />
33
+ {!last && <View style={timelineConnectorStyle(theme)} />}
34
+ </View>
35
+ <View style={{ flex: 1, flexDirection: 'row', alignItems: 'flex-start', paddingBottom: last ? 0 : theme.spacing.lg }}>
36
+ <View style={{ flex: 1 }}>
37
+ <RNText style={listRowTitleStyle(theme)}>{item.title}</RNText>
38
+ {item.meta ? <RNText style={listRowSubtitleStyle(theme)}>{item.meta}</RNText> : null}
39
+ </View>
40
+ {item.trailing ? <View>{item.trailing}</View> : null}
41
+ </View>
42
+ </View>
43
+ );
44
+ })}
45
+ </View>
46
+ );
47
+ }
@@ -0,0 +1,39 @@
1
+ import { View, type StyleProp, type ViewStyle } from 'react-native';
2
+ import { Card, CardContent } from './Card';
3
+ import { Text } from './Text';
4
+ import { TrendChart } from './TrendChart';
5
+
6
+ export interface TrendCardProps {
7
+ /** Small label, top-left (e.g. "Streams · 28 days"). */
8
+ caption: string;
9
+ /** Headline value, top-right (pre-formatted). */
10
+ value: string;
11
+ data: number[];
12
+ color?: string;
13
+ height?: number;
14
+ /** [start, end] captions under the chart. */
15
+ labels?: [string, string];
16
+ style?: StyleProp<ViewStyle>;
17
+ }
18
+
19
+ /** A card with a caption + headline value over an area trend chart. The common
20
+ * "metric over time" block (reach, engagement, per-platform streams, …). */
21
+ export function TrendCard({ caption, value, data, color, height = 120, labels, style }: TrendCardProps) {
22
+ return (
23
+ <Card style={style}>
24
+ <CardContent>
25
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'baseline' }}>
26
+ <Text variant="caption">{caption}</Text>
27
+ <Text variant="body" style={{ fontWeight: '700' }}>
28
+ {value}
29
+ </Text>
30
+ </View>
31
+ {data.length > 0 && (
32
+ <View style={{ marginTop: 10 }}>
33
+ <TrendChart data={data} height={height} color={color} labels={labels} />
34
+ </View>
35
+ )}
36
+ </CardContent>
37
+ </Card>
38
+ );
39
+ }
@@ -0,0 +1,68 @@
1
+ import { useState } from 'react';
2
+ import { View, Text as RNText, type LayoutChangeEvent } from 'react-native';
3
+ import Svg, { Path, Line, Defs, LinearGradient, Stop } from 'react-native-svg';
4
+ import { useTheme } from './ThemeProvider';
5
+ import { linePath, areaPath } from '../charts-math';
6
+
7
+ export interface TrendChartProps {
8
+ data: number[];
9
+ height?: number;
10
+ color?: string;
11
+ /** Number of horizontal gridlines. */
12
+ gridLines?: number;
13
+ /** [start, end] captions under the chart (e.g. dates). */
14
+ labels?: [string, string];
15
+ strokeWidth?: number;
16
+ }
17
+
18
+ /** Full-width area+line trend chart with faint gridlines. Measures its own
19
+ * width via onLayout so it fills the parent. */
20
+ export function TrendChart({
21
+ data,
22
+ height = 130,
23
+ color = '#3B82F6',
24
+ gridLines = 3,
25
+ labels,
26
+ strokeWidth = 2,
27
+ }: TrendChartProps) {
28
+ const theme = useTheme();
29
+ const [width, setWidth] = useState(0);
30
+ const onLayout = (e: LayoutChangeEvent) => setWidth(e.nativeEvent.layout.width);
31
+ const chartH = labels ? height - 18 : height;
32
+ const box = { width, height: chartH, pad: strokeWidth + 1 };
33
+ const gradId = 'trendFill';
34
+
35
+ return (
36
+ <View onLayout={onLayout}>
37
+ {width > 0 && (
38
+ <Svg width={width} height={chartH}>
39
+ <Defs>
40
+ <LinearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
41
+ <Stop offset="0" stopColor={color} stopOpacity={0.35} />
42
+ <Stop offset="1" stopColor={color} stopOpacity={0.02} />
43
+ </LinearGradient>
44
+ </Defs>
45
+ {Array.from({ length: gridLines }).map((_, i) => {
46
+ const y = (chartH / (gridLines + 1)) * (i + 1);
47
+ return <Line key={i} x1={0} y1={y} x2={width} y2={y} stroke={theme.colors.border} strokeWidth={1} />;
48
+ })}
49
+ <Path d={areaPath(data, box)} fill={`url(#${gradId})`} />
50
+ <Path
51
+ d={linePath(data, box)}
52
+ stroke={color}
53
+ strokeWidth={strokeWidth}
54
+ fill="none"
55
+ strokeLinecap="round"
56
+ strokeLinejoin="round"
57
+ />
58
+ </Svg>
59
+ )}
60
+ {labels && (
61
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 4 }}>
62
+ <RNText style={{ fontSize: theme.fontSizes.xs, color: theme.colors.mutedForeground }}>{labels[0]}</RNText>
63
+ <RNText style={{ fontSize: theme.fontSizes.xs, color: theme.colors.mutedForeground }}>{labels[1]}</RNText>
64
+ </View>
65
+ )}
66
+ </View>
67
+ );
68
+ }
package/src/index.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @startsimpli/ui-native — React Native UI primitives fed by shared design
3
+ * tokens. The native counterpart to @startsimpli/ui (web/Tailwind); both draw
4
+ * from the same token values (see ./tokens and packages/ui/theme/contract.css).
5
+ */
6
+
7
+ // Design tokens + resolved themes + pure style builders
8
+ export * from './tokens';
9
+ export * from './theme';
10
+ export * from './styles';
11
+
12
+ // Theming + layout
13
+ export { ThemeProvider, useTheme } from './components/ThemeProvider';
14
+ export { Screen, type ScreenProps } from './components/Screen';
15
+ export { ErrorBoundary, type ErrorBoundaryProps } from './components/ErrorBoundary';
16
+
17
+ // Primitives
18
+ export { Text, type TextProps } from './components/Text';
19
+ export { Button, type ButtonProps } from './components/Button';
20
+ export { Badge, type BadgeProps } from './components/Badge';
21
+ export { Label } from './components/Label';
22
+ export { Input, type InputProps } from './components/Input';
23
+ export { Separator, type SeparatorProps } from './components/Separator';
24
+ export { Checkbox, type CheckboxProps } from './components/Checkbox';
25
+ export { Switch, type SwitchProps } from './components/Switch';
26
+ export { Progress, type ProgressProps } from './components/Progress';
27
+ export { Alert, type AlertProps } from './components/Alert';
28
+ export { Skeleton, type SkeletonProps } from './components/Skeleton';
29
+ export { SkeletonList, type SkeletonListProps } from './components/SkeletonList';
30
+ export { Spinner } from './components/Spinner';
31
+ export {
32
+ Card,
33
+ CardHeader,
34
+ CardTitle,
35
+ CardDescription,
36
+ CardContent,
37
+ } from './components/Card';
38
+ export { Avatar, type AvatarProps } from './components/Avatar';
39
+ export { Flag, type FlagProps } from './components/Flag';
40
+ export { StatTile, type StatTileProps } from './components/StatTile';
41
+ export { EmptyState, type EmptyStateProps } from './components/EmptyState';
42
+ export { ListRow, type ListRowProps } from './components/ListRow';
43
+ export { MetricRow, type MetricRowProps } from './components/MetricRow';
44
+ export { TabBar, type TabBarProps, type TabItem } from './components/TabBar';
45
+ export { ShareBar, type ShareBarProps, type ShareSegment } from './components/ShareBar';
46
+ export { Chip, type ChipProps } from './components/Chip';
47
+ export { SegmentedControl, type SegmentedControlProps, type SegmentedOption } from './components/SegmentedControl';
48
+ export { Timeline, type TimelineProps, type TimelineItem } from './components/Timeline';
49
+
50
+ // Charts (require the optional react-native-svg peer dependency)
51
+ export * from './charts-math';
52
+ export { Sparkline, type SparklineProps } from './components/Sparkline';
53
+ export { DonutChart, type DonutChartProps } from './components/DonutChart';
54
+ export { DonutLegend, type DonutLegendProps, type DonutLegendItem } from './components/DonutLegend';
55
+ export { TrendChart, type TrendChartProps } from './components/TrendChart';
56
+ export { TrendCard, type TrendCardProps } from './components/TrendCard';
57
+ export { CompareChart, type CompareChartProps, type CompareSeries } from './components/CompareChart';
58
+ export { DemographicBars, type DemographicBarsProps, type DemographicBar } from './components/DemographicBars';
59
+ export { MultiLineLegend, type MultiLineLegendProps, type MultiLineLegendItem } from './components/MultiLineLegend';
60
+ export { DualRangeControl, type DualRangeControlProps } from './components/DualRangeControl';
61
+ export { GradientBox, type GradientBoxProps } from './components/GradientBox';
62
+ export { HeroBanner, type HeroBannerProps } from './components/HeroBanner';
63
+ export { AccentProvider, useAccent } from './components/AccentContext';
64
+ export { DataTable, type DataTableProps, type DataColumn, type DataRow } from './components/DataTable';