@oxyhq/bloom 0.29.5 → 0.30.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.
- package/lib/commonjs/activity-heatmap/ActivityHeatmap.js +304 -0
- package/lib/commonjs/activity-heatmap/ActivityHeatmap.js.map +1 -0
- package/lib/commonjs/activity-heatmap/bucketByDay.js +51 -0
- package/lib/commonjs/activity-heatmap/bucketByDay.js.map +1 -0
- package/lib/commonjs/activity-heatmap/index.js +20 -0
- package/lib/commonjs/activity-heatmap/index.js.map +1 -0
- package/lib/commonjs/activity-heatmap/types.js +6 -0
- package/lib/commonjs/activity-heatmap/types.js.map +1 -0
- package/lib/commonjs/avatar-group/AvatarGroupBase.js +42 -18
- package/lib/commonjs/avatar-group/AvatarGroupBase.js.map +1 -1
- package/lib/commonjs/composition-bar/CompositionBar.js +159 -0
- package/lib/commonjs/composition-bar/CompositionBar.js.map +1 -0
- package/lib/commonjs/composition-bar/index.js +13 -0
- package/lib/commonjs/composition-bar/index.js.map +1 -0
- package/lib/commonjs/composition-bar/types.js +6 -0
- package/lib/commonjs/composition-bar/types.js.map +1 -0
- package/lib/commonjs/dot-grid-meter/DotGridMeter.js +69 -0
- package/lib/commonjs/dot-grid-meter/DotGridMeter.js.map +1 -0
- package/lib/commonjs/dot-grid-meter/index.js +13 -0
- package/lib/commonjs/dot-grid-meter/index.js.map +1 -0
- package/lib/commonjs/dot-grid-meter/types.js +6 -0
- package/lib/commonjs/dot-grid-meter/types.js.map +1 -0
- package/lib/commonjs/index.js +59 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +59 -12
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/profile-card/ProfileCard.js +301 -0
- package/lib/commonjs/profile-card/ProfileCard.js.map +1 -0
- package/lib/commonjs/profile-card/index.js +13 -0
- package/lib/commonjs/profile-card/index.js.map +1 -0
- package/lib/commonjs/profile-card/types.js +6 -0
- package/lib/commonjs/profile-card/types.js.map +1 -0
- package/lib/commonjs/stat-bar/StatBar.js +210 -0
- package/lib/commonjs/stat-bar/StatBar.js.map +1 -0
- package/lib/commonjs/stat-bar/index.js +13 -0
- package/lib/commonjs/stat-bar/index.js.map +1 -0
- package/lib/commonjs/stat-bar/types.js +6 -0
- package/lib/commonjs/stat-bar/types.js.map +1 -0
- package/lib/commonjs/typography/index.js +4 -5
- package/lib/commonjs/typography/index.js.map +1 -1
- package/lib/commonjs/typography/styled-text.js +12 -19
- package/lib/commonjs/typography/styled-text.js.map +1 -1
- package/lib/module/activity-heatmap/ActivityHeatmap.js +299 -0
- package/lib/module/activity-heatmap/ActivityHeatmap.js.map +1 -0
- package/lib/module/activity-heatmap/bucketByDay.js +47 -0
- package/lib/module/activity-heatmap/bucketByDay.js.map +1 -0
- package/lib/module/activity-heatmap/index.js +5 -0
- package/lib/module/activity-heatmap/index.js.map +1 -0
- package/lib/module/activity-heatmap/types.js +4 -0
- package/lib/module/activity-heatmap/types.js.map +1 -0
- package/lib/module/avatar-group/AvatarGroupBase.js +42 -18
- package/lib/module/avatar-group/AvatarGroupBase.js.map +1 -1
- package/lib/module/composition-bar/CompositionBar.js +154 -0
- package/lib/module/composition-bar/CompositionBar.js.map +1 -0
- package/lib/module/composition-bar/index.js +4 -0
- package/lib/module/composition-bar/index.js.map +1 -0
- package/lib/module/composition-bar/types.js +4 -0
- package/lib/module/composition-bar/types.js.map +1 -0
- package/lib/module/dot-grid-meter/DotGridMeter.js +64 -0
- package/lib/module/dot-grid-meter/DotGridMeter.js.map +1 -0
- package/lib/module/dot-grid-meter/index.js +4 -0
- package/lib/module/dot-grid-meter/index.js.map +1 -0
- package/lib/module/dot-grid-meter/types.js +4 -0
- package/lib/module/dot-grid-meter/types.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +6 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/profile-card/ProfileCard.js +296 -0
- package/lib/module/profile-card/ProfileCard.js.map +1 -0
- package/lib/module/profile-card/index.js +4 -0
- package/lib/module/profile-card/index.js.map +1 -0
- package/lib/module/profile-card/types.js +4 -0
- package/lib/module/profile-card/types.js.map +1 -0
- package/lib/module/stat-bar/StatBar.js +205 -0
- package/lib/module/stat-bar/StatBar.js.map +1 -0
- package/lib/module/stat-bar/index.js +4 -0
- package/lib/module/stat-bar/index.js.map +1 -0
- package/lib/module/stat-bar/types.js +4 -0
- package/lib/module/stat-bar/types.js.map +1 -0
- package/lib/module/typography/index.js +5 -6
- package/lib/module/typography/index.js.map +1 -1
- package/lib/module/typography/styled-text.js +12 -19
- package/lib/module/typography/styled-text.js.map +1 -1
- package/lib/typescript/commonjs/activity-heatmap/ActivityHeatmap.d.ts +4 -0
- package/lib/typescript/commonjs/activity-heatmap/ActivityHeatmap.d.ts.map +1 -0
- package/lib/typescript/commonjs/activity-heatmap/bucketByDay.d.ts +9 -0
- package/lib/typescript/commonjs/activity-heatmap/bucketByDay.d.ts.map +1 -0
- package/lib/typescript/commonjs/activity-heatmap/index.d.ts +4 -0
- package/lib/typescript/commonjs/activity-heatmap/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/activity-heatmap/types.d.ts +73 -0
- package/lib/typescript/commonjs/activity-heatmap/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/avatar-group/AvatarGroupBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/avatar-group/types.d.ts +15 -1
- package/lib/typescript/commonjs/avatar-group/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/composition-bar/CompositionBar.d.ts +4 -0
- package/lib/typescript/commonjs/composition-bar/CompositionBar.d.ts.map +1 -0
- package/lib/typescript/commonjs/composition-bar/index.d.ts +3 -0
- package/lib/typescript/commonjs/composition-bar/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/composition-bar/types.d.ts +29 -0
- package/lib/typescript/commonjs/composition-bar/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/dot-grid-meter/DotGridMeter.d.ts +4 -0
- package/lib/typescript/commonjs/dot-grid-meter/DotGridMeter.d.ts.map +1 -0
- package/lib/typescript/commonjs/dot-grid-meter/index.d.ts +3 -0
- package/lib/typescript/commonjs/dot-grid-meter/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/dot-grid-meter/types.d.ts +36 -0
- package/lib/typescript/commonjs/dot-grid-meter/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +10 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +10 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/profile-card/ProfileCard.d.ts +4 -0
- package/lib/typescript/commonjs/profile-card/ProfileCard.d.ts.map +1 -0
- package/lib/typescript/commonjs/profile-card/index.d.ts +3 -0
- package/lib/typescript/commonjs/profile-card/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/profile-card/types.d.ts +86 -0
- package/lib/typescript/commonjs/profile-card/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/stat-bar/StatBar.d.ts +4 -0
- package/lib/typescript/commonjs/stat-bar/StatBar.d.ts.map +1 -0
- package/lib/typescript/commonjs/stat-bar/index.d.ts +3 -0
- package/lib/typescript/commonjs/stat-bar/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/stat-bar/types.d.ts +62 -0
- package/lib/typescript/commonjs/stat-bar/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/typography/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/typography/styled-text.d.ts +1 -6
- package/lib/typescript/commonjs/typography/styled-text.d.ts.map +1 -1
- package/lib/typescript/module/activity-heatmap/ActivityHeatmap.d.ts +4 -0
- package/lib/typescript/module/activity-heatmap/ActivityHeatmap.d.ts.map +1 -0
- package/lib/typescript/module/activity-heatmap/bucketByDay.d.ts +9 -0
- package/lib/typescript/module/activity-heatmap/bucketByDay.d.ts.map +1 -0
- package/lib/typescript/module/activity-heatmap/index.d.ts +4 -0
- package/lib/typescript/module/activity-heatmap/index.d.ts.map +1 -0
- package/lib/typescript/module/activity-heatmap/types.d.ts +73 -0
- package/lib/typescript/module/activity-heatmap/types.d.ts.map +1 -0
- package/lib/typescript/module/avatar-group/AvatarGroupBase.d.ts.map +1 -1
- package/lib/typescript/module/avatar-group/types.d.ts +15 -1
- package/lib/typescript/module/avatar-group/types.d.ts.map +1 -1
- package/lib/typescript/module/composition-bar/CompositionBar.d.ts +4 -0
- package/lib/typescript/module/composition-bar/CompositionBar.d.ts.map +1 -0
- package/lib/typescript/module/composition-bar/index.d.ts +3 -0
- package/lib/typescript/module/composition-bar/index.d.ts.map +1 -0
- package/lib/typescript/module/composition-bar/types.d.ts +29 -0
- package/lib/typescript/module/composition-bar/types.d.ts.map +1 -0
- package/lib/typescript/module/dot-grid-meter/DotGridMeter.d.ts +4 -0
- package/lib/typescript/module/dot-grid-meter/DotGridMeter.d.ts.map +1 -0
- package/lib/typescript/module/dot-grid-meter/index.d.ts +3 -0
- package/lib/typescript/module/dot-grid-meter/index.d.ts.map +1 -0
- package/lib/typescript/module/dot-grid-meter/types.d.ts +36 -0
- package/lib/typescript/module/dot-grid-meter/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +10 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +10 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/profile-card/ProfileCard.d.ts +4 -0
- package/lib/typescript/module/profile-card/ProfileCard.d.ts.map +1 -0
- package/lib/typescript/module/profile-card/index.d.ts +3 -0
- package/lib/typescript/module/profile-card/index.d.ts.map +1 -0
- package/lib/typescript/module/profile-card/types.d.ts +86 -0
- package/lib/typescript/module/profile-card/types.d.ts.map +1 -0
- package/lib/typescript/module/stat-bar/StatBar.d.ts +4 -0
- package/lib/typescript/module/stat-bar/StatBar.d.ts.map +1 -0
- package/lib/typescript/module/stat-bar/index.d.ts +3 -0
- package/lib/typescript/module/stat-bar/index.d.ts.map +1 -0
- package/lib/typescript/module/stat-bar/types.d.ts +62 -0
- package/lib/typescript/module/stat-bar/types.d.ts.map +1 -0
- package/lib/typescript/module/typography/index.d.ts.map +1 -1
- package/lib/typescript/module/typography/styled-text.d.ts +1 -6
- package/lib/typescript/module/typography/styled-text.d.ts.map +1 -1
- package/package.json +56 -1
- package/src/__tests__/StatWidgets.test.tsx +184 -0
- package/src/__tests__/public-api-contract.test.ts +15 -0
- package/src/activity-heatmap/ActivityHeatmap.stories.tsx +108 -0
- package/src/activity-heatmap/ActivityHeatmap.tsx +289 -0
- package/src/activity-heatmap/bucketByDay.ts +49 -0
- package/src/activity-heatmap/index.ts +3 -0
- package/src/activity-heatmap/types.ts +74 -0
- package/src/avatar-group/AvatarGroup.stories.tsx +14 -0
- package/src/avatar-group/AvatarGroupBase.tsx +39 -18
- package/src/avatar-group/types.ts +15 -1
- package/src/composition-bar/CompositionBar.stories.tsx +70 -0
- package/src/composition-bar/CompositionBar.tsx +161 -0
- package/src/composition-bar/index.ts +2 -0
- package/src/composition-bar/types.ts +30 -0
- package/src/dot-grid-meter/DotGridMeter.stories.tsx +47 -0
- package/src/dot-grid-meter/DotGridMeter.tsx +71 -0
- package/src/dot-grid-meter/index.ts +2 -0
- package/src/dot-grid-meter/types.ts +36 -0
- package/src/index.ts +24 -0
- package/src/index.web.ts +24 -0
- package/src/profile-card/ProfileCard.stories.tsx +210 -0
- package/src/profile-card/ProfileCard.tsx +303 -0
- package/src/profile-card/index.ts +9 -0
- package/src/profile-card/types.ts +86 -0
- package/src/stat-bar/StatBar.stories.tsx +61 -0
- package/src/stat-bar/StatBar.tsx +156 -0
- package/src/stat-bar/index.ts +7 -0
- package/src/stat-bar/types.ts +65 -0
- package/src/typography/index.tsx +4 -6
- package/src/typography/styled-text.ts +11 -28
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { AvatarRingConfig } from '../avatar/types';
|
|
5
|
+
import type { AvatarGroupItem } from '../avatar-group/types';
|
|
6
|
+
|
|
7
|
+
export type ProfileCardLayout = 'widget' | 'wide';
|
|
8
|
+
export type ProfileCardVariant = 'wallet' | 'social' | 'shopping' | 'stat';
|
|
9
|
+
|
|
10
|
+
/** The avatar block of a {@link ProfileCard}. All values are consumer-supplied. */
|
|
11
|
+
export interface ProfileCardAvatar {
|
|
12
|
+
/**
|
|
13
|
+
* Avatar value, routed into {@link Avatar}'s `source` prop — a full URL or a
|
|
14
|
+
* resolver-handled id (e.g. an Oxy file ID).
|
|
15
|
+
*/
|
|
16
|
+
source?: string | null;
|
|
17
|
+
/** Decorative ring. Defaults to a solid ring in the variant accent color. */
|
|
18
|
+
ring?: AvatarRingConfig;
|
|
19
|
+
/** Badge overlay pinned to the avatar's bottom-right (e.g. a small logo). */
|
|
20
|
+
badge?: ReactNode;
|
|
21
|
+
/** Name used for the avatar's initial/placeholder and accessibility. */
|
|
22
|
+
name?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The metric visual rendered below the headline. */
|
|
26
|
+
export type ProfileCardMetric =
|
|
27
|
+
| { kind: 'dots'; label?: string; filled: number; total: number; filledColor?: string }
|
|
28
|
+
| {
|
|
29
|
+
kind: 'progress';
|
|
30
|
+
label: string;
|
|
31
|
+
value: number;
|
|
32
|
+
max: number;
|
|
33
|
+
minLabel?: string;
|
|
34
|
+
maxLabel?: string;
|
|
35
|
+
icon?: ReactNode;
|
|
36
|
+
fillColor?: string;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
kind: 'split';
|
|
40
|
+
label: string;
|
|
41
|
+
percent: number;
|
|
42
|
+
leftValue: string;
|
|
43
|
+
rightValue: string;
|
|
44
|
+
fillColor?: string;
|
|
45
|
+
}
|
|
46
|
+
| { kind: 'custom'; node: ReactNode };
|
|
47
|
+
|
|
48
|
+
/** The trailing facepile row (e.g. "Top tokens"). */
|
|
49
|
+
export interface ProfileCardFooter {
|
|
50
|
+
/** Small label above the row. */
|
|
51
|
+
label?: string;
|
|
52
|
+
/** Avatars rendered adjacent in a row. */
|
|
53
|
+
items: AvatarGroupItem[];
|
|
54
|
+
/** Maximum avatars before collapsing into `+N`. @default 4 */
|
|
55
|
+
max?: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ProfileCardProps {
|
|
59
|
+
/**
|
|
60
|
+
* Card shape. `'widget'` is a compact ~square card; `'wide'` is a full-width
|
|
61
|
+
* card. @default 'widget'
|
|
62
|
+
*/
|
|
63
|
+
layout?: ProfileCardLayout;
|
|
64
|
+
/**
|
|
65
|
+
* Selects sensible accent defaults (ring / metric fill). Every accent stays
|
|
66
|
+
* overridable per-element. @default 'stat'
|
|
67
|
+
*/
|
|
68
|
+
variant?: ProfileCardVariant;
|
|
69
|
+
/** Avatar block. */
|
|
70
|
+
avatar: ProfileCardAvatar;
|
|
71
|
+
/** Pre-formatted headline value (e.g. a balance or count). */
|
|
72
|
+
value: string;
|
|
73
|
+
/** Secondary line under the headline. */
|
|
74
|
+
subtitle?: string;
|
|
75
|
+
/** Node floated at the card's top-right (e.g. a trophy or menu icon). */
|
|
76
|
+
headlineIcon?: ReactNode;
|
|
77
|
+
/** Metric visual below the headline. */
|
|
78
|
+
metric?: ProfileCardMetric;
|
|
79
|
+
/** Trailing facepile row. */
|
|
80
|
+
footer?: ProfileCardFooter;
|
|
81
|
+
/** Press handler — makes the whole card pressable. */
|
|
82
|
+
onPress?: () => void;
|
|
83
|
+
/** Container style override (merged over the card surface). */
|
|
84
|
+
style?: StyleProp<ViewStyle>;
|
|
85
|
+
testID?: string;
|
|
86
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
|
|
5
|
+
import { StatBar } from './index';
|
|
6
|
+
import { BloomThemeProvider } from '../theme';
|
|
7
|
+
import { Star_Filled_Corner0_Rounded as StarIcon } from '../icons/Star';
|
|
8
|
+
|
|
9
|
+
function Demo() {
|
|
10
|
+
return (
|
|
11
|
+
<View style={{ gap: 28 }}>
|
|
12
|
+
<StatBar
|
|
13
|
+
variant="progress"
|
|
14
|
+
label="TX count 24h"
|
|
15
|
+
value={192}
|
|
16
|
+
max={350}
|
|
17
|
+
minLabel="32"
|
|
18
|
+
maxLabel="350"
|
|
19
|
+
icon={<StarIcon size="sm" style={{ color: '#F59E0B' }} />}
|
|
20
|
+
/>
|
|
21
|
+
<StatBar
|
|
22
|
+
variant="split"
|
|
23
|
+
label="Net flow 24h"
|
|
24
|
+
percent={62}
|
|
25
|
+
leftValue="+$4,210"
|
|
26
|
+
rightValue="-$2,560"
|
|
27
|
+
leftColor="#10B981"
|
|
28
|
+
rightColor="#EF4444"
|
|
29
|
+
/>
|
|
30
|
+
</View>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const meta: Meta<typeof StatBar> = {
|
|
35
|
+
title: 'Data Display/StatBar',
|
|
36
|
+
component: StatBar,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default meta;
|
|
40
|
+
|
|
41
|
+
type Story = StoryObj<typeof StatBar>;
|
|
42
|
+
|
|
43
|
+
export const Light: Story = {
|
|
44
|
+
render: () => (
|
|
45
|
+
<BloomThemeProvider mode="light">
|
|
46
|
+
<View style={{ padding: 24, width: 320 }}>
|
|
47
|
+
<Demo />
|
|
48
|
+
</View>
|
|
49
|
+
</BloomThemeProvider>
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Dark: Story = {
|
|
54
|
+
render: () => (
|
|
55
|
+
<BloomThemeProvider mode="dark">
|
|
56
|
+
<View style={{ padding: 24, width: 320, backgroundColor: '#000' }}>
|
|
57
|
+
<Demo />
|
|
58
|
+
</View>
|
|
59
|
+
</BloomThemeProvider>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../theme/use-theme';
|
|
5
|
+
import { Text } from '../typography';
|
|
6
|
+
import type { StatBarProps } from './types';
|
|
7
|
+
|
|
8
|
+
function clampPercent(value: number): number {
|
|
9
|
+
if (Number.isNaN(value)) return 0;
|
|
10
|
+
return Math.max(0, Math.min(100, value));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A horizontal labeled value bar with two variants:
|
|
15
|
+
*
|
|
16
|
+
* - `progress` — a rounded capsule track with a proportional `value / max`
|
|
17
|
+
* fill, an optional top-right `icon`, and an optional min/max footer row.
|
|
18
|
+
* - `split` — a capsule split at `percent` (left portion active, remainder the
|
|
19
|
+
* track color), a bold percentage right of the label, and a two-sided footer
|
|
20
|
+
* of opposing values (inflow/outflow style).
|
|
21
|
+
*
|
|
22
|
+
* Pure `<View>` composition, so it renders identically on web and native.
|
|
23
|
+
*/
|
|
24
|
+
const StatBarComponent: React.FC<StatBarProps> = (props) => {
|
|
25
|
+
const { colors } = useTheme();
|
|
26
|
+
const { label, fillColor, trackColor, height = 6, icon, style, testID } = props;
|
|
27
|
+
const fill = fillColor ?? colors.primary;
|
|
28
|
+
const track = trackColor ?? colors.backgroundSecondary;
|
|
29
|
+
const radius = height / 2;
|
|
30
|
+
|
|
31
|
+
if (props.variant === 'split') {
|
|
32
|
+
const { percent, leftValue, rightValue, leftColor, rightColor } = props;
|
|
33
|
+
const leftPercent = clampPercent(percent);
|
|
34
|
+
const activeLeft = leftColor ?? fill;
|
|
35
|
+
const remainder = rightColor ?? track;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<View style={[styles.wrap, style]} testID={testID}>
|
|
39
|
+
<View style={styles.labelRow}>
|
|
40
|
+
<Text style={[styles.label, { color: colors.textSecondary }]} numberOfLines={1}>
|
|
41
|
+
{label}
|
|
42
|
+
</Text>
|
|
43
|
+
<Text style={[styles.percent, { color: colors.text }]} numberOfLines={1}>
|
|
44
|
+
{Math.round(leftPercent)}%
|
|
45
|
+
</Text>
|
|
46
|
+
</View>
|
|
47
|
+
|
|
48
|
+
<View style={[styles.track, { height, borderRadius: radius, backgroundColor: remainder }]}>
|
|
49
|
+
<View
|
|
50
|
+
style={{
|
|
51
|
+
width: `${leftPercent}%`,
|
|
52
|
+
height,
|
|
53
|
+
borderRadius: radius,
|
|
54
|
+
backgroundColor: activeLeft,
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
</View>
|
|
58
|
+
|
|
59
|
+
<View style={styles.footerRow}>
|
|
60
|
+
<Text style={[styles.footerValue, { color: colors.textTertiary }]} numberOfLines={1}>
|
|
61
|
+
{leftValue}
|
|
62
|
+
</Text>
|
|
63
|
+
<Text style={[styles.footerValue, { color: colors.textTertiary }]} numberOfLines={1}>
|
|
64
|
+
{rightValue}
|
|
65
|
+
</Text>
|
|
66
|
+
</View>
|
|
67
|
+
</View>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const { value, max, minLabel, maxLabel } = props;
|
|
72
|
+
const ratio = max > 0 ? clampPercent((value / max) * 100) : 0;
|
|
73
|
+
const hasFooter = minLabel != null || maxLabel != null;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<View style={[styles.wrap, style]} testID={testID}>
|
|
77
|
+
<View style={styles.labelRow}>
|
|
78
|
+
<Text style={[styles.label, { color: colors.textSecondary }]} numberOfLines={1}>
|
|
79
|
+
{label}
|
|
80
|
+
</Text>
|
|
81
|
+
{icon != null && <View style={styles.icon}>{icon}</View>}
|
|
82
|
+
</View>
|
|
83
|
+
|
|
84
|
+
<View
|
|
85
|
+
style={[styles.track, { height, borderRadius: radius, backgroundColor: track }]}
|
|
86
|
+
accessibilityRole="progressbar"
|
|
87
|
+
accessibilityValue={{ min: 0, max, now: value }}
|
|
88
|
+
>
|
|
89
|
+
<View
|
|
90
|
+
style={{
|
|
91
|
+
width: `${ratio}%`,
|
|
92
|
+
height,
|
|
93
|
+
borderRadius: radius,
|
|
94
|
+
backgroundColor: fill,
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</View>
|
|
98
|
+
|
|
99
|
+
{hasFooter && (
|
|
100
|
+
<View style={styles.footerRow}>
|
|
101
|
+
<Text style={[styles.footerValue, { color: colors.textTertiary }]} numberOfLines={1}>
|
|
102
|
+
{minLabel ?? ''}
|
|
103
|
+
</Text>
|
|
104
|
+
<Text style={[styles.footerValue, { color: colors.textTertiary }]} numberOfLines={1}>
|
|
105
|
+
{maxLabel ?? ''}
|
|
106
|
+
</Text>
|
|
107
|
+
</View>
|
|
108
|
+
)}
|
|
109
|
+
</View>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const styles = StyleSheet.create({
|
|
114
|
+
wrap: {
|
|
115
|
+
gap: 8,
|
|
116
|
+
},
|
|
117
|
+
labelRow: {
|
|
118
|
+
flexDirection: 'row',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
justifyContent: 'space-between',
|
|
121
|
+
gap: 8,
|
|
122
|
+
},
|
|
123
|
+
label: {
|
|
124
|
+
flexShrink: 1,
|
|
125
|
+
fontSize: 13,
|
|
126
|
+
fontWeight: '500',
|
|
127
|
+
},
|
|
128
|
+
percent: {
|
|
129
|
+
fontSize: 14,
|
|
130
|
+
fontWeight: '700',
|
|
131
|
+
letterSpacing: -0.2,
|
|
132
|
+
fontVariant: ['tabular-nums'],
|
|
133
|
+
},
|
|
134
|
+
icon: {
|
|
135
|
+
marginLeft: 8,
|
|
136
|
+
},
|
|
137
|
+
track: {
|
|
138
|
+
width: '100%',
|
|
139
|
+
overflow: 'hidden',
|
|
140
|
+
flexDirection: 'row',
|
|
141
|
+
borderCurve: 'continuous',
|
|
142
|
+
},
|
|
143
|
+
footerRow: {
|
|
144
|
+
flexDirection: 'row',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
justifyContent: 'space-between',
|
|
147
|
+
gap: 8,
|
|
148
|
+
},
|
|
149
|
+
footerValue: {
|
|
150
|
+
fontSize: 12,
|
|
151
|
+
fontVariant: ['tabular-nums'],
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export const StatBar = memo(StatBarComponent);
|
|
156
|
+
StatBar.displayName = 'StatBar';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export type StatBarVariant = 'progress' | 'split';
|
|
5
|
+
|
|
6
|
+
interface StatBarBaseProps {
|
|
7
|
+
/** Label shown at the top-left of the bar. */
|
|
8
|
+
label: string;
|
|
9
|
+
/**
|
|
10
|
+
* Fill color of the (active portion of the) track.
|
|
11
|
+
* @default theme.colors.primary
|
|
12
|
+
*/
|
|
13
|
+
fillColor?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Color of the empty/track portion.
|
|
16
|
+
* @default theme.colors.backgroundSecondary
|
|
17
|
+
*/
|
|
18
|
+
trackColor?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Height of the capsule track in px.
|
|
21
|
+
* @default 6
|
|
22
|
+
*/
|
|
23
|
+
height?: number;
|
|
24
|
+
/** Optional node rendered at the top-right of the label row (e.g. a trophy icon). */
|
|
25
|
+
icon?: ReactNode;
|
|
26
|
+
/** Container style override. */
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
testID?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A labeled progress bar with an optional min/max footer row. */
|
|
32
|
+
export interface StatBarProgressProps extends StatBarBaseProps {
|
|
33
|
+
variant?: 'progress';
|
|
34
|
+
/** Current value; the fill spans `value / max` of the track. */
|
|
35
|
+
value: number;
|
|
36
|
+
/** Maximum value (the full track). */
|
|
37
|
+
max: number;
|
|
38
|
+
/** Optional label shown at the bottom-left (e.g. the range minimum). */
|
|
39
|
+
minLabel?: string;
|
|
40
|
+
/** Optional label shown at the bottom-right (e.g. the range maximum). */
|
|
41
|
+
maxLabel?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A two-sided split bar showing an inflow/outflow-style ratio. */
|
|
45
|
+
export interface StatBarSplitProps extends StatBarBaseProps {
|
|
46
|
+
variant: 'split';
|
|
47
|
+
/** Share of the track filled from the left, 0–100. Shown right of the label. */
|
|
48
|
+
percent: number;
|
|
49
|
+
/** Value label shown at the bottom-left. */
|
|
50
|
+
leftValue: string;
|
|
51
|
+
/** Value label shown at the bottom-right. */
|
|
52
|
+
rightValue: string;
|
|
53
|
+
/**
|
|
54
|
+
* Color of the left (active) portion.
|
|
55
|
+
* @default fillColor (theme.colors.primary)
|
|
56
|
+
*/
|
|
57
|
+
leftColor?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Color of the right portion.
|
|
60
|
+
* @default trackColor (theme.colors.backgroundSecondary)
|
|
61
|
+
*/
|
|
62
|
+
rightColor?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type StatBarProps = StatBarProgressProps | StatBarSplitProps;
|
package/src/typography/index.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { useTheme } from '../theme/use-theme';
|
|
9
9
|
import { fontFamilies } from '../fonts/tokens';
|
|
10
10
|
import { mergeTypographyStyle, typographyDefaultsWhenNoClassName } from './defaults';
|
|
11
|
-
import { getInteropText
|
|
11
|
+
import { getInteropText } from './styled-text';
|
|
12
12
|
|
|
13
13
|
export type TextProps = RNTextProps & {
|
|
14
14
|
className?: string;
|
|
@@ -50,16 +50,14 @@ const DEFAULT_TEXT_TYPOGRAPHY: TextStyle = {
|
|
|
50
50
|
const TextComponent = function Text({ children, style, className, ...rest }: TextProps) {
|
|
51
51
|
const { colors } = useTheme();
|
|
52
52
|
const InteropText = getInteropText();
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const resolvedClassName = interopActive && className?.trim() ? className : undefined;
|
|
53
|
+
const trimmedClassName = className?.trim() ? className : undefined;
|
|
56
54
|
|
|
57
55
|
return (
|
|
58
56
|
<InteropText
|
|
59
57
|
{...rest}
|
|
60
|
-
{...(
|
|
58
|
+
{...(trimmedClassName ? { className: trimmedClassName } : {})}
|
|
61
59
|
style={mergeTypographyStyle(
|
|
62
|
-
|
|
60
|
+
trimmedClassName,
|
|
63
61
|
{ ...DEFAULT_TEXT_TYPOGRAPHY, color: colors.text },
|
|
64
62
|
SANS_FONT_FAMILY,
|
|
65
63
|
style,
|
|
@@ -1,41 +1,24 @@
|
|
|
1
1
|
import type { ComponentType } from 'react';
|
|
2
2
|
import { Text as RNText, type TextProps } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import { lazyRequire } from '../utils/lazy-require';
|
|
3
|
+
import { styled } from 'react-native-css';
|
|
5
4
|
|
|
6
5
|
type StyledTextComponent = ComponentType<TextProps & { className?: string }>;
|
|
7
6
|
|
|
8
|
-
type NativeWindModule = {
|
|
9
|
-
styled: (
|
|
10
|
-
component: typeof RNText,
|
|
11
|
-
mapping?: { className: 'style' },
|
|
12
|
-
) => StyledTextComponent;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const getNativeWind = lazyRequire<NativeWindModule>('nativewind');
|
|
16
|
-
|
|
17
|
-
let interopText: StyledTextComponent = RNText;
|
|
18
|
-
|
|
19
7
|
/**
|
|
20
|
-
* RN `Text` wired for NativeWind `className` → `style` interop
|
|
21
|
-
*
|
|
22
|
-
*
|
|
8
|
+
* RN `Text` wired for NativeWind `className` → `style` interop.
|
|
9
|
+
*
|
|
10
|
+
* Import `styled` from Bloom's direct dependency `react-native-css` — not via
|
|
11
|
+
* lazy `require('nativewind')`. Metro cannot reliably bundle dynamic requires
|
|
12
|
+
* from published `@oxyhq/bloom/lib`, so the 0.29.4 lazy path left interop
|
|
13
|
+
* inactive at runtime and Bloom defaults (fontSize 13) overrode hero utilities.
|
|
23
14
|
*/
|
|
24
|
-
|
|
25
|
-
if (interopText !== RNText) {
|
|
26
|
-
return interopText;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const nativewind = getNativeWind();
|
|
30
|
-
if (nativewind?.styled) {
|
|
31
|
-
interopText = nativewind.styled(RNText, { className: 'style' });
|
|
32
|
-
}
|
|
15
|
+
const interopText: StyledTextComponent = styled(RNText, { className: 'style' });
|
|
33
16
|
|
|
17
|
+
export function getInteropText(): StyledTextComponent {
|
|
34
18
|
return interopText;
|
|
35
19
|
}
|
|
36
20
|
|
|
37
|
-
/** True when className interop is active (
|
|
21
|
+
/** True when className interop is active (always — react-native-css is a direct dep). */
|
|
38
22
|
export function hasTypographyClassNameInterop(): boolean {
|
|
39
|
-
|
|
40
|
-
return interopText !== RNText;
|
|
23
|
+
return true;
|
|
41
24
|
}
|