@oxyhq/bloom 0.1.14 → 0.1.16
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/accordion/Accordion.js +230 -0
- package/lib/commonjs/accordion/Accordion.js.map +1 -0
- package/lib/commonjs/accordion/index.js +31 -0
- package/lib/commonjs/accordion/index.js.map +1 -0
- package/lib/commonjs/accordion/types.js +6 -0
- package/lib/commonjs/accordion/types.js.map +1 -0
- package/lib/commonjs/avatar/Avatar.js +19 -7
- package/lib/commonjs/avatar/Avatar.js.map +1 -1
- package/lib/commonjs/badge/Badge.js +173 -0
- package/lib/commonjs/badge/Badge.js.map +1 -0
- package/lib/commonjs/badge/index.js +13 -0
- package/lib/commonjs/badge/index.js.map +1 -0
- package/lib/commonjs/badge/types.js +6 -0
- package/lib/commonjs/badge/types.js.map +1 -0
- package/lib/commonjs/bottom-sheet/index.js +32 -14
- package/lib/commonjs/bottom-sheet/index.js.map +1 -1
- package/lib/commonjs/card/Card.js +165 -0
- package/lib/commonjs/card/Card.js.map +1 -0
- package/lib/commonjs/card/index.js +43 -0
- package/lib/commonjs/card/index.js.map +1 -0
- package/lib/commonjs/card/types.js +6 -0
- package/lib/commonjs/card/types.js.map +1 -0
- package/lib/commonjs/checkbox/Checkbox.js +177 -0
- package/lib/commonjs/checkbox/Checkbox.js.map +1 -0
- package/lib/commonjs/checkbox/index.js +13 -0
- package/lib/commonjs/checkbox/index.js.map +1 -0
- package/lib/commonjs/checkbox/types.js +6 -0
- package/lib/commonjs/checkbox/types.js.map +1 -0
- package/lib/commonjs/chip/Chip.js +180 -0
- package/lib/commonjs/chip/Chip.js.map +1 -0
- package/lib/commonjs/chip/index.js +13 -0
- package/lib/commonjs/chip/index.js.map +1 -0
- package/lib/commonjs/chip/types.js +6 -0
- package/lib/commonjs/chip/types.js.map +1 -0
- package/lib/commonjs/index.js +56 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/tabs/Tabs.js +202 -0
- package/lib/commonjs/tabs/Tabs.js.map +1 -0
- package/lib/commonjs/tabs/index.js +25 -0
- package/lib/commonjs/tabs/index.js.map +1 -0
- package/lib/commonjs/tabs/types.js +6 -0
- package/lib/commonjs/tabs/types.js.map +1 -0
- package/lib/module/accordion/Accordion.js +225 -0
- package/lib/module/accordion/Accordion.js.map +1 -0
- package/lib/module/accordion/index.js +4 -0
- package/lib/module/accordion/index.js.map +1 -0
- package/lib/module/accordion/types.js +4 -0
- package/lib/module/accordion/types.js.map +1 -0
- package/lib/module/avatar/Avatar.js +19 -7
- package/lib/module/avatar/Avatar.js.map +1 -1
- package/lib/module/badge/Badge.js +168 -0
- package/lib/module/badge/Badge.js.map +1 -0
- package/lib/module/badge/index.js +4 -0
- package/lib/module/badge/index.js.map +1 -0
- package/lib/module/badge/types.js +4 -0
- package/lib/module/badge/types.js.map +1 -0
- package/lib/module/bottom-sheet/index.js +32 -14
- package/lib/module/bottom-sheet/index.js.map +1 -1
- package/lib/module/card/Card.js +160 -0
- package/lib/module/card/Card.js.map +1 -0
- package/lib/module/card/index.js +4 -0
- package/lib/module/card/index.js.map +1 -0
- package/lib/module/card/types.js +4 -0
- package/lib/module/card/types.js.map +1 -0
- package/lib/module/checkbox/Checkbox.js +172 -0
- package/lib/module/checkbox/Checkbox.js.map +1 -0
- package/lib/module/checkbox/index.js +4 -0
- package/lib/module/checkbox/index.js.map +1 -0
- package/lib/module/checkbox/types.js +4 -0
- package/lib/module/checkbox/types.js.map +1 -0
- package/lib/module/chip/Chip.js +175 -0
- package/lib/module/chip/Chip.js.map +1 -0
- package/lib/module/chip/index.js +4 -0
- package/lib/module/chip/index.js.map +1 -0
- package/lib/module/chip/types.js +4 -0
- package/lib/module/chip/types.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/tabs/Tabs.js +197 -0
- package/lib/module/tabs/Tabs.js.map +1 -0
- package/lib/module/tabs/index.js +4 -0
- package/lib/module/tabs/index.js.map +1 -0
- package/lib/module/tabs/types.js +4 -0
- package/lib/module/tabs/types.js.map +1 -0
- package/lib/typescript/commonjs/__tests__/BloomThemeProvider.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/BloomThemeProvider.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/BottomSheet.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/BottomSheet.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/Button.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/Button.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/theme.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/theme.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/Accordion.d.ts +7 -0
- package/lib/typescript/commonjs/accordion/Accordion.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/index.d.ts +3 -0
- package/lib/typescript/commonjs/accordion/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/types.d.ts +38 -0
- package/lib/typescript/commonjs/accordion/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/commonjs/avatar/types.d.ts +4 -0
- package/lib/typescript/commonjs/avatar/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/badge/Badge.d.ts +4 -0
- package/lib/typescript/commonjs/badge/Badge.d.ts.map +1 -0
- package/lib/typescript/commonjs/badge/index.d.ts +3 -0
- package/lib/typescript/commonjs/badge/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/badge/types.d.ts +29 -0
- package/lib/typescript/commonjs/badge/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/card/Card.d.ts +9 -0
- package/lib/typescript/commonjs/card/Card.d.ts.map +1 -0
- package/lib/typescript/commonjs/card/index.d.ts +3 -0
- package/lib/typescript/commonjs/card/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/card/types.d.ts +34 -0
- package/lib/typescript/commonjs/card/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/Checkbox.d.ts +4 -0
- package/lib/typescript/commonjs/checkbox/Checkbox.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/index.d.ts +3 -0
- package/lib/typescript/commonjs/checkbox/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/types.d.ts +25 -0
- package/lib/typescript/commonjs/checkbox/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/Chip.d.ts +4 -0
- package/lib/typescript/commonjs/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/index.d.ts +3 -0
- package/lib/typescript/commonjs/chip/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/types.d.ts +31 -0
- package/lib/typescript/commonjs/chip/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +6 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/tabs/Tabs.d.ts +6 -0
- package/lib/typescript/commonjs/tabs/Tabs.d.ts.map +1 -0
- package/lib/typescript/commonjs/tabs/index.d.ts +3 -0
- package/lib/typescript/commonjs/tabs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/tabs/types.d.ts +34 -0
- package/lib/typescript/commonjs/tabs/types.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/BloomThemeProvider.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/BloomThemeProvider.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/BottomSheet.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/BottomSheet.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/Button.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/Button.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/theme.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/theme.test.d.ts.map +1 -0
- package/lib/typescript/module/accordion/Accordion.d.ts +7 -0
- package/lib/typescript/module/accordion/Accordion.d.ts.map +1 -0
- package/lib/typescript/module/accordion/index.d.ts +3 -0
- package/lib/typescript/module/accordion/index.d.ts.map +1 -0
- package/lib/typescript/module/accordion/types.d.ts +38 -0
- package/lib/typescript/module/accordion/types.d.ts.map +1 -0
- package/lib/typescript/module/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/module/avatar/types.d.ts +4 -0
- package/lib/typescript/module/avatar/types.d.ts.map +1 -1
- package/lib/typescript/module/badge/Badge.d.ts +4 -0
- package/lib/typescript/module/badge/Badge.d.ts.map +1 -0
- package/lib/typescript/module/badge/index.d.ts +3 -0
- package/lib/typescript/module/badge/index.d.ts.map +1 -0
- package/lib/typescript/module/badge/types.d.ts +29 -0
- package/lib/typescript/module/badge/types.d.ts.map +1 -0
- package/lib/typescript/module/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/module/card/Card.d.ts +9 -0
- package/lib/typescript/module/card/Card.d.ts.map +1 -0
- package/lib/typescript/module/card/index.d.ts +3 -0
- package/lib/typescript/module/card/index.d.ts.map +1 -0
- package/lib/typescript/module/card/types.d.ts +34 -0
- package/lib/typescript/module/card/types.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/Checkbox.d.ts +4 -0
- package/lib/typescript/module/checkbox/Checkbox.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/index.d.ts +3 -0
- package/lib/typescript/module/checkbox/index.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/types.d.ts +25 -0
- package/lib/typescript/module/checkbox/types.d.ts.map +1 -0
- package/lib/typescript/module/chip/Chip.d.ts +4 -0
- package/lib/typescript/module/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/module/chip/index.d.ts +3 -0
- package/lib/typescript/module/chip/index.d.ts.map +1 -0
- package/lib/typescript/module/chip/types.d.ts +31 -0
- package/lib/typescript/module/chip/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +6 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/tabs/Tabs.d.ts +6 -0
- package/lib/typescript/module/tabs/Tabs.d.ts.map +1 -0
- package/lib/typescript/module/tabs/index.d.ts +3 -0
- package/lib/typescript/module/tabs/index.d.ts.map +1 -0
- package/lib/typescript/module/tabs/types.d.ts +34 -0
- package/lib/typescript/module/tabs/types.d.ts.map +1 -0
- package/package.json +79 -1
- package/src/__tests__/BloomThemeProvider.test.tsx +160 -0
- package/src/__tests__/BottomSheet.test.tsx +109 -0
- package/src/__tests__/Button.test.tsx +98 -0
- package/src/__tests__/theme.test.ts +148 -0
- package/src/accordion/Accordion.tsx +261 -0
- package/src/accordion/index.ts +8 -0
- package/src/accordion/types.ts +42 -0
- package/src/avatar/Avatar.tsx +16 -6
- package/src/avatar/types.ts +4 -0
- package/src/badge/Badge.tsx +151 -0
- package/src/badge/index.ts +8 -0
- package/src/badge/types.ts +30 -0
- package/src/bottom-sheet/index.tsx +30 -11
- package/src/card/Card.tsx +197 -0
- package/src/card/index.ts +10 -0
- package/src/card/types.ts +40 -0
- package/src/checkbox/Checkbox.tsx +166 -0
- package/src/checkbox/index.ts +2 -0
- package/src/checkbox/types.ts +26 -0
- package/src/chip/Chip.tsx +156 -0
- package/src/chip/index.ts +2 -0
- package/src/chip/types.ts +32 -0
- package/src/index.ts +8 -0
- package/src/tabs/Tabs.tsx +218 -0
- package/src/tabs/index.ts +2 -0
- package/src/tabs/types.ts +37 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APP_COLOR_PRESETS,
|
|
3
|
+
APP_COLOR_NAMES,
|
|
4
|
+
HEX_TO_APP_COLOR,
|
|
5
|
+
hexToAppColorName,
|
|
6
|
+
} from '../theme/color-presets';
|
|
7
|
+
import type { Theme, ThemeColors, ThemeMode } from '../theme/types';
|
|
8
|
+
|
|
9
|
+
describe('Theme system', () => {
|
|
10
|
+
describe('color presets', () => {
|
|
11
|
+
it('has all named presets defined', () => {
|
|
12
|
+
for (const name of APP_COLOR_NAMES) {
|
|
13
|
+
expect(APP_COLOR_PRESETS[name]).toBeDefined();
|
|
14
|
+
expect(APP_COLOR_PRESETS[name]!.name).toBe(name);
|
|
15
|
+
expect(APP_COLOR_PRESETS[name]!.hex).toMatch(/^#[0-9a-f]{6}$/i);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('each preset has both light and dark color variables', () => {
|
|
20
|
+
const requiredVars = [
|
|
21
|
+
'--background',
|
|
22
|
+
'--foreground',
|
|
23
|
+
'--surface',
|
|
24
|
+
'--primary',
|
|
25
|
+
'--border',
|
|
26
|
+
'--muted',
|
|
27
|
+
'--muted-foreground',
|
|
28
|
+
'--destructive',
|
|
29
|
+
'--input',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const name of APP_COLOR_NAMES) {
|
|
33
|
+
const preset = APP_COLOR_PRESETS[name]!;
|
|
34
|
+
for (const v of requiredVars) {
|
|
35
|
+
expect(preset.light[v]).toBeDefined();
|
|
36
|
+
expect(preset.dark[v]).toBeDefined();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('light and dark variants have different values', () => {
|
|
42
|
+
for (const name of APP_COLOR_NAMES) {
|
|
43
|
+
const preset = APP_COLOR_PRESETS[name]!;
|
|
44
|
+
// Background should differ between light and dark
|
|
45
|
+
expect(preset.light['--background']).not.toBe(preset.dark['--background']);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('hexToAppColorName', () => {
|
|
51
|
+
it('maps known hex values to correct color names', () => {
|
|
52
|
+
expect(hexToAppColorName('#005c67')).toBe('teal');
|
|
53
|
+
expect(hexToAppColorName('#1d9bf0')).toBe('blue');
|
|
54
|
+
expect(hexToAppColorName('#10b981')).toBe('green');
|
|
55
|
+
expect(hexToAppColorName('#ef4444')).toBe('red');
|
|
56
|
+
expect(hexToAppColorName('#c46ede')).toBe('oxy');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('is case-insensitive', () => {
|
|
60
|
+
expect(hexToAppColorName('#005C67')).toBe('teal');
|
|
61
|
+
expect(hexToAppColorName('#1D9BF0')).toBe('blue');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('defaults to teal for unknown hex values', () => {
|
|
65
|
+
expect(hexToAppColorName('#000000')).toBe('teal');
|
|
66
|
+
expect(hexToAppColorName('#ffffff')).toBe('teal');
|
|
67
|
+
expect(hexToAppColorName('#123456')).toBe('teal');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('HEX_TO_APP_COLOR mapping', () => {
|
|
72
|
+
it('has a hex entry for every named preset', () => {
|
|
73
|
+
const mappedNames = new Set(Object.values(HEX_TO_APP_COLOR));
|
|
74
|
+
for (const name of APP_COLOR_NAMES) {
|
|
75
|
+
expect(mappedNames.has(name)).toBe(true);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('hex keys match the preset hex values', () => {
|
|
80
|
+
for (const [hex, name] of Object.entries(HEX_TO_APP_COLOR)) {
|
|
81
|
+
const preset = APP_COLOR_PRESETS[name];
|
|
82
|
+
expect(preset).toBeDefined();
|
|
83
|
+
expect(preset!.hex.toLowerCase()).toBe(hex.toLowerCase());
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('Theme type shape', () => {
|
|
89
|
+
it('supports light and dark modes with isDark/isLight flags', () => {
|
|
90
|
+
const lightTheme: Theme = {
|
|
91
|
+
mode: 'light',
|
|
92
|
+
isDark: false,
|
|
93
|
+
isLight: true,
|
|
94
|
+
colors: createMockColors(),
|
|
95
|
+
};
|
|
96
|
+
expect(lightTheme.isDark).toBe(false);
|
|
97
|
+
expect(lightTheme.isLight).toBe(true);
|
|
98
|
+
|
|
99
|
+
const darkTheme: Theme = {
|
|
100
|
+
mode: 'dark',
|
|
101
|
+
isDark: true,
|
|
102
|
+
isLight: false,
|
|
103
|
+
colors: createMockColors(),
|
|
104
|
+
};
|
|
105
|
+
expect(darkTheme.isDark).toBe(true);
|
|
106
|
+
expect(darkTheme.isLight).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('ThemeMode includes all expected variants', () => {
|
|
110
|
+
const modes: ThemeMode[] = ['light', 'dark', 'system', 'adaptive'];
|
|
111
|
+
expect(modes).toHaveLength(4);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function createMockColors(): ThemeColors {
|
|
117
|
+
return {
|
|
118
|
+
background: '#fff',
|
|
119
|
+
backgroundSecondary: '#f5f5f5',
|
|
120
|
+
backgroundTertiary: '#eee',
|
|
121
|
+
text: '#000',
|
|
122
|
+
textSecondary: '#666',
|
|
123
|
+
textTertiary: '#999',
|
|
124
|
+
border: '#ccc',
|
|
125
|
+
borderLight: '#ddd',
|
|
126
|
+
primary: '#005c67',
|
|
127
|
+
primaryLight: '#e0f7fa',
|
|
128
|
+
primaryDark: '#003d44',
|
|
129
|
+
secondary: '#005c67',
|
|
130
|
+
tint: '#005c67',
|
|
131
|
+
icon: '#666',
|
|
132
|
+
iconActive: '#005c67',
|
|
133
|
+
success: '#10B981',
|
|
134
|
+
error: '#EF4444',
|
|
135
|
+
warning: '#F59E0B',
|
|
136
|
+
info: '#3B82F6',
|
|
137
|
+
primarySubtle: '#e0f7fa',
|
|
138
|
+
primarySubtleForeground: '#003d44',
|
|
139
|
+
negative: '#B91C1C',
|
|
140
|
+
negativeForeground: '#FFFFFF',
|
|
141
|
+
negativeSubtle: '#fef2f2',
|
|
142
|
+
negativeSubtleForeground: '#B91C1C',
|
|
143
|
+
contrast50: '#f5f5f5',
|
|
144
|
+
card: '#f5f5f5',
|
|
145
|
+
shadow: 'rgba(0,0,0,0.1)',
|
|
146
|
+
overlay: 'rgba(0,0,0,0.5)',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import React, { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { View, Text, Pressable, Animated, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../theme/use-theme';
|
|
5
|
+
import { animation, borderRadius, space } from '../styles/tokens';
|
|
6
|
+
import type {
|
|
7
|
+
AccordionProps,
|
|
8
|
+
AccordionItemProps,
|
|
9
|
+
AccordionTriggerProps,
|
|
10
|
+
AccordionContentProps,
|
|
11
|
+
AccordionType,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
// ---- Context ----
|
|
15
|
+
|
|
16
|
+
interface AccordionContextValue {
|
|
17
|
+
expandedValues: Set<string>;
|
|
18
|
+
toggle: (value: string) => void;
|
|
19
|
+
type: AccordionType;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const AccordionContext = createContext<AccordionContextValue>({
|
|
23
|
+
expandedValues: new Set(),
|
|
24
|
+
toggle: () => {},
|
|
25
|
+
type: 'single',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
interface AccordionItemContextValue {
|
|
29
|
+
value: string;
|
|
30
|
+
isExpanded: boolean;
|
|
31
|
+
disabled: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const AccordionItemContext = createContext<AccordionItemContextValue>({
|
|
35
|
+
value: '',
|
|
36
|
+
isExpanded: false,
|
|
37
|
+
disabled: false,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ---- Accordion Root ----
|
|
41
|
+
|
|
42
|
+
const AccordionComponent: React.FC<AccordionProps> = ({
|
|
43
|
+
value,
|
|
44
|
+
onValueChange,
|
|
45
|
+
type = 'single',
|
|
46
|
+
children,
|
|
47
|
+
style,
|
|
48
|
+
testID,
|
|
49
|
+
}) => {
|
|
50
|
+
const expandedValues = useMemo(() => {
|
|
51
|
+
if (value == null) return new Set<string>();
|
|
52
|
+
if (Array.isArray(value)) return new Set(value);
|
|
53
|
+
return new Set([value]);
|
|
54
|
+
}, [value]);
|
|
55
|
+
|
|
56
|
+
const toggle = useCallback(
|
|
57
|
+
(itemValue: string) => {
|
|
58
|
+
if (type === 'single') {
|
|
59
|
+
const next = expandedValues.has(itemValue) ? undefined : itemValue;
|
|
60
|
+
onValueChange(next);
|
|
61
|
+
} else {
|
|
62
|
+
const next = new Set(expandedValues);
|
|
63
|
+
if (next.has(itemValue)) {
|
|
64
|
+
next.delete(itemValue);
|
|
65
|
+
} else {
|
|
66
|
+
next.add(itemValue);
|
|
67
|
+
}
|
|
68
|
+
onValueChange(Array.from(next));
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
[type, expandedValues, onValueChange],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const contextValue = useMemo(
|
|
75
|
+
() => ({ expandedValues, toggle, type }),
|
|
76
|
+
[expandedValues, toggle, type],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<AccordionContext.Provider value={contextValue}>
|
|
81
|
+
<View style={style} testID={testID}>
|
|
82
|
+
{children}
|
|
83
|
+
</View>
|
|
84
|
+
</AccordionContext.Provider>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// ---- Accordion Item ----
|
|
89
|
+
|
|
90
|
+
const AccordionItemComponent: React.FC<AccordionItemProps> = ({
|
|
91
|
+
value,
|
|
92
|
+
children,
|
|
93
|
+
disabled = false,
|
|
94
|
+
style,
|
|
95
|
+
}) => {
|
|
96
|
+
const { expandedValues } = useContext(AccordionContext);
|
|
97
|
+
const isExpanded = expandedValues.has(value);
|
|
98
|
+
const theme = useTheme();
|
|
99
|
+
|
|
100
|
+
const itemContext = useMemo(
|
|
101
|
+
() => ({ value, isExpanded, disabled }),
|
|
102
|
+
[value, isExpanded, disabled],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<AccordionItemContext.Provider value={itemContext}>
|
|
107
|
+
<View
|
|
108
|
+
style={[
|
|
109
|
+
{
|
|
110
|
+
borderBottomWidth: 1,
|
|
111
|
+
borderBottomColor: theme.colors.borderLight,
|
|
112
|
+
},
|
|
113
|
+
style,
|
|
114
|
+
]}
|
|
115
|
+
>
|
|
116
|
+
{children}
|
|
117
|
+
</View>
|
|
118
|
+
</AccordionItemContext.Provider>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// ---- Accordion Trigger ----
|
|
123
|
+
|
|
124
|
+
const AccordionTriggerComponent: React.FC<AccordionTriggerProps> = ({
|
|
125
|
+
children,
|
|
126
|
+
icon,
|
|
127
|
+
style,
|
|
128
|
+
textStyle,
|
|
129
|
+
}) => {
|
|
130
|
+
const theme = useTheme();
|
|
131
|
+
const { toggle } = useContext(AccordionContext);
|
|
132
|
+
const { value, isExpanded, disabled } = useContext(AccordionItemContext);
|
|
133
|
+
const rotateAnim = useRef(new Animated.Value(isExpanded ? 1 : 0)).current;
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
Animated.spring(rotateAnim, {
|
|
137
|
+
toValue: isExpanded ? 1 : 0,
|
|
138
|
+
useNativeDriver: true,
|
|
139
|
+
...animation.spring.snappy,
|
|
140
|
+
}).start();
|
|
141
|
+
}, [isExpanded, rotateAnim]);
|
|
142
|
+
|
|
143
|
+
const handlePress = useCallback(() => {
|
|
144
|
+
if (!disabled) {
|
|
145
|
+
toggle(value);
|
|
146
|
+
}
|
|
147
|
+
}, [value, disabled, toggle]);
|
|
148
|
+
|
|
149
|
+
const rotation = rotateAnim.interpolate({
|
|
150
|
+
inputRange: [0, 1],
|
|
151
|
+
outputRange: ['0deg', '180deg'],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Pressable
|
|
156
|
+
style={({ pressed }) => [
|
|
157
|
+
{
|
|
158
|
+
flexDirection: 'row',
|
|
159
|
+
alignItems: 'center',
|
|
160
|
+
paddingVertical: space.md,
|
|
161
|
+
paddingHorizontal: space.xs,
|
|
162
|
+
gap: space.sm,
|
|
163
|
+
opacity: disabled ? 0.4 : pressed ? 0.7 : 1,
|
|
164
|
+
},
|
|
165
|
+
style,
|
|
166
|
+
]}
|
|
167
|
+
onPress={handlePress}
|
|
168
|
+
disabled={disabled}
|
|
169
|
+
accessibilityRole="button"
|
|
170
|
+
accessibilityState={{ expanded: isExpanded, disabled }}
|
|
171
|
+
>
|
|
172
|
+
{icon}
|
|
173
|
+
<View style={{ flex: 1 }}>
|
|
174
|
+
{typeof children === 'string' ? (
|
|
175
|
+
<Text
|
|
176
|
+
style={[
|
|
177
|
+
{
|
|
178
|
+
fontSize: 15,
|
|
179
|
+
fontWeight: '600',
|
|
180
|
+
color: theme.colors.text,
|
|
181
|
+
},
|
|
182
|
+
textStyle,
|
|
183
|
+
]}
|
|
184
|
+
>
|
|
185
|
+
{children}
|
|
186
|
+
</Text>
|
|
187
|
+
) : (
|
|
188
|
+
children
|
|
189
|
+
)}
|
|
190
|
+
</View>
|
|
191
|
+
<Animated.View style={{ transform: [{ rotate: rotation }] }}>
|
|
192
|
+
<Text
|
|
193
|
+
style={{
|
|
194
|
+
fontSize: 16,
|
|
195
|
+
color: theme.colors.textSecondary,
|
|
196
|
+
lineHeight: 18,
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
{'\u25BE'}
|
|
200
|
+
</Text>
|
|
201
|
+
</Animated.View>
|
|
202
|
+
</Pressable>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// ---- Accordion Content ----
|
|
207
|
+
|
|
208
|
+
const AccordionContentComponent: React.FC<AccordionContentProps> = ({
|
|
209
|
+
children,
|
|
210
|
+
style,
|
|
211
|
+
}) => {
|
|
212
|
+
const { isExpanded } = useContext(AccordionItemContext);
|
|
213
|
+
const heightAnim = useRef(new Animated.Value(isExpanded ? 1 : 0)).current;
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
Animated.spring(heightAnim, {
|
|
217
|
+
toValue: isExpanded ? 1 : 0,
|
|
218
|
+
useNativeDriver: false,
|
|
219
|
+
...animation.spring.gentle,
|
|
220
|
+
}).start();
|
|
221
|
+
}, [isExpanded, heightAnim]);
|
|
222
|
+
|
|
223
|
+
const opacity = heightAnim.interpolate({
|
|
224
|
+
inputRange: [0, 1],
|
|
225
|
+
outputRange: [0, 1],
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const maxHeight = heightAnim.interpolate({
|
|
229
|
+
inputRange: [0, 1],
|
|
230
|
+
outputRange: [0, 500], // reasonable max; content will use its natural height
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<Animated.View
|
|
235
|
+
style={[
|
|
236
|
+
{
|
|
237
|
+
overflow: 'hidden',
|
|
238
|
+
opacity,
|
|
239
|
+
maxHeight,
|
|
240
|
+
},
|
|
241
|
+
style,
|
|
242
|
+
]}
|
|
243
|
+
>
|
|
244
|
+
<View style={{ paddingBottom: space.md, paddingHorizontal: space.xs }}>
|
|
245
|
+
{children}
|
|
246
|
+
</View>
|
|
247
|
+
</Animated.View>
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export const Accordion = memo(AccordionComponent);
|
|
252
|
+
Accordion.displayName = 'Accordion';
|
|
253
|
+
|
|
254
|
+
export const AccordionItem = memo(AccordionItemComponent);
|
|
255
|
+
AccordionItem.displayName = 'AccordionItem';
|
|
256
|
+
|
|
257
|
+
export const AccordionTrigger = memo(AccordionTriggerComponent);
|
|
258
|
+
AccordionTrigger.displayName = 'AccordionTrigger';
|
|
259
|
+
|
|
260
|
+
export const AccordionContent = memo(AccordionContentComponent);
|
|
261
|
+
AccordionContent.displayName = 'AccordionContent';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type AccordionType = 'single' | 'multiple';
|
|
4
|
+
|
|
5
|
+
export interface AccordionProps {
|
|
6
|
+
/** Controls which items are expanded. For 'single' type, pass a string or undefined.
|
|
7
|
+
* For 'multiple' type, pass an array of strings. */
|
|
8
|
+
value: string | string[] | undefined;
|
|
9
|
+
/** Called when expanded items change. */
|
|
10
|
+
onValueChange: (value: string | string[] | undefined) => void;
|
|
11
|
+
/** Whether only one item can be expanded at a time. */
|
|
12
|
+
type?: AccordionType;
|
|
13
|
+
/** Accordion items. */
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
style?: StyleProp<ViewStyle>;
|
|
16
|
+
testID?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AccordionItemProps {
|
|
20
|
+
/** Unique value identifying this item. */
|
|
21
|
+
value: string;
|
|
22
|
+
/** Item content: should be AccordionTrigger and AccordionContent. */
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
/** Whether this item is disabled. */
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
style?: StyleProp<ViewStyle>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AccordionTriggerProps {
|
|
30
|
+
/** Trigger content (label text). */
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
/** Icon to show on the left side. */
|
|
33
|
+
icon?: React.ReactNode;
|
|
34
|
+
style?: StyleProp<ViewStyle>;
|
|
35
|
+
textStyle?: StyleProp<TextStyle>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AccordionContentProps {
|
|
39
|
+
/** Content to show when expanded. */
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
style?: StyleProp<ViewStyle>;
|
|
42
|
+
}
|
package/src/avatar/Avatar.tsx
CHANGED
|
@@ -37,18 +37,20 @@ function SquircleImage({
|
|
|
37
37
|
fallbackSource,
|
|
38
38
|
size,
|
|
39
39
|
fallbackColor,
|
|
40
|
+
placeholderIcon,
|
|
40
41
|
onError,
|
|
41
42
|
}: {
|
|
42
43
|
uri?: string;
|
|
43
44
|
fallbackSource?: AvatarProps['fallbackSource'];
|
|
44
45
|
size: number;
|
|
45
46
|
fallbackColor: string;
|
|
47
|
+
placeholderIcon?: React.ReactNode;
|
|
46
48
|
onError: () => void;
|
|
47
49
|
}) {
|
|
48
50
|
const svg = getSvgModule();
|
|
49
51
|
if (!svg) {
|
|
50
52
|
// Fallback to circle if react-native-svg is not installed
|
|
51
|
-
return <CircleFallback size={size} fallbackColor={fallbackColor} />;
|
|
53
|
+
return <CircleFallback size={size} fallbackColor={fallbackColor} icon={placeholderIcon} />;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
const { default: Svg, Defs, ClipPath, Path, Image: SvgImage } = svg;
|
|
@@ -56,7 +58,7 @@ function SquircleImage({
|
|
|
56
58
|
|
|
57
59
|
const href = uri ? { uri } : fallbackSource;
|
|
58
60
|
if (!href) {
|
|
59
|
-
return <CircleFallback size={size} fallbackColor={fallbackColor} />;
|
|
61
|
+
return <CircleFallback size={size} fallbackColor={fallbackColor} icon={placeholderIcon} />;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
return (
|
|
@@ -87,7 +89,7 @@ function SquircleImage({
|
|
|
87
89
|
);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
function CircleFallback({ size, fallbackColor }: { size: number; fallbackColor: string }) {
|
|
92
|
+
function CircleFallback({ size, fallbackColor, icon }: { size: number; fallbackColor: string; icon?: React.ReactNode }) {
|
|
91
93
|
const radius = size / 2;
|
|
92
94
|
return (
|
|
93
95
|
<View
|
|
@@ -96,8 +98,13 @@ function CircleFallback({ size, fallbackColor }: { size: number; fallbackColor:
|
|
|
96
98
|
height: size,
|
|
97
99
|
borderRadius: radius,
|
|
98
100
|
backgroundColor: fallbackColor,
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
overflow: 'hidden',
|
|
99
104
|
}}
|
|
100
|
-
|
|
105
|
+
>
|
|
106
|
+
{icon ?? null}
|
|
107
|
+
</View>
|
|
101
108
|
);
|
|
102
109
|
}
|
|
103
110
|
|
|
@@ -111,13 +118,15 @@ const AvatarComponent: React.FC<AvatarProps> = ({
|
|
|
111
118
|
shape = 'circle',
|
|
112
119
|
style,
|
|
113
120
|
imageStyle,
|
|
121
|
+
placeholderColor,
|
|
122
|
+
placeholderIcon,
|
|
114
123
|
onPress,
|
|
115
124
|
testID,
|
|
116
125
|
}) => {
|
|
117
126
|
const [errored, setErrored] = useState(false);
|
|
118
127
|
const theme = useTheme();
|
|
119
128
|
const radius = size / 2;
|
|
120
|
-
const fallbackColor = theme.colors.backgroundTertiary;
|
|
129
|
+
const fallbackColor = placeholderColor || theme.colors.backgroundTertiary;
|
|
121
130
|
|
|
122
131
|
// Reset error state when source changes (e.g., list item recycling
|
|
123
132
|
// or async URL resolution replacing an initial file ID).
|
|
@@ -168,6 +177,7 @@ const AvatarComponent: React.FC<AvatarProps> = ({
|
|
|
168
177
|
fallbackSource={fallbackSource}
|
|
169
178
|
size={size}
|
|
170
179
|
fallbackColor={fallbackColor}
|
|
180
|
+
placeholderIcon={placeholderIcon}
|
|
171
181
|
onError={() => setErrored(true)}
|
|
172
182
|
/>
|
|
173
183
|
) : (
|
|
@@ -180,7 +190,7 @@ const AvatarComponent: React.FC<AvatarProps> = ({
|
|
|
180
190
|
style={[StyleSheet.absoluteFillObject, { borderRadius: radius }, imageStyle]}
|
|
181
191
|
/>
|
|
182
192
|
) : (
|
|
183
|
-
<CircleFallback size={size} fallbackColor={fallbackColor} />
|
|
193
|
+
<CircleFallback size={size} fallbackColor={fallbackColor} icon={placeholderIcon} />
|
|
184
194
|
)}
|
|
185
195
|
</View>
|
|
186
196
|
)}
|
package/src/avatar/types.ts
CHANGED
|
@@ -26,6 +26,10 @@ export interface AvatarProps {
|
|
|
26
26
|
style?: StyleProp<ViewStyle>;
|
|
27
27
|
/** Image style (circle shape only) */
|
|
28
28
|
imageStyle?: StyleProp<ImageStyle>;
|
|
29
|
+
/** Custom background color for the placeholder circle (overrides theme default) */
|
|
30
|
+
placeholderColor?: string;
|
|
31
|
+
/** Custom icon rendered inside the placeholder circle when no image is available */
|
|
32
|
+
placeholderIcon?: ReactNode;
|
|
29
33
|
/** Press handler — wraps avatar in TouchableOpacity when provided */
|
|
30
34
|
onPress?: () => void;
|
|
31
35
|
testID?: string;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React, { memo, useMemo } from 'react';
|
|
2
|
+
import { View, Text, type ViewStyle, type TextStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../theme/use-theme';
|
|
5
|
+
import type { BadgeProps, BadgeColor, BadgeVariant } from './types';
|
|
6
|
+
|
|
7
|
+
const SIZE_CONFIG = {
|
|
8
|
+
small: { minWidth: 16, height: 16, fontSize: 10, paddingHorizontal: 4, dotSize: 6 },
|
|
9
|
+
medium: { minWidth: 20, height: 20, fontSize: 12, paddingHorizontal: 6, dotSize: 8 },
|
|
10
|
+
large: { minWidth: 24, height: 24, fontSize: 14, paddingHorizontal: 8, dotSize: 10 },
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
const PLACEMENT_CONFIG = {
|
|
14
|
+
'top-right': { top: -4, right: -4 },
|
|
15
|
+
'top-left': { top: -4, left: -4 },
|
|
16
|
+
'bottom-right': { bottom: -4, right: -4 },
|
|
17
|
+
'bottom-left': { bottom: -4, left: -4 },
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
function useColorPair(
|
|
21
|
+
color: BadgeColor,
|
|
22
|
+
variant: BadgeVariant,
|
|
23
|
+
theme: ReturnType<typeof useTheme>,
|
|
24
|
+
): { bg: string; fg: string } {
|
|
25
|
+
const colorMap: Record<BadgeColor, string> = {
|
|
26
|
+
default: theme.colors.textSecondary,
|
|
27
|
+
primary: theme.colors.primary,
|
|
28
|
+
success: theme.colors.success,
|
|
29
|
+
warning: theme.colors.warning,
|
|
30
|
+
error: theme.colors.error,
|
|
31
|
+
info: theme.colors.info,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const base = colorMap[color];
|
|
35
|
+
|
|
36
|
+
switch (variant) {
|
|
37
|
+
case 'solid':
|
|
38
|
+
return { bg: base, fg: '#fff' };
|
|
39
|
+
case 'subtle':
|
|
40
|
+
return { bg: base + '20', fg: base };
|
|
41
|
+
case 'outlined':
|
|
42
|
+
return { bg: 'transparent', fg: base };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const BadgeComponent: React.FC<BadgeProps> = ({
|
|
47
|
+
content,
|
|
48
|
+
variant = 'solid',
|
|
49
|
+
color = 'error',
|
|
50
|
+
size = 'medium',
|
|
51
|
+
dot = false,
|
|
52
|
+
max,
|
|
53
|
+
invisible = false,
|
|
54
|
+
placement = 'top-right',
|
|
55
|
+
children,
|
|
56
|
+
style,
|
|
57
|
+
textStyle,
|
|
58
|
+
testID,
|
|
59
|
+
}) => {
|
|
60
|
+
const theme = useTheme();
|
|
61
|
+
const colors = useColorPair(color, variant, theme);
|
|
62
|
+
const sizeConfig = SIZE_CONFIG[size];
|
|
63
|
+
|
|
64
|
+
const displayContent = useMemo(() => {
|
|
65
|
+
if (dot) return null;
|
|
66
|
+
if (content == null) return null;
|
|
67
|
+
if (typeof content === 'number' && max != null && content > max) {
|
|
68
|
+
return `${max}+`;
|
|
69
|
+
}
|
|
70
|
+
return String(content);
|
|
71
|
+
}, [content, dot, max]);
|
|
72
|
+
|
|
73
|
+
const badgeStyle = useMemo((): ViewStyle => {
|
|
74
|
+
if (dot) {
|
|
75
|
+
return {
|
|
76
|
+
width: sizeConfig.dotSize,
|
|
77
|
+
height: sizeConfig.dotSize,
|
|
78
|
+
borderRadius: sizeConfig.dotSize / 2,
|
|
79
|
+
backgroundColor: colors.bg,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const base: ViewStyle = {
|
|
84
|
+
minWidth: sizeConfig.height,
|
|
85
|
+
height: sizeConfig.height,
|
|
86
|
+
borderRadius: sizeConfig.height / 2,
|
|
87
|
+
paddingHorizontal: sizeConfig.paddingHorizontal,
|
|
88
|
+
backgroundColor: colors.bg,
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (variant === 'outlined') {
|
|
94
|
+
base.borderWidth = 1;
|
|
95
|
+
base.borderColor = colors.fg;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return base;
|
|
99
|
+
}, [dot, sizeConfig, colors, variant]);
|
|
100
|
+
|
|
101
|
+
const badgeTextStyle = useMemo(
|
|
102
|
+
(): TextStyle => ({
|
|
103
|
+
fontSize: sizeConfig.fontSize,
|
|
104
|
+
fontWeight: '600',
|
|
105
|
+
color: colors.fg,
|
|
106
|
+
textAlign: 'center',
|
|
107
|
+
lineHeight: sizeConfig.height,
|
|
108
|
+
}),
|
|
109
|
+
[sizeConfig, colors],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Standalone badge (no children)
|
|
113
|
+
if (!children) {
|
|
114
|
+
if (invisible) return null;
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<View style={[badgeStyle, style]} testID={testID}>
|
|
118
|
+
{displayContent != null && (
|
|
119
|
+
<Text style={[badgeTextStyle, textStyle]}>{displayContent}</Text>
|
|
120
|
+
)}
|
|
121
|
+
</View>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Positioned badge wrapping children
|
|
126
|
+
return (
|
|
127
|
+
<View style={{ position: 'relative', alignSelf: 'flex-start' }} testID={testID}>
|
|
128
|
+
{children}
|
|
129
|
+
{!invisible && (
|
|
130
|
+
<View
|
|
131
|
+
style={[
|
|
132
|
+
{
|
|
133
|
+
position: 'absolute',
|
|
134
|
+
zIndex: 1,
|
|
135
|
+
...PLACEMENT_CONFIG[placement],
|
|
136
|
+
},
|
|
137
|
+
badgeStyle,
|
|
138
|
+
style,
|
|
139
|
+
]}
|
|
140
|
+
>
|
|
141
|
+
{displayContent != null && (
|
|
142
|
+
<Text style={[badgeTextStyle, textStyle]}>{displayContent}</Text>
|
|
143
|
+
)}
|
|
144
|
+
</View>
|
|
145
|
+
)}
|
|
146
|
+
</View>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const Badge = memo(BadgeComponent);
|
|
151
|
+
Badge.displayName = 'Badge';
|