@compsych/mobile-ui 1.0.11 → 1.0.14
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/package.json +2 -1
- package/src/components/ActionSheet/index.tsx +124 -57
- package/src/components/Alert/index.tsx +19 -10
- package/src/components/Avatar/index.tsx +95 -92
- package/src/components/Badge/index.tsx +39 -36
- package/src/components/BodyText/index.tsx +5 -5
- package/src/components/Breadcrumb/index.tsx +32 -30
- package/src/components/Button/Button.test.tsx +13 -7
- package/src/components/Button/index.tsx +114 -92
- package/src/components/ChatBubble/ChatBubble.test.tsx +77 -0
- package/src/components/ChatBubble/index.tsx +187 -0
- package/src/components/ChatInput/ChatInput.test.tsx +90 -0
- package/src/components/ChatInput/index.tsx +263 -0
- package/src/components/Checkbox/index.tsx +24 -21
- package/src/components/Chip/index.tsx +75 -72
- package/src/components/Divider/index.tsx +3 -3
- package/src/components/EmptyState/index.tsx +72 -55
- package/src/components/HeaderText/index.tsx +5 -5
- package/src/components/Input/index.tsx +40 -37
- package/src/components/List/index.tsx +2 -3
- package/src/components/Pagination/index.tsx +56 -43
- package/src/components/PlanCard/index.tsx +118 -50
- package/src/components/ProgressTracker/index.tsx +44 -26
- package/src/components/PromotionCard/index.tsx +217 -212
- package/src/components/RadioButton/index.tsx +27 -25
- package/src/components/ScreenContainer/ScreenContainer.test.tsx +12 -2
- package/src/components/ScreenContainer/index.tsx +21 -10
- package/src/components/SegmentedControl/index.tsx +37 -30
- package/src/components/SelectionCard/index.tsx +45 -32
- package/src/components/ServiceCard/index.tsx +125 -128
- package/src/components/Slider/index.tsx +77 -77
- package/src/components/Snackbar/index.tsx +69 -57
- package/src/components/Switch/index.tsx +13 -12
- package/src/components/Tooltip/index.tsx +3 -3
- package/src/icons/index.ts +12 -59
- package/src/icons/types.ts +7 -23
- package/src/index.ts +15 -22
- package/src/theme/ThemeContext.ts +11 -0
- package/src/theme/ThemeProvider.tsx +40 -0
- package/src/theme/__tests__/ThemeProvider.test.tsx +101 -0
- package/src/theme/__tests__/defaultTheme.test.ts +36 -0
- package/src/theme/__tests__/mergeTheme.test.ts +94 -0
- package/src/theme/defaultTheme.ts +15 -0
- package/src/theme/index.ts +8 -0
- package/src/theme/mergeTheme.ts +47 -0
- package/src/theme/types.ts +39 -0
- package/src/theme/useTheme.ts +16 -0
- package/src/icons/AtomIcon.tsx +0 -32
- package/src/icons/BinocularsIcon.tsx +0 -45
- package/src/icons/FileChartColumnIncreasingIcon.tsx +0 -44
- package/src/icons/FlagIcon.tsx +0 -20
- package/src/icons/GlobeIcon.tsx +0 -20
- package/src/icons/GraduationCapIcon.tsx +0 -32
- package/src/icons/HandHeartIcon.tsx +0 -38
- package/src/icons/HandshakeIcon.tsx +0 -39
- package/src/icons/HazeIcon.tsx +0 -42
- package/src/icons/HeartHandshakeIcon.tsx +0 -20
- package/src/icons/HourglassIcon.tsx +0 -33
- package/src/icons/IdCardIcon.tsx +0 -44
- package/src/icons/MessageCirclePlusIcon.tsx +0 -32
- package/src/icons/MountainSnowIcon.tsx +0 -26
- package/src/icons/SnowflakeIcon.tsx +0 -86
- package/src/icons/StethoscopeIcon.tsx +0 -34
- package/src/icons/UserRoundIcon.tsx +0 -26
- package/src/icons/WheatIcon.tsx +0 -62
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compsych/mobile-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "ComPsych React Native Design System — 19 mobile UI components with self-contained design tokens",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@expo/vector-icons": ">=15.0.0",
|
|
34
34
|
"expo-font": ">=13.0.0",
|
|
35
|
+
"lucide-react-native": ">=0.400.0",
|
|
35
36
|
"react": ">=18.0.0",
|
|
36
37
|
"react-native": ">=0.73.0",
|
|
37
38
|
"react-native-svg": ">=15.0.0"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
Modal,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
import { Ionicons } from '@expo/vector-icons';
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { useTheme } from '../../theme';
|
|
16
16
|
import { Button } from '../Button';
|
|
17
17
|
import { HeaderText } from '../HeaderText';
|
|
18
18
|
|
|
@@ -28,11 +28,17 @@ export interface ActionSheetProps {
|
|
|
28
28
|
children?: React.ReactNode;
|
|
29
29
|
primaryAction?: ActionSheetAction;
|
|
30
30
|
secondaryAction?: ActionSheetAction;
|
|
31
|
+
/**
|
|
32
|
+
* `half` — floats at the bottom with a 6 px inset gutter; all corners
|
|
33
|
+
* rounded; content limited to maxHeight. Default.
|
|
34
|
+
* `full` — fills the screen from 10 px below the top; only top corners
|
|
35
|
+
* rounded; content expands to fill; close button appears on both
|
|
36
|
+
* sides of the title for one-handed reachability.
|
|
37
|
+
*/
|
|
38
|
+
variant?: 'half' | 'full';
|
|
31
39
|
style?: StyleProp<ViewStyle>;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
const { colorRoles: cr } = sys;
|
|
35
|
-
|
|
36
42
|
export function ActionSheet({
|
|
37
43
|
visible,
|
|
38
44
|
onClose,
|
|
@@ -40,8 +46,68 @@ export function ActionSheet({
|
|
|
40
46
|
children,
|
|
41
47
|
primaryAction,
|
|
42
48
|
secondaryAction,
|
|
49
|
+
variant = 'half',
|
|
43
50
|
style,
|
|
44
51
|
}: ActionSheetProps) {
|
|
52
|
+
const { colorRoles: cr, dimensions: dim } = useTheme();
|
|
53
|
+
const isFull = variant === 'full';
|
|
54
|
+
|
|
55
|
+
const dynamicStyles = useMemo(
|
|
56
|
+
() => ({
|
|
57
|
+
sheetWrapperHalf: {
|
|
58
|
+
paddingHorizontal: dim.spacing.padding.sysPadding6,
|
|
59
|
+
paddingBottom: dim.spacing.padding.sysPadding6,
|
|
60
|
+
},
|
|
61
|
+
sheetWrapperFull: {
|
|
62
|
+
marginTop: 10,
|
|
63
|
+
},
|
|
64
|
+
sheetHalf: {
|
|
65
|
+
backgroundColor: cr.surface.surfaceContainer.sysSurfaceContainerLowest,
|
|
66
|
+
borderRadius: dim.borderRadius.sysRadiusXxl,
|
|
67
|
+
},
|
|
68
|
+
sheetFull: {
|
|
69
|
+
backgroundColor: cr.surface.surfaceContainer.sysSurfaceContainerLowest,
|
|
70
|
+
borderTopLeftRadius: dim.borderRadius.sysRadiusXxl,
|
|
71
|
+
borderTopRightRadius: dim.borderRadius.sysRadiusXxl,
|
|
72
|
+
},
|
|
73
|
+
titleRow: {
|
|
74
|
+
paddingHorizontal: dim.spacing.padding.sysPadding16,
|
|
75
|
+
paddingVertical: dim.spacing.padding.sysPadding8,
|
|
76
|
+
},
|
|
77
|
+
closeButton: {
|
|
78
|
+
borderRadius: dim.borderRadius.sysRadiusFull,
|
|
79
|
+
backgroundColor: cr.surface.surfaceContainer.sysSurfaceContainer,
|
|
80
|
+
},
|
|
81
|
+
contentInner: {
|
|
82
|
+
padding: dim.spacing.padding.sysPadding16,
|
|
83
|
+
},
|
|
84
|
+
actions: {
|
|
85
|
+
padding: dim.spacing.padding.sysPadding24,
|
|
86
|
+
gap: dim.spacing.padding.sysPadding12,
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
[cr, dim],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const closeButton = (
|
|
93
|
+
<Pressable
|
|
94
|
+
onPress={onClose}
|
|
95
|
+
accessibilityRole="button"
|
|
96
|
+
accessibilityLabel="Close"
|
|
97
|
+
style={({ pressed }) => [
|
|
98
|
+
styles.closeButtonSize,
|
|
99
|
+
dynamicStyles.closeButton,
|
|
100
|
+
pressed && styles.closePressed,
|
|
101
|
+
]}
|
|
102
|
+
>
|
|
103
|
+
<Ionicons
|
|
104
|
+
name="close"
|
|
105
|
+
size={20}
|
|
106
|
+
color={cr.surface.surface.sysOnSurface}
|
|
107
|
+
/>
|
|
108
|
+
</Pressable>
|
|
109
|
+
);
|
|
110
|
+
|
|
45
111
|
return (
|
|
46
112
|
<Modal
|
|
47
113
|
visible={visible}
|
|
@@ -53,9 +119,24 @@ export function ActionSheet({
|
|
|
53
119
|
{/* Backdrop */}
|
|
54
120
|
<Pressable style={styles.backdrop} onPress={onClose} accessible={false} />
|
|
55
121
|
|
|
56
|
-
{/* Sheet */}
|
|
57
|
-
<View
|
|
58
|
-
|
|
122
|
+
{/* Sheet wrapper */}
|
|
123
|
+
<View
|
|
124
|
+
style={[
|
|
125
|
+
styles.sheetWrapper,
|
|
126
|
+
isFull
|
|
127
|
+
? dynamicStyles.sheetWrapperFull
|
|
128
|
+
: dynamicStyles.sheetWrapperHalf,
|
|
129
|
+
style,
|
|
130
|
+
]}
|
|
131
|
+
pointerEvents="box-none"
|
|
132
|
+
>
|
|
133
|
+
<View
|
|
134
|
+
style={[
|
|
135
|
+
styles.sheet,
|
|
136
|
+
isFull ? styles.sheetFull : styles.sheetHalf,
|
|
137
|
+
isFull ? dynamicStyles.sheetFull : dynamicStyles.sheetHalf,
|
|
138
|
+
]}
|
|
139
|
+
>
|
|
59
140
|
{/* Toolbar */}
|
|
60
141
|
<View style={styles.toolbar}>
|
|
61
142
|
{/* Grabber */}
|
|
@@ -64,9 +145,9 @@ export function ActionSheet({
|
|
|
64
145
|
</View>
|
|
65
146
|
|
|
66
147
|
{/* Title row */}
|
|
67
|
-
<View style={styles.titleRow}>
|
|
68
|
-
{/*
|
|
69
|
-
<View style={styles.
|
|
148
|
+
<View style={[styles.titleRow, dynamicStyles.titleRow]}>
|
|
149
|
+
{/* Left slot: close button (full) or invisible spacer (half) */}
|
|
150
|
+
{isFull ? closeButton : <View style={styles.closeButtonSize} />}
|
|
70
151
|
|
|
71
152
|
<HeaderText
|
|
72
153
|
variant="titleSmall"
|
|
@@ -77,34 +158,16 @@ export function ActionSheet({
|
|
|
77
158
|
{title ?? ''}
|
|
78
159
|
</HeaderText>
|
|
79
160
|
|
|
80
|
-
{/*
|
|
81
|
-
|
|
82
|
-
onPress={onClose}
|
|
83
|
-
accessibilityRole="button"
|
|
84
|
-
accessibilityLabel="Close"
|
|
85
|
-
style={({ pressed }) => [
|
|
86
|
-
styles.closeButton,
|
|
87
|
-
{
|
|
88
|
-
backgroundColor:
|
|
89
|
-
cr.surface.surfaceContainer.sysSurfaceContainer,
|
|
90
|
-
},
|
|
91
|
-
pressed && styles.closePresseed,
|
|
92
|
-
]}
|
|
93
|
-
>
|
|
94
|
-
<Ionicons
|
|
95
|
-
name="close"
|
|
96
|
-
size={20}
|
|
97
|
-
color={cr.surface.surface.sysOnSurface}
|
|
98
|
-
/>
|
|
99
|
-
</Pressable>
|
|
161
|
+
{/* Right close button — always shown */}
|
|
162
|
+
{closeButton}
|
|
100
163
|
</View>
|
|
101
164
|
</View>
|
|
102
165
|
|
|
103
166
|
{/* Content */}
|
|
104
167
|
{children && (
|
|
105
168
|
<ScrollView
|
|
106
|
-
style={styles.
|
|
107
|
-
contentContainerStyle={
|
|
169
|
+
style={isFull ? styles.contentFull : styles.contentHalf}
|
|
170
|
+
contentContainerStyle={dynamicStyles.contentInner}
|
|
108
171
|
showsVerticalScrollIndicator={false}
|
|
109
172
|
>
|
|
110
173
|
{children}
|
|
@@ -113,7 +176,7 @@ export function ActionSheet({
|
|
|
113
176
|
|
|
114
177
|
{/* Actions */}
|
|
115
178
|
{(primaryAction || secondaryAction) && (
|
|
116
|
-
<View style={
|
|
179
|
+
<View style={dynamicStyles.actions}>
|
|
117
180
|
{primaryAction && (
|
|
118
181
|
<Button
|
|
119
182
|
label={primaryAction.label}
|
|
@@ -140,40 +203,57 @@ export function ActionSheet({
|
|
|
140
203
|
);
|
|
141
204
|
}
|
|
142
205
|
|
|
143
|
-
const { colorRoles: cr2, dimensions: dim2 } = sys;
|
|
144
|
-
|
|
145
206
|
const styles = StyleSheet.create({
|
|
146
207
|
backdrop: {
|
|
147
|
-
|
|
208
|
+
position: 'absolute',
|
|
209
|
+
top: 0,
|
|
210
|
+
left: 0,
|
|
211
|
+
right: 0,
|
|
212
|
+
bottom: 0,
|
|
148
213
|
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
149
214
|
},
|
|
150
215
|
sheetWrapper: {
|
|
151
216
|
flex: 1,
|
|
152
217
|
justifyContent: 'flex-end',
|
|
153
|
-
paddingHorizontal: dim2.spacing.padding.sysPadding6,
|
|
154
|
-
paddingBottom: dim2.spacing.padding.sysPadding6,
|
|
155
218
|
},
|
|
219
|
+
// ── half ────────────────────────────────────────────────────────────────────
|
|
156
220
|
sheet: {
|
|
157
|
-
backgroundColor: cr2.surface.surfaceContainer.sysSurfaceContainerLowest,
|
|
158
|
-
borderRadius: dim2.borderRadius.sysRadiusXxl,
|
|
159
221
|
borderWidth: 1,
|
|
160
222
|
borderColor: 'rgba(255,255,255,0.4)',
|
|
161
223
|
overflow: 'hidden',
|
|
224
|
+
},
|
|
225
|
+
sheetHalf: {
|
|
162
226
|
shadowColor: '#000',
|
|
163
227
|
shadowOffset: { width: 0, height: 4 },
|
|
164
228
|
shadowOpacity: 0.1,
|
|
165
229
|
shadowRadius: 16,
|
|
166
230
|
elevation: 8,
|
|
167
231
|
},
|
|
232
|
+
contentHalf: {
|
|
233
|
+
maxHeight: 400,
|
|
234
|
+
},
|
|
235
|
+
// ── full ────────────────────────────────────────────────────────────────────
|
|
236
|
+
sheetFull: {
|
|
237
|
+
flex: 1,
|
|
238
|
+
shadowColor: '#000',
|
|
239
|
+
shadowOffset: { width: 0, height: 15 },
|
|
240
|
+
shadowOpacity: 0.18,
|
|
241
|
+
shadowRadius: 75,
|
|
242
|
+
elevation: 16,
|
|
243
|
+
},
|
|
244
|
+
contentFull: {
|
|
245
|
+
flex: 1,
|
|
246
|
+
},
|
|
247
|
+
// ── shared ──────────────────────────────────────────────────────────────────
|
|
168
248
|
toolbar: {
|
|
169
249
|
alignItems: 'center',
|
|
170
250
|
width: '100%',
|
|
171
251
|
},
|
|
172
252
|
grabberRow: {
|
|
253
|
+
width: '100%',
|
|
173
254
|
height: 16,
|
|
174
|
-
justifyContent: 'flex-
|
|
255
|
+
justifyContent: 'flex-start',
|
|
175
256
|
alignItems: 'center',
|
|
176
|
-
paddingBottom: 0,
|
|
177
257
|
paddingTop: 5,
|
|
178
258
|
},
|
|
179
259
|
grabber: {
|
|
@@ -186,32 +266,19 @@ const styles = StyleSheet.create({
|
|
|
186
266
|
flexDirection: 'row',
|
|
187
267
|
alignItems: 'center',
|
|
188
268
|
justifyContent: 'space-between',
|
|
189
|
-
paddingHorizontal: dim2.spacing.padding.sysPadding16,
|
|
190
|
-
paddingVertical: dim2.spacing.padding.sysPadding8,
|
|
191
269
|
width: '100%',
|
|
192
270
|
},
|
|
193
271
|
titleText: {
|
|
194
272
|
flex: 1,
|
|
195
273
|
textAlign: 'center',
|
|
196
274
|
},
|
|
197
|
-
|
|
275
|
+
closeButtonSize: {
|
|
198
276
|
width: 32,
|
|
199
277
|
height: 32,
|
|
200
|
-
borderRadius: dim2.borderRadius.sysRadiusFull,
|
|
201
278
|
alignItems: 'center',
|
|
202
279
|
justifyContent: 'center',
|
|
203
280
|
},
|
|
204
|
-
|
|
281
|
+
closePressed: {
|
|
205
282
|
opacity: 0.7,
|
|
206
283
|
},
|
|
207
|
-
content: {
|
|
208
|
-
maxHeight: 400,
|
|
209
|
-
},
|
|
210
|
-
contentInner: {
|
|
211
|
-
padding: dim2.spacing.padding.sysPadding16,
|
|
212
|
-
},
|
|
213
|
-
actions: {
|
|
214
|
-
padding: dim2.spacing.padding.sysPadding24,
|
|
215
|
-
gap: dim2.spacing.padding.sysPadding12,
|
|
216
|
-
},
|
|
217
284
|
});
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import { Ionicons } from '@expo/vector-icons';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { useTheme } from '../../theme';
|
|
15
15
|
|
|
16
16
|
export type AlertVariant =
|
|
17
17
|
| 'default'
|
|
@@ -43,8 +43,6 @@ export interface AlertProps {
|
|
|
43
43
|
style?: StyleProp<ViewStyle>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const { colorRoles: cr, dimensions: dim, typeScale: ts } = sys;
|
|
47
|
-
|
|
48
46
|
// ── Variant color map ────────────────────────────────────────────────────────
|
|
49
47
|
|
|
50
48
|
type VariantColors = {
|
|
@@ -55,7 +53,12 @@ type VariantColors = {
|
|
|
55
53
|
actionBorder: boolean;
|
|
56
54
|
};
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
type ColorRoles = ReturnType<typeof useTheme>['colorRoles'];
|
|
57
|
+
|
|
58
|
+
function getVariantColors(
|
|
59
|
+
variant: AlertVariant,
|
|
60
|
+
cr: ColorRoles,
|
|
61
|
+
): VariantColors {
|
|
59
62
|
switch (variant) {
|
|
60
63
|
case 'default':
|
|
61
64
|
return {
|
|
@@ -143,7 +146,8 @@ export function Alert({
|
|
|
143
146
|
onDismiss,
|
|
144
147
|
style,
|
|
145
148
|
}: AlertProps) {
|
|
146
|
-
const
|
|
149
|
+
const { colorRoles: cr, dimensions: dim, typeScale: ts } = useTheme();
|
|
150
|
+
const c = getVariantColors(variant, cr);
|
|
147
151
|
const isLg = size === 'lg';
|
|
148
152
|
|
|
149
153
|
const resolvedIcon =
|
|
@@ -246,7 +250,14 @@ export function Alert({
|
|
|
246
250
|
accessibilityLabel={actionLabel}
|
|
247
251
|
style={({ pressed }) => [
|
|
248
252
|
styles.actionLg,
|
|
249
|
-
|
|
253
|
+
{
|
|
254
|
+
backgroundColor:
|
|
255
|
+
cr.surface.surfaceContainer.sysSurfaceContainerLowest,
|
|
256
|
+
},
|
|
257
|
+
c.actionBorder && [
|
|
258
|
+
styles.actionLgBorderBase,
|
|
259
|
+
{ borderColor: cr.outline.sysOutline },
|
|
260
|
+
],
|
|
250
261
|
pressed && { opacity: 0.84 },
|
|
251
262
|
]}
|
|
252
263
|
>
|
|
@@ -332,12 +343,11 @@ const styles = StyleSheet.create({
|
|
|
332
343
|
flex: 1,
|
|
333
344
|
minWidth: 0,
|
|
334
345
|
},
|
|
335
|
-
// lg action button — elevated pill
|
|
346
|
+
// lg action button — elevated pill (backgroundColor and borderColor applied inline)
|
|
336
347
|
actionLg: {
|
|
337
348
|
height: 40,
|
|
338
349
|
paddingHorizontal: 24,
|
|
339
350
|
borderRadius: 9999,
|
|
340
|
-
backgroundColor: cr.surface.surfaceContainer.sysSurfaceContainerLowest,
|
|
341
351
|
alignItems: 'center',
|
|
342
352
|
justifyContent: 'center',
|
|
343
353
|
flexShrink: 0,
|
|
@@ -347,9 +357,8 @@ const styles = StyleSheet.create({
|
|
|
347
357
|
shadowRadius: 4,
|
|
348
358
|
elevation: 1,
|
|
349
359
|
},
|
|
350
|
-
|
|
360
|
+
actionLgBorderBase: {
|
|
351
361
|
borderWidth: 1,
|
|
352
|
-
borderColor: cr.outline.sysOutline,
|
|
353
362
|
},
|
|
354
363
|
// sm action button — text only
|
|
355
364
|
actionSm: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
Image,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
import { Ionicons } from '@expo/vector-icons';
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { useTheme } from '../../theme';
|
|
16
16
|
|
|
17
17
|
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
|
|
18
18
|
export type AvatarVariant = 'text' | 'image' | 'icon';
|
|
@@ -35,95 +35,6 @@ export interface AvatarProps {
|
|
|
35
35
|
style?: StyleProp<ViewStyle>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const { colorRoles: cr, dimensions: dim, typeScale: ts } = sys;
|
|
39
|
-
|
|
40
|
-
const SIZE_TOKENS = {
|
|
41
|
-
xs: {
|
|
42
|
-
diameter: 24,
|
|
43
|
-
fontSize: ts.labelSmall.sysFontSize,
|
|
44
|
-
lineHeight: ts.labelSmall.sysLineHeight,
|
|
45
|
-
fontWeight: '400' as const,
|
|
46
|
-
iconSize: 12,
|
|
47
|
-
ringBorder: dim.borderWidth.sysStrokeMedium,
|
|
48
|
-
ringInset: -1,
|
|
49
|
-
badgeSize: 0, // xs has no presence badge
|
|
50
|
-
badgeIconSize: 0,
|
|
51
|
-
badgeOffset: 0,
|
|
52
|
-
},
|
|
53
|
-
sm: {
|
|
54
|
-
diameter: 32,
|
|
55
|
-
fontSize: ts.labelMedium.sysFontSize,
|
|
56
|
-
lineHeight: ts.labelMedium.sysLineHeight,
|
|
57
|
-
fontWeight: '400' as const,
|
|
58
|
-
iconSize: 16,
|
|
59
|
-
ringBorder: dim.borderWidth.sysStrokeMedium,
|
|
60
|
-
ringInset: -2,
|
|
61
|
-
badgeSize: 16,
|
|
62
|
-
badgeIconSize: 12,
|
|
63
|
-
badgeOffset: -4,
|
|
64
|
-
},
|
|
65
|
-
md: {
|
|
66
|
-
diameter: 40,
|
|
67
|
-
fontSize: ts.labelLarge.sysFontSize,
|
|
68
|
-
lineHeight: ts.labelLarge.sysLineHeight,
|
|
69
|
-
fontWeight: '500' as const,
|
|
70
|
-
iconSize: 20,
|
|
71
|
-
ringBorder: dim.borderWidth.sysStrokeThick,
|
|
72
|
-
ringInset: -2,
|
|
73
|
-
badgeSize: 20,
|
|
74
|
-
badgeIconSize: 16,
|
|
75
|
-
badgeOffset: -4,
|
|
76
|
-
},
|
|
77
|
-
lg: {
|
|
78
|
-
diameter: 48,
|
|
79
|
-
fontSize: ts.labelLarge.sysFontSize,
|
|
80
|
-
lineHeight: ts.labelLarge.sysLineHeight,
|
|
81
|
-
fontWeight: '500' as const,
|
|
82
|
-
iconSize: 24,
|
|
83
|
-
ringBorder: dim.borderWidth.sysStrokeThick,
|
|
84
|
-
ringInset: -2,
|
|
85
|
-
badgeSize: 20,
|
|
86
|
-
badgeIconSize: 16,
|
|
87
|
-
badgeOffset: -4,
|
|
88
|
-
},
|
|
89
|
-
xl: {
|
|
90
|
-
diameter: 64,
|
|
91
|
-
fontSize: ts.labelLarge.sysFontSize,
|
|
92
|
-
lineHeight: ts.labelLarge.sysLineHeight,
|
|
93
|
-
fontWeight: '500' as const,
|
|
94
|
-
iconSize: 32,
|
|
95
|
-
ringBorder: dim.borderWidth.sysStrokeThick,
|
|
96
|
-
ringInset: -2,
|
|
97
|
-
badgeSize: 24,
|
|
98
|
-
badgeIconSize: 20,
|
|
99
|
-
badgeOffset: -4,
|
|
100
|
-
},
|
|
101
|
-
'2xl': {
|
|
102
|
-
diameter: 88,
|
|
103
|
-
fontSize: ts.titleMedium.sysFontSize,
|
|
104
|
-
lineHeight: ts.titleMedium.sysLineHeight,
|
|
105
|
-
fontWeight: '400' as const,
|
|
106
|
-
iconSize: 44,
|
|
107
|
-
ringBorder: dim.borderWidth.sysStrokeBold,
|
|
108
|
-
ringInset: -2,
|
|
109
|
-
badgeSize: 32,
|
|
110
|
-
badgeIconSize: 24,
|
|
111
|
-
badgeOffset: 0,
|
|
112
|
-
},
|
|
113
|
-
'3xl': {
|
|
114
|
-
diameter: 120,
|
|
115
|
-
fontSize: ts.titleLarge.sysFontSize,
|
|
116
|
-
lineHeight: ts.titleLarge.sysLineHeight,
|
|
117
|
-
fontWeight: '500' as const,
|
|
118
|
-
iconSize: 60,
|
|
119
|
-
ringBorder: dim.borderWidth.sysStrokeBold,
|
|
120
|
-
ringInset: -2,
|
|
121
|
-
badgeSize: 32,
|
|
122
|
-
badgeIconSize: 24,
|
|
123
|
-
badgeOffset: 0,
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
|
|
127
38
|
export function Avatar({
|
|
128
39
|
variant = 'text',
|
|
129
40
|
size = 'md',
|
|
@@ -135,7 +46,99 @@ export function Avatar({
|
|
|
135
46
|
accessibilityLabel,
|
|
136
47
|
style,
|
|
137
48
|
}: AvatarProps) {
|
|
138
|
-
const
|
|
49
|
+
const { colorRoles: cr, dimensions: dim, typeScale: ts } = useTheme();
|
|
50
|
+
|
|
51
|
+
const sizeTokens = useMemo(
|
|
52
|
+
() => ({
|
|
53
|
+
xs: {
|
|
54
|
+
diameter: 24,
|
|
55
|
+
fontSize: ts.labelSmall.sysFontSize,
|
|
56
|
+
lineHeight: ts.labelSmall.sysLineHeight,
|
|
57
|
+
fontWeight: '400' as const,
|
|
58
|
+
iconSize: 12,
|
|
59
|
+
ringBorder: dim.borderWidth.sysStrokeMedium,
|
|
60
|
+
ringInset: -1,
|
|
61
|
+
badgeSize: 0,
|
|
62
|
+
badgeIconSize: 0,
|
|
63
|
+
badgeOffset: 0,
|
|
64
|
+
},
|
|
65
|
+
sm: {
|
|
66
|
+
diameter: 32,
|
|
67
|
+
fontSize: ts.labelMedium.sysFontSize,
|
|
68
|
+
lineHeight: ts.labelMedium.sysLineHeight,
|
|
69
|
+
fontWeight: '400' as const,
|
|
70
|
+
iconSize: 16,
|
|
71
|
+
ringBorder: dim.borderWidth.sysStrokeMedium,
|
|
72
|
+
ringInset: -2,
|
|
73
|
+
badgeSize: 16,
|
|
74
|
+
badgeIconSize: 12,
|
|
75
|
+
badgeOffset: -4,
|
|
76
|
+
},
|
|
77
|
+
md: {
|
|
78
|
+
diameter: 40,
|
|
79
|
+
fontSize: ts.labelLarge.sysFontSize,
|
|
80
|
+
lineHeight: ts.labelLarge.sysLineHeight,
|
|
81
|
+
fontWeight: '500' as const,
|
|
82
|
+
iconSize: 20,
|
|
83
|
+
ringBorder: dim.borderWidth.sysStrokeThick,
|
|
84
|
+
ringInset: -2,
|
|
85
|
+
badgeSize: 20,
|
|
86
|
+
badgeIconSize: 16,
|
|
87
|
+
badgeOffset: -4,
|
|
88
|
+
},
|
|
89
|
+
lg: {
|
|
90
|
+
diameter: 48,
|
|
91
|
+
fontSize: ts.labelLarge.sysFontSize,
|
|
92
|
+
lineHeight: ts.labelLarge.sysLineHeight,
|
|
93
|
+
fontWeight: '500' as const,
|
|
94
|
+
iconSize: 24,
|
|
95
|
+
ringBorder: dim.borderWidth.sysStrokeThick,
|
|
96
|
+
ringInset: -2,
|
|
97
|
+
badgeSize: 20,
|
|
98
|
+
badgeIconSize: 16,
|
|
99
|
+
badgeOffset: -4,
|
|
100
|
+
},
|
|
101
|
+
xl: {
|
|
102
|
+
diameter: 64,
|
|
103
|
+
fontSize: ts.labelLarge.sysFontSize,
|
|
104
|
+
lineHeight: ts.labelLarge.sysLineHeight,
|
|
105
|
+
fontWeight: '500' as const,
|
|
106
|
+
iconSize: 32,
|
|
107
|
+
ringBorder: dim.borderWidth.sysStrokeThick,
|
|
108
|
+
ringInset: -2,
|
|
109
|
+
badgeSize: 24,
|
|
110
|
+
badgeIconSize: 20,
|
|
111
|
+
badgeOffset: -4,
|
|
112
|
+
},
|
|
113
|
+
'2xl': {
|
|
114
|
+
diameter: 88,
|
|
115
|
+
fontSize: ts.titleMedium.sysFontSize,
|
|
116
|
+
lineHeight: ts.titleMedium.sysLineHeight,
|
|
117
|
+
fontWeight: '400' as const,
|
|
118
|
+
iconSize: 44,
|
|
119
|
+
ringBorder: dim.borderWidth.sysStrokeBold,
|
|
120
|
+
ringInset: -2,
|
|
121
|
+
badgeSize: 32,
|
|
122
|
+
badgeIconSize: 24,
|
|
123
|
+
badgeOffset: 0,
|
|
124
|
+
},
|
|
125
|
+
'3xl': {
|
|
126
|
+
diameter: 120,
|
|
127
|
+
fontSize: ts.titleLarge.sysFontSize,
|
|
128
|
+
lineHeight: ts.titleLarge.sysLineHeight,
|
|
129
|
+
fontWeight: '500' as const,
|
|
130
|
+
iconSize: 60,
|
|
131
|
+
ringBorder: dim.borderWidth.sysStrokeBold,
|
|
132
|
+
ringInset: -2,
|
|
133
|
+
badgeSize: 32,
|
|
134
|
+
badgeIconSize: 24,
|
|
135
|
+
badgeOffset: 0,
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
[dim, ts],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const s = sizeTokens[size];
|
|
139
142
|
const r = s.diameter / 2;
|
|
140
143
|
|
|
141
144
|
const defaultLabel = variant === 'text' ? `Avatar: ${initials}` : 'Avatar';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useTheme } from '../../theme';
|
|
6
6
|
|
|
7
7
|
export type BadgeSize = 'sm' | 'md' | 'lg';
|
|
8
8
|
export type BadgeStyle =
|
|
@@ -21,37 +21,6 @@ export interface BadgeProps {
|
|
|
21
21
|
style?: StyleProp<ViewStyle>;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const { colorRoles: cr, dimensions: dim, typeScale: ts } = sys;
|
|
25
|
-
|
|
26
|
-
// ── Size tokens ───────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
const SIZE_TOKENS = {
|
|
29
|
-
sm: {
|
|
30
|
-
outerSize: 16,
|
|
31
|
-
paddingH: dim.spacing.padding.sysPadding4,
|
|
32
|
-
fontSize: ts.labelSmall.sysFontSize,
|
|
33
|
-
lineHeight: ts.labelSmall.sysLineHeight,
|
|
34
|
-
letterSpacing: ts.labelSmall.sysTracking,
|
|
35
|
-
dotInner: 6,
|
|
36
|
-
},
|
|
37
|
-
md: {
|
|
38
|
-
outerSize: 20,
|
|
39
|
-
paddingH: dim.spacing.padding.sysPadding8,
|
|
40
|
-
fontSize: ts.labelMedium.sysFontSize,
|
|
41
|
-
lineHeight: ts.labelMedium.sysLineHeight,
|
|
42
|
-
letterSpacing: ts.labelMedium.sysTracking,
|
|
43
|
-
dotInner: 8,
|
|
44
|
-
},
|
|
45
|
-
lg: {
|
|
46
|
-
outerSize: 24,
|
|
47
|
-
paddingH: dim.spacing.padding.sysPadding8,
|
|
48
|
-
fontSize: ts.labelMedium.sysFontSize,
|
|
49
|
-
lineHeight: ts.labelMedium.sysLineHeight,
|
|
50
|
-
letterSpacing: ts.labelMedium.sysTracking,
|
|
51
|
-
dotInner: 12,
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
24
|
// ── Style/color tokens ────────────────────────────────────────────────────────
|
|
56
25
|
|
|
57
26
|
type StyleColors = {
|
|
@@ -60,7 +29,9 @@ type StyleColors = {
|
|
|
60
29
|
elevated: boolean;
|
|
61
30
|
};
|
|
62
31
|
|
|
63
|
-
|
|
32
|
+
type ColorRoles = ReturnType<typeof useTheme>['colorRoles'];
|
|
33
|
+
|
|
34
|
+
function getStyleColors(style: BadgeStyle, cr: ColorRoles): StyleColors {
|
|
64
35
|
switch (style) {
|
|
65
36
|
case 'filled':
|
|
66
37
|
return {
|
|
@@ -110,8 +81,40 @@ export function Badge({
|
|
|
110
81
|
badgeStyle = 'filled',
|
|
111
82
|
style,
|
|
112
83
|
}: BadgeProps) {
|
|
113
|
-
const
|
|
114
|
-
|
|
84
|
+
const { colorRoles: cr, dimensions: dim, typeScale: ts } = useTheme();
|
|
85
|
+
|
|
86
|
+
const sizeTokens = useMemo(
|
|
87
|
+
() => ({
|
|
88
|
+
sm: {
|
|
89
|
+
outerSize: 16,
|
|
90
|
+
paddingH: dim.spacing.padding.sysPadding4,
|
|
91
|
+
fontSize: ts.labelSmall.sysFontSize,
|
|
92
|
+
lineHeight: ts.labelSmall.sysLineHeight,
|
|
93
|
+
letterSpacing: ts.labelSmall.sysTracking,
|
|
94
|
+
dotInner: 6,
|
|
95
|
+
},
|
|
96
|
+
md: {
|
|
97
|
+
outerSize: 20,
|
|
98
|
+
paddingH: dim.spacing.padding.sysPadding8,
|
|
99
|
+
fontSize: ts.labelMedium.sysFontSize,
|
|
100
|
+
lineHeight: ts.labelMedium.sysLineHeight,
|
|
101
|
+
letterSpacing: ts.labelMedium.sysTracking,
|
|
102
|
+
dotInner: 8,
|
|
103
|
+
},
|
|
104
|
+
lg: {
|
|
105
|
+
outerSize: 24,
|
|
106
|
+
paddingH: dim.spacing.padding.sysPadding8,
|
|
107
|
+
fontSize: ts.labelMedium.sysFontSize,
|
|
108
|
+
lineHeight: ts.labelMedium.sysLineHeight,
|
|
109
|
+
letterSpacing: ts.labelMedium.sysTracking,
|
|
110
|
+
dotInner: 12,
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
[dim, ts],
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const s = sizeTokens[size];
|
|
117
|
+
const c = getStyleColors(badgeStyle, cr);
|
|
115
118
|
|
|
116
119
|
// Dot variant — render a solid filled circle inside a transparent wrapper
|
|
117
120
|
if (badgeStyle === 'dot') {
|