syzygy-ui-rn 2.0.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/LICENSE +21 -0
- package/README.md +151 -0
- package/package.json +56 -0
- package/src/__tests__/components.test.tsx +368 -0
- package/src/__tests__/tokens.test.ts +47 -0
- package/src/components/badges/Badge.tsx +74 -0
- package/src/components/buttons/DestructiveButton.tsx +82 -0
- package/src/components/buttons/GhostButton.tsx +77 -0
- package/src/components/buttons/IconButton.tsx +61 -0
- package/src/components/buttons/PrimaryButton.tsx +82 -0
- package/src/components/buttons/SecondaryButton.tsx +84 -0
- package/src/components/cards/CardView.tsx +45 -0
- package/src/components/display/Avatar.tsx +53 -0
- package/src/components/display/Chip.tsx +64 -0
- package/src/components/display/CountBadge.tsx +58 -0
- package/src/components/display/DividerLine.tsx +29 -0
- package/src/components/display/LazyImageView.tsx +57 -0
- package/src/components/display/ListRow.tsx +76 -0
- package/src/components/display/SectionHeader.tsx +55 -0
- package/src/components/display/StarRatingView.tsx +70 -0
- package/src/components/feedback/EmptyStateView.tsx +93 -0
- package/src/components/feedback/ErrorStateView.tsx +75 -0
- package/src/components/feedback/LoadingView.tsx +57 -0
- package/src/components/feedback/ProgressBar.tsx +42 -0
- package/src/components/feedback/PullToRefresh.tsx +38 -0
- package/src/components/feedback/ShimmerView.tsx +47 -0
- package/src/components/feedback/ToastView.tsx +71 -0
- package/src/components/inputs/CheckboxInput.tsx +73 -0
- package/src/components/inputs/Dropdown.tsx +110 -0
- package/src/components/inputs/QuantityStepper.tsx +81 -0
- package/src/components/inputs/RadioButtonInput.tsx +73 -0
- package/src/components/inputs/SearchInput.tsx +107 -0
- package/src/components/inputs/SecureInput.tsx +105 -0
- package/src/components/inputs/SegmentedControl.tsx +70 -0
- package/src/components/inputs/SliderInput.tsx +131 -0
- package/src/components/inputs/TextInput.tsx +90 -0
- package/src/components/inputs/ToggleSwitch.tsx +50 -0
- package/src/components/layout/KeyboardAvoidingScrollView.tsx +40 -0
- package/src/components/navigation/AppBar.tsx +53 -0
- package/src/components/navigation/BackButton.tsx +63 -0
- package/src/components/navigation/BottomNavigationBar.tsx +65 -0
- package/src/components/navigation/PagerView.tsx +40 -0
- package/src/components/navigation/TabBar.tsx +68 -0
- package/src/components/navigation/TabBarItem.ts +8 -0
- package/src/components/overlay/BottomSheet.tsx +64 -0
- package/src/components/overlay/CollapsibleView.tsx +82 -0
- package/src/components/overlay/ModalDialog.tsx +47 -0
- package/src/index.ts +54 -0
- package/src/tokens/colors.ts +84 -0
- package/src/tokens/index.ts +4 -0
- package/src/tokens/radius.ts +8 -0
- package/src/tokens/spacing.ts +10 -0
- package/src/tokens/typography.ts +27 -0
- package/src/transitions/navigationTransitions.ts +66 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
StyleProp,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
useColorScheme,
|
|
7
|
+
View,
|
|
8
|
+
ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
|
|
11
|
+
import { getColors } from '../../tokens/colors';
|
|
12
|
+
import { radius } from '../../tokens/radius';
|
|
13
|
+
import { spacing } from '../../tokens/spacing';
|
|
14
|
+
import { fontSizes, fontWeights } from '../../tokens/typography';
|
|
15
|
+
|
|
16
|
+
export type BadgeVariant = 'primary' | 'success' | 'warning' | 'error';
|
|
17
|
+
|
|
18
|
+
export interface BadgeProps {
|
|
19
|
+
text: string;
|
|
20
|
+
variant?: BadgeVariant;
|
|
21
|
+
style?: StyleProp<ViewStyle>;
|
|
22
|
+
accessibilityLabel?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const Badge: React.FC<BadgeProps> = ({
|
|
26
|
+
text,
|
|
27
|
+
variant = 'primary',
|
|
28
|
+
style,
|
|
29
|
+
accessibilityLabel,
|
|
30
|
+
}) => {
|
|
31
|
+
const scheme = useColorScheme();
|
|
32
|
+
const colors = getColors(scheme);
|
|
33
|
+
|
|
34
|
+
const backgroundColor =
|
|
35
|
+
variant === 'primary'
|
|
36
|
+
? colors.primary
|
|
37
|
+
: variant === 'success'
|
|
38
|
+
? colors.success
|
|
39
|
+
: variant === 'warning'
|
|
40
|
+
? colors.warning
|
|
41
|
+
: colors.error;
|
|
42
|
+
|
|
43
|
+
const textColor =
|
|
44
|
+
variant === 'primary'
|
|
45
|
+
? colors.primaryText
|
|
46
|
+
: variant === 'success'
|
|
47
|
+
? colors.successText
|
|
48
|
+
: variant === 'warning'
|
|
49
|
+
? colors.warningText
|
|
50
|
+
: colors.errorText;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View
|
|
54
|
+
style={[styles.container, { backgroundColor }, style]}
|
|
55
|
+
accessibilityRole="text"
|
|
56
|
+
accessibilityLabel={accessibilityLabel ?? text}
|
|
57
|
+
>
|
|
58
|
+
<Text style={[styles.text, { color: textColor }]}>{text}</Text>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const styles = StyleSheet.create({
|
|
64
|
+
container: {
|
|
65
|
+
alignSelf: 'flex-start',
|
|
66
|
+
paddingHorizontal: spacing.sm,
|
|
67
|
+
paddingVertical: spacing.xs,
|
|
68
|
+
borderRadius: radius.full,
|
|
69
|
+
},
|
|
70
|
+
text: {
|
|
71
|
+
fontSize: fontSizes.xs,
|
|
72
|
+
fontWeight: fontWeights.semibold,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
StyleProp,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
useColorScheme,
|
|
9
|
+
ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
import { getColors } from '../../tokens/colors';
|
|
13
|
+
import { radius } from '../../tokens/radius';
|
|
14
|
+
import { spacing } from '../../tokens/spacing';
|
|
15
|
+
import { fontSizes, fontWeights } from '../../tokens/typography';
|
|
16
|
+
|
|
17
|
+
export interface DestructiveButtonProps {
|
|
18
|
+
title: string;
|
|
19
|
+
onPress: () => void;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
accessibilityLabel?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const DestructiveButton: React.FC<DestructiveButtonProps> = ({
|
|
27
|
+
title,
|
|
28
|
+
onPress,
|
|
29
|
+
disabled = false,
|
|
30
|
+
loading = false,
|
|
31
|
+
style,
|
|
32
|
+
accessibilityLabel,
|
|
33
|
+
}) => {
|
|
34
|
+
const scheme = useColorScheme();
|
|
35
|
+
const colors = getColors(scheme);
|
|
36
|
+
const isDisabled = disabled || loading;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<TouchableOpacity
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
disabled={isDisabled}
|
|
42
|
+
accessibilityRole="button"
|
|
43
|
+
accessibilityLabel={accessibilityLabel ?? title}
|
|
44
|
+
accessibilityState={{ disabled: isDisabled, busy: loading }}
|
|
45
|
+
style={[
|
|
46
|
+
styles.base,
|
|
47
|
+
{
|
|
48
|
+
backgroundColor: isDisabled ? colors.disabled : colors.destructive,
|
|
49
|
+
},
|
|
50
|
+
style,
|
|
51
|
+
]}
|
|
52
|
+
>
|
|
53
|
+
{loading ? (
|
|
54
|
+
<ActivityIndicator color={colors.destructiveText} />
|
|
55
|
+
) : (
|
|
56
|
+
<Text
|
|
57
|
+
style={[
|
|
58
|
+
styles.text,
|
|
59
|
+
{ color: isDisabled ? colors.disabledText : colors.destructiveText },
|
|
60
|
+
]}
|
|
61
|
+
>
|
|
62
|
+
{title}
|
|
63
|
+
</Text>
|
|
64
|
+
)}
|
|
65
|
+
</TouchableOpacity>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const styles = StyleSheet.create({
|
|
70
|
+
base: {
|
|
71
|
+
minHeight: 44,
|
|
72
|
+
paddingHorizontal: spacing.md,
|
|
73
|
+
paddingVertical: spacing.sm,
|
|
74
|
+
borderRadius: radius.md,
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
justifyContent: 'center',
|
|
77
|
+
},
|
|
78
|
+
text: {
|
|
79
|
+
fontSize: fontSizes.md,
|
|
80
|
+
fontWeight: fontWeights.semibold,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
StyleProp,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
useColorScheme,
|
|
9
|
+
ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
import { getColors } from '../../tokens/colors';
|
|
13
|
+
import { radius } from '../../tokens/radius';
|
|
14
|
+
import { spacing } from '../../tokens/spacing';
|
|
15
|
+
import { fontSizes, fontWeights } from '../../tokens/typography';
|
|
16
|
+
|
|
17
|
+
export interface GhostButtonProps {
|
|
18
|
+
title: string;
|
|
19
|
+
onPress: () => void;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
accessibilityLabel?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const GhostButton: React.FC<GhostButtonProps> = ({
|
|
27
|
+
title,
|
|
28
|
+
onPress,
|
|
29
|
+
disabled = false,
|
|
30
|
+
loading = false,
|
|
31
|
+
style,
|
|
32
|
+
accessibilityLabel,
|
|
33
|
+
}) => {
|
|
34
|
+
const scheme = useColorScheme();
|
|
35
|
+
const colors = getColors(scheme);
|
|
36
|
+
const isDisabled = disabled || loading;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<TouchableOpacity
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
disabled={isDisabled}
|
|
42
|
+
accessibilityRole="button"
|
|
43
|
+
accessibilityLabel={accessibilityLabel ?? title}
|
|
44
|
+
accessibilityState={{ disabled: isDisabled, busy: loading }}
|
|
45
|
+
style={[styles.base, style]}
|
|
46
|
+
>
|
|
47
|
+
{loading ? (
|
|
48
|
+
<ActivityIndicator color={colors.primary} />
|
|
49
|
+
) : (
|
|
50
|
+
<Text
|
|
51
|
+
style={[
|
|
52
|
+
styles.text,
|
|
53
|
+
{ color: isDisabled ? colors.disabledText : colors.primary },
|
|
54
|
+
]}
|
|
55
|
+
>
|
|
56
|
+
{title}
|
|
57
|
+
</Text>
|
|
58
|
+
)}
|
|
59
|
+
</TouchableOpacity>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const styles = StyleSheet.create({
|
|
64
|
+
base: {
|
|
65
|
+
minHeight: 44,
|
|
66
|
+
paddingHorizontal: spacing.md,
|
|
67
|
+
paddingVertical: spacing.sm,
|
|
68
|
+
borderRadius: radius.md,
|
|
69
|
+
alignItems: 'center',
|
|
70
|
+
justifyContent: 'center',
|
|
71
|
+
backgroundColor: 'transparent',
|
|
72
|
+
},
|
|
73
|
+
text: {
|
|
74
|
+
fontSize: fontSizes.md,
|
|
75
|
+
fontWeight: fontWeights.semibold,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
StyleProp,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
useColorScheme,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
|
|
10
|
+
import { getColors } from '../../tokens/colors';
|
|
11
|
+
import { radius } from '../../tokens/radius';
|
|
12
|
+
import { spacing } from '../../tokens/spacing';
|
|
13
|
+
|
|
14
|
+
export interface IconButtonProps {
|
|
15
|
+
icon: React.ReactNode;
|
|
16
|
+
onPress: () => void;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
style?: StyleProp<ViewStyle>;
|
|
19
|
+
accessibilityLabel: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const IconButton: React.FC<IconButtonProps> = ({
|
|
23
|
+
icon,
|
|
24
|
+
onPress,
|
|
25
|
+
disabled = false,
|
|
26
|
+
style,
|
|
27
|
+
accessibilityLabel,
|
|
28
|
+
}) => {
|
|
29
|
+
const scheme = useColorScheme();
|
|
30
|
+
const colors = getColors(scheme);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<TouchableOpacity
|
|
34
|
+
onPress={onPress}
|
|
35
|
+
disabled={disabled}
|
|
36
|
+
accessibilityRole="button"
|
|
37
|
+
accessibilityLabel={accessibilityLabel}
|
|
38
|
+
accessibilityState={{ disabled }}
|
|
39
|
+
style={[
|
|
40
|
+
styles.base,
|
|
41
|
+
{
|
|
42
|
+
backgroundColor: disabled ? colors.disabled : colors.surfaceAlt,
|
|
43
|
+
},
|
|
44
|
+
style,
|
|
45
|
+
]}
|
|
46
|
+
>
|
|
47
|
+
{icon}
|
|
48
|
+
</TouchableOpacity>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const styles = StyleSheet.create({
|
|
53
|
+
base: {
|
|
54
|
+
width: 44,
|
|
55
|
+
height: 44,
|
|
56
|
+
borderRadius: radius.full,
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
justifyContent: 'center',
|
|
59
|
+
padding: spacing.xs,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
StyleProp,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
useColorScheme,
|
|
9
|
+
ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
import { getColors } from '../../tokens/colors';
|
|
13
|
+
import { radius } from '../../tokens/radius';
|
|
14
|
+
import { spacing } from '../../tokens/spacing';
|
|
15
|
+
import { fontSizes, fontWeights } from '../../tokens/typography';
|
|
16
|
+
|
|
17
|
+
export interface PrimaryButtonProps {
|
|
18
|
+
title: string;
|
|
19
|
+
onPress: () => void;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
accessibilityLabel?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const PrimaryButton: React.FC<PrimaryButtonProps> = ({
|
|
27
|
+
title,
|
|
28
|
+
onPress,
|
|
29
|
+
disabled = false,
|
|
30
|
+
loading = false,
|
|
31
|
+
style,
|
|
32
|
+
accessibilityLabel,
|
|
33
|
+
}) => {
|
|
34
|
+
const scheme = useColorScheme();
|
|
35
|
+
const colors = getColors(scheme);
|
|
36
|
+
const isDisabled = disabled || loading;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<TouchableOpacity
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
disabled={isDisabled}
|
|
42
|
+
accessibilityRole="button"
|
|
43
|
+
accessibilityLabel={accessibilityLabel ?? title}
|
|
44
|
+
accessibilityState={{ disabled: isDisabled, busy: loading }}
|
|
45
|
+
style={[
|
|
46
|
+
styles.base,
|
|
47
|
+
{
|
|
48
|
+
backgroundColor: isDisabled ? colors.disabled : colors.primary,
|
|
49
|
+
},
|
|
50
|
+
style,
|
|
51
|
+
]}
|
|
52
|
+
>
|
|
53
|
+
{loading ? (
|
|
54
|
+
<ActivityIndicator color={colors.primaryText} />
|
|
55
|
+
) : (
|
|
56
|
+
<Text
|
|
57
|
+
style={[
|
|
58
|
+
styles.text,
|
|
59
|
+
{ color: isDisabled ? colors.disabledText : colors.primaryText },
|
|
60
|
+
]}
|
|
61
|
+
>
|
|
62
|
+
{title}
|
|
63
|
+
</Text>
|
|
64
|
+
)}
|
|
65
|
+
</TouchableOpacity>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const styles = StyleSheet.create({
|
|
70
|
+
base: {
|
|
71
|
+
minHeight: 44,
|
|
72
|
+
paddingHorizontal: spacing.md,
|
|
73
|
+
paddingVertical: spacing.sm,
|
|
74
|
+
borderRadius: radius.md,
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
justifyContent: 'center',
|
|
77
|
+
},
|
|
78
|
+
text: {
|
|
79
|
+
fontSize: fontSizes.md,
|
|
80
|
+
fontWeight: fontWeights.semibold,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
StyleProp,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
useColorScheme,
|
|
9
|
+
ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
import { getColors } from '../../tokens/colors';
|
|
13
|
+
import { radius } from '../../tokens/radius';
|
|
14
|
+
import { spacing } from '../../tokens/spacing';
|
|
15
|
+
import { fontSizes, fontWeights } from '../../tokens/typography';
|
|
16
|
+
|
|
17
|
+
export interface SecondaryButtonProps {
|
|
18
|
+
title: string;
|
|
19
|
+
onPress: () => void;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
accessibilityLabel?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const SecondaryButton: React.FC<SecondaryButtonProps> = ({
|
|
27
|
+
title,
|
|
28
|
+
onPress,
|
|
29
|
+
disabled = false,
|
|
30
|
+
loading = false,
|
|
31
|
+
style,
|
|
32
|
+
accessibilityLabel,
|
|
33
|
+
}) => {
|
|
34
|
+
const scheme = useColorScheme();
|
|
35
|
+
const colors = getColors(scheme);
|
|
36
|
+
const isDisabled = disabled || loading;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<TouchableOpacity
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
disabled={isDisabled}
|
|
42
|
+
accessibilityRole="button"
|
|
43
|
+
accessibilityLabel={accessibilityLabel ?? title}
|
|
44
|
+
accessibilityState={{ disabled: isDisabled, busy: loading }}
|
|
45
|
+
style={[
|
|
46
|
+
styles.base,
|
|
47
|
+
{
|
|
48
|
+
backgroundColor: isDisabled ? colors.disabled : colors.secondary,
|
|
49
|
+
borderColor: colors.border,
|
|
50
|
+
},
|
|
51
|
+
style,
|
|
52
|
+
]}
|
|
53
|
+
>
|
|
54
|
+
{loading ? (
|
|
55
|
+
<ActivityIndicator color={colors.secondaryText} />
|
|
56
|
+
) : (
|
|
57
|
+
<Text
|
|
58
|
+
style={[
|
|
59
|
+
styles.text,
|
|
60
|
+
{ color: isDisabled ? colors.disabledText : colors.secondaryText },
|
|
61
|
+
]}
|
|
62
|
+
>
|
|
63
|
+
{title}
|
|
64
|
+
</Text>
|
|
65
|
+
)}
|
|
66
|
+
</TouchableOpacity>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const styles = StyleSheet.create({
|
|
71
|
+
base: {
|
|
72
|
+
minHeight: 44,
|
|
73
|
+
paddingHorizontal: spacing.md,
|
|
74
|
+
paddingVertical: spacing.sm,
|
|
75
|
+
borderRadius: radius.md,
|
|
76
|
+
borderWidth: 1,
|
|
77
|
+
alignItems: 'center',
|
|
78
|
+
justifyContent: 'center',
|
|
79
|
+
},
|
|
80
|
+
text: {
|
|
81
|
+
fontSize: fontSizes.md,
|
|
82
|
+
fontWeight: fontWeights.semibold,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleProp, StyleSheet, useColorScheme, View, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { getColors } from '../../tokens/colors';
|
|
5
|
+
import { radius } from '../../tokens/radius';
|
|
6
|
+
import { spacing } from '../../tokens/spacing';
|
|
7
|
+
|
|
8
|
+
export interface CardViewProps {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
style?: StyleProp<ViewStyle>;
|
|
11
|
+
accessibilityLabel?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const CardView: React.FC<CardViewProps> = ({
|
|
15
|
+
children,
|
|
16
|
+
style,
|
|
17
|
+
accessibilityLabel,
|
|
18
|
+
}) => {
|
|
19
|
+
const scheme = useColorScheme();
|
|
20
|
+
const colors = getColors(scheme);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<View
|
|
24
|
+
accessibilityLabel={accessibilityLabel}
|
|
25
|
+
style={[
|
|
26
|
+
styles.container,
|
|
27
|
+
{
|
|
28
|
+
backgroundColor: colors.surface,
|
|
29
|
+
borderColor: colors.border,
|
|
30
|
+
},
|
|
31
|
+
style,
|
|
32
|
+
]}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
</View>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const styles = StyleSheet.create({
|
|
40
|
+
container: {
|
|
41
|
+
borderRadius: radius.lg,
|
|
42
|
+
borderWidth: 1,
|
|
43
|
+
padding: spacing.md,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleProp, StyleSheet, Text, useColorScheme, View, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { getColors } from '../../tokens/colors';
|
|
5
|
+
import { fontSizes, fontWeights } from '../../tokens/typography';
|
|
6
|
+
|
|
7
|
+
export type AvatarSize = 'small' | 'medium' | 'large';
|
|
8
|
+
|
|
9
|
+
const DIMENSIONS: Record<AvatarSize, number> = {
|
|
10
|
+
small: 32,
|
|
11
|
+
medium: 44,
|
|
12
|
+
large: 64,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface AvatarProps {
|
|
16
|
+
initials: string;
|
|
17
|
+
size?: AvatarSize;
|
|
18
|
+
style?: StyleProp<ViewStyle>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A circular avatar showing a fallback initials label. */
|
|
22
|
+
export const Avatar: React.FC<AvatarProps> = ({ initials, size = 'medium', style }) => {
|
|
23
|
+
const scheme = useColorScheme();
|
|
24
|
+
const colors = getColors(scheme);
|
|
25
|
+
const dimension = DIMENSIONS[size];
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View
|
|
29
|
+
accessibilityLabel={initials}
|
|
30
|
+
style={[
|
|
31
|
+
styles.container,
|
|
32
|
+
{
|
|
33
|
+
width: dimension,
|
|
34
|
+
height: dimension,
|
|
35
|
+
borderRadius: dimension / 2,
|
|
36
|
+
backgroundColor: colors.primary,
|
|
37
|
+
},
|
|
38
|
+
style,
|
|
39
|
+
]}
|
|
40
|
+
>
|
|
41
|
+
<Text style={{ color: colors.primaryText, fontSize: fontSizes.sm, fontWeight: fontWeights.semibold }}>
|
|
42
|
+
{initials}
|
|
43
|
+
</Text>
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const styles = StyleSheet.create({
|
|
49
|
+
container: {
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
justifyContent: 'center',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
StyleProp,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
useColorScheme,
|
|
8
|
+
View,
|
|
9
|
+
ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
import { getColors } from '../../tokens/colors';
|
|
13
|
+
import { radius } from '../../tokens/radius';
|
|
14
|
+
import { spacing } from '../../tokens/spacing';
|
|
15
|
+
import { fontSizes } from '../../tokens/typography';
|
|
16
|
+
|
|
17
|
+
export interface ChipProps {
|
|
18
|
+
text: string;
|
|
19
|
+
onRemove?: () => void;
|
|
20
|
+
style?: StyleProp<ViewStyle>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A compact tag/chip with an optional trailing remove button. */
|
|
24
|
+
export const Chip: React.FC<ChipProps> = ({ text, onRemove, style }) => {
|
|
25
|
+
const scheme = useColorScheme();
|
|
26
|
+
const colors = getColors(scheme);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<View
|
|
30
|
+
style={[styles.container, { backgroundColor: colors.surfaceAlt }, style]}
|
|
31
|
+
accessibilityLabel={text}
|
|
32
|
+
>
|
|
33
|
+
<Text style={{ color: colors.textPrimary, fontSize: fontSizes.sm }}>{text}</Text>
|
|
34
|
+
{onRemove ? (
|
|
35
|
+
<TouchableOpacity
|
|
36
|
+
onPress={onRemove}
|
|
37
|
+
accessibilityRole="button"
|
|
38
|
+
accessibilityLabel={`Remove ${text}`}
|
|
39
|
+
style={styles.remove}
|
|
40
|
+
>
|
|
41
|
+
<Text style={{ color: colors.textSecondary }}>{'✕'}</Text>
|
|
42
|
+
</TouchableOpacity>
|
|
43
|
+
) : null}
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const styles = StyleSheet.create({
|
|
49
|
+
container: {
|
|
50
|
+
flexDirection: 'row',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
alignSelf: 'flex-start',
|
|
53
|
+
paddingHorizontal: spacing.sm,
|
|
54
|
+
paddingVertical: spacing.xs,
|
|
55
|
+
borderRadius: radius.full,
|
|
56
|
+
},
|
|
57
|
+
remove: {
|
|
58
|
+
marginLeft: spacing.xs,
|
|
59
|
+
minWidth: 20,
|
|
60
|
+
minHeight: 20,
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
justifyContent: 'center',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleProp, StyleSheet, Text, View, ViewStyle, useColorScheme } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { getColors } from '../../tokens/colors';
|
|
5
|
+
import { radius } from '../../tokens/radius';
|
|
6
|
+
import { spacing } from '../../tokens/spacing';
|
|
7
|
+
import { fontSizes } from '../../tokens/typography';
|
|
8
|
+
|
|
9
|
+
export interface CountBadgeProps {
|
|
10
|
+
count?: number;
|
|
11
|
+
maxDisplayCount?: number;
|
|
12
|
+
style?: StyleProp<ViewStyle>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A small numeric/dot badge meant to overlay an icon (e.g. a bell with an
|
|
17
|
+
* unread count). Distinct from this library's own `Badge`, which is a
|
|
18
|
+
* standalone labeled pill.
|
|
19
|
+
*/
|
|
20
|
+
export const CountBadge: React.FC<CountBadgeProps> = ({ count, maxDisplayCount = 99, style }) => {
|
|
21
|
+
const scheme = useColorScheme();
|
|
22
|
+
const colors = getColors(scheme);
|
|
23
|
+
|
|
24
|
+
if (count != null && count > 0) {
|
|
25
|
+
const display = count > maxDisplayCount ? `${maxDisplayCount}+` : `${count}`;
|
|
26
|
+
return (
|
|
27
|
+
<View
|
|
28
|
+
style={[styles.numeric, { backgroundColor: colors.destructive }, style]}
|
|
29
|
+
accessibilityLabel={`${count} unread`}
|
|
30
|
+
>
|
|
31
|
+
<Text style={{ color: colors.destructiveText, fontSize: fontSizes.xs }}>{display}</Text>
|
|
32
|
+
</View>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<View
|
|
38
|
+
style={[styles.dot, { backgroundColor: colors.destructive }, style]}
|
|
39
|
+
accessibilityLabel="New"
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const styles = StyleSheet.create({
|
|
45
|
+
numeric: {
|
|
46
|
+
minWidth: 16,
|
|
47
|
+
height: 16,
|
|
48
|
+
borderRadius: radius.full,
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
paddingHorizontal: spacing.xs,
|
|
52
|
+
},
|
|
53
|
+
dot: {
|
|
54
|
+
width: 10,
|
|
55
|
+
height: 10,
|
|
56
|
+
borderRadius: 5,
|
|
57
|
+
},
|
|
58
|
+
});
|