@bigbinary/neetoui-rn 3.0.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -1
- package/lib/typescript/src/components/BottomSheet/BottomSheet.d.ts +8 -1
- package/lib/typescript/src/components/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatInput/AttachmentsView.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatInput/ChatInput.d.ts.map +1 -1
- package/lib/typescript/src/components/CheckBox/CheckBox.d.ts +7 -1
- package/lib/typescript/src/components/CheckBox/CheckBox.d.ts.map +1 -1
- package/lib/typescript/src/components/Input/Input.d.ts +12 -11
- package/lib/typescript/src/components/Input/Input.d.ts.map +1 -1
- package/lib/typescript/src/components/Input/InputFrame.d.ts +41 -0
- package/lib/typescript/src/components/Input/InputFrame.d.ts.map +1 -0
- package/lib/typescript/src/components/Input/index.d.ts +1 -0
- package/lib/typescript/src/components/Input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/NeetoUIProvider/NeetoUIProvider.d.ts.map +1 -1
- package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +6 -1
- package/lib/typescript/src/components/RadioButton/RadioButton.d.ts.map +1 -1
- package/lib/typescript/src/components/SearchBar/SearchBar.d.ts.map +1 -1
- package/lib/typescript/src/components/Skeleton/Skeleton.d.ts.map +1 -1
- package/lib/typescript/src/components/Typography/Typography.d.ts +5 -0
- package/lib/typescript/src/components/Typography/Typography.d.ts.map +1 -1
- package/lib/typescript/src/components/Typography/index.d.ts +1 -1
- package/lib/typescript/src/components/Typography/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/unistyles.d.ts +28 -1
- package/lib/typescript/src/unistyles.d.ts.map +1 -1
- package/lib/typescript/src/utils/scale.d.ts +22 -0
- package/lib/typescript/src/utils/scale.d.ts.map +1 -1
- package/package.json +8 -6
- package/src/components/Alert/Alert.tsx +51 -44
- package/src/components/BottomSheet/BottomSheet.tsx +98 -55
- package/src/components/BottomSheetMenu/BottomSheetMenu.tsx +55 -48
- package/src/components/ChatInput/AttachmentsView.tsx +32 -18
- package/src/components/ChatInput/ChatInput.tsx +148 -99
- package/src/components/ChatInput/IconButton.tsx +21 -10
- package/src/components/ChatInput/MentionsInput.tsx +34 -27
- package/src/components/CheckBox/CheckBox.tsx +54 -33
- package/src/components/EmptyView/EmptyView.tsx +27 -20
- package/src/components/Header/Header.tsx +25 -18
- package/src/components/Header/ScreenHeader.tsx +17 -10
- package/src/components/Input/Input.tsx +111 -67
- package/src/components/Input/InputFrame.tsx +261 -0
- package/src/components/Input/index.ts +1 -0
- package/src/components/NeetoUIProvider/NeetoUIProvider.tsx +2 -1
- package/src/components/OnBoarding/OnBoarding.tsx +61 -54
- package/src/components/RadioButton/RadioButton.tsx +72 -31
- package/src/components/SearchBar/SearchBar.tsx +34 -26
- package/src/components/Skeleton/Skeleton.tsx +18 -11
- package/src/components/Toast/Toast.tsx +38 -31
- package/src/components/Typography/Typography.tsx +31 -2
- package/src/components/Typography/index.ts +5 -1
- package/src/index.ts +7 -2
- package/src/unistyles-init.ts +4 -0
- package/src/unistyles.ts +52 -4
- package/src/utils/scale.ts +36 -9
- package/src/types/neeto-icons-rn.d.ts +0 -33
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type StyleProp, type TextStyle } from "react-native";
|
|
1
|
+
import RadioInactive from "@bigbinary/neeto-icons-rn/icons/RadioInactive";
|
|
2
|
+
import { View, type StyleProp, type TextStyle } from "react-native";
|
|
3
3
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
4
4
|
|
|
5
|
-
import { moderateScale } from "../../utils/scale";
|
|
5
|
+
import { moderateScale, scaleFor } from "../../utils/scale";
|
|
6
6
|
import { Touchable, type TouchableProps } from "../Touchable";
|
|
7
7
|
import { Typography } from "../Typography";
|
|
8
8
|
|
|
@@ -13,8 +13,21 @@ export type RadioButtonProps = Omit<TouchableProps, "onPress"> & {
|
|
|
13
13
|
label: string;
|
|
14
14
|
labelStyle?: StyleProp<TextStyle>;
|
|
15
15
|
iconSize?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Diameter of the filled dot. Defaults to 37.5% of the icon box, which is
|
|
18
|
+
* half the ring's outer diameter — the Material proportion.
|
|
19
|
+
*/
|
|
20
|
+
dotSize?: number;
|
|
16
21
|
};
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The icon set's `Radio` glyph fills a 24 box with a ring of radius 8.4 and
|
|
25
|
+
* a dot of radius 3, so the dot reads as a pinprick at the sizes apps use.
|
|
26
|
+
* Drawing the dot here over the plain `RadioInactive` ring keeps both states
|
|
27
|
+
* on identical ring geometry and makes the fill sizeable.
|
|
28
|
+
*/
|
|
29
|
+
const DOT_RATIO = 0.375;
|
|
30
|
+
|
|
18
31
|
export const RadioButton = ({
|
|
19
32
|
selected = false,
|
|
20
33
|
onSelect,
|
|
@@ -22,6 +35,7 @@ export const RadioButton = ({
|
|
|
22
35
|
label,
|
|
23
36
|
labelStyle,
|
|
24
37
|
iconSize,
|
|
38
|
+
dotSize,
|
|
25
39
|
style,
|
|
26
40
|
...rest
|
|
27
41
|
}: RadioButtonProps) => {
|
|
@@ -29,7 +43,8 @@ export const RadioButton = ({
|
|
|
29
43
|
|
|
30
44
|
styles.useVariants({ selected, disabled });
|
|
31
45
|
|
|
32
|
-
const
|
|
46
|
+
const size = iconSize ?? moderateScale(22);
|
|
47
|
+
const dot = dotSize ?? size * DOT_RATIO;
|
|
33
48
|
|
|
34
49
|
return (
|
|
35
50
|
<Touchable
|
|
@@ -43,38 +58,64 @@ export const RadioButton = ({
|
|
|
43
58
|
<Typography size="m" style={[styles.label, labelStyle]}>
|
|
44
59
|
{label}
|
|
45
60
|
</Typography>
|
|
46
|
-
<
|
|
47
|
-
color={theme.colors.text.primary}
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
<View style={styles.iconWrapper}>
|
|
62
|
+
<RadioInactive color={theme.colors.text.primary} size={size} />
|
|
63
|
+
{selected && (
|
|
64
|
+
<View
|
|
65
|
+
style={[
|
|
66
|
+
styles.dot,
|
|
67
|
+
{
|
|
68
|
+
backgroundColor: theme.colors.text.primary,
|
|
69
|
+
borderRadius: dot / 2,
|
|
70
|
+
height: dot,
|
|
71
|
+
width: dot,
|
|
72
|
+
},
|
|
73
|
+
]}
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</View>
|
|
50
77
|
</Touchable>
|
|
51
78
|
);
|
|
52
79
|
};
|
|
53
80
|
|
|
54
|
-
const styles = StyleSheet.create(theme =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
const styles = StyleSheet.create((theme, rt) => {
|
|
82
|
+
// Reading rt.screen registers the dependency that has this sheet
|
|
83
|
+
// recomputed on a window resize, so scaled sizes follow rotation,
|
|
84
|
+
// split-screen and foldables instead of freezing at first render.
|
|
85
|
+
const moderateScale = scaleFor(rt.screen);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
container: {
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
flexDirection: "row",
|
|
91
|
+
paddingVertical: moderateScale(8),
|
|
92
|
+
},
|
|
93
|
+
iconWrapper: {
|
|
94
|
+
alignItems: "center",
|
|
95
|
+
justifyContent: "center",
|
|
96
|
+
},
|
|
97
|
+
dot: {
|
|
98
|
+
position: "absolute",
|
|
99
|
+
},
|
|
100
|
+
label: {
|
|
101
|
+
flex: 1,
|
|
102
|
+
lineHeight: moderateScale(22),
|
|
103
|
+
variants: {
|
|
104
|
+
selected: {
|
|
105
|
+
true: {
|
|
106
|
+
color: theme.colors.text.primary,
|
|
107
|
+
fontFamily: theme.fonts.medium,
|
|
108
|
+
},
|
|
109
|
+
false: {
|
|
110
|
+
color: theme.colors.text.secondary,
|
|
111
|
+
fontFamily: theme.fonts.regular,
|
|
112
|
+
},
|
|
68
113
|
},
|
|
69
|
-
|
|
70
|
-
color: theme.colors.
|
|
71
|
-
|
|
114
|
+
disabled: {
|
|
115
|
+
true: { color: theme.colors.palette.grey400 },
|
|
116
|
+
false: {},
|
|
72
117
|
},
|
|
73
118
|
},
|
|
74
|
-
disabled: {
|
|
75
|
-
true: { color: theme.colors.palette.grey400 },
|
|
76
|
-
false: {},
|
|
77
|
-
},
|
|
78
119
|
},
|
|
79
|
-
}
|
|
80
|
-
})
|
|
120
|
+
};
|
|
121
|
+
});
|
|
@@ -6,11 +6,12 @@ import {
|
|
|
6
6
|
type TextInputProps,
|
|
7
7
|
type ViewStyle,
|
|
8
8
|
} from "react-native";
|
|
9
|
-
import
|
|
9
|
+
import Close from "@bigbinary/neeto-icons-rn/icons/Close";
|
|
10
|
+
import Search from "@bigbinary/neeto-icons-rn/icons/Search";
|
|
10
11
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
11
12
|
|
|
12
13
|
import { useDebounce } from "../../hooks";
|
|
13
|
-
import { moderateScale } from "../../utils/scale";
|
|
14
|
+
import { moderateScale, scaleFor } from "../../utils/scale";
|
|
14
15
|
import { Touchable } from "../Touchable";
|
|
15
16
|
|
|
16
17
|
export type SearchBarProps = {
|
|
@@ -96,27 +97,34 @@ export const SearchBar = ({
|
|
|
96
97
|
);
|
|
97
98
|
};
|
|
98
99
|
|
|
99
|
-
const styles = StyleSheet.create(theme =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
100
|
+
const styles = StyleSheet.create((theme, rt) => {
|
|
101
|
+
// Reading rt.screen registers the dependency that has this sheet
|
|
102
|
+
// recomputed on a window resize, so scaled sizes follow rotation,
|
|
103
|
+
// split-screen and foldables instead of freezing at first render.
|
|
104
|
+
const moderateScale = scaleFor(rt.screen);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
container: {
|
|
108
|
+
alignItems: "center",
|
|
109
|
+
borderColor: theme.colors.palette.grey400,
|
|
110
|
+
borderRadius: moderateScale(8),
|
|
111
|
+
borderWidth: 1,
|
|
112
|
+
flexDirection: "row",
|
|
113
|
+
gap: moderateScale(8),
|
|
114
|
+
height: moderateScale(42),
|
|
115
|
+
paddingHorizontal: moderateScale(8),
|
|
116
|
+
},
|
|
117
|
+
input: {
|
|
118
|
+
color: theme.colors.text.primary,
|
|
119
|
+
flex: 1,
|
|
120
|
+
fontFamily: theme.fonts.regular,
|
|
121
|
+
fontSize: moderateScale(16),
|
|
122
|
+
padding: 0,
|
|
123
|
+
},
|
|
124
|
+
cancelButton: {
|
|
125
|
+
height: "100%",
|
|
126
|
+
justifyContent: "center",
|
|
127
|
+
paddingHorizontal: moderateScale(8),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
});
|
|
@@ -10,7 +10,7 @@ import Animated, {
|
|
|
10
10
|
} from "react-native-reanimated";
|
|
11
11
|
import { StyleSheet, type UnistylesVariants } from "react-native-unistyles";
|
|
12
12
|
|
|
13
|
-
import { moderateScale } from "../../utils/scale";
|
|
13
|
+
import { moderateScale, scaleFor } from "../../utils/scale";
|
|
14
14
|
|
|
15
15
|
type SkeletonVariants = UnistylesVariants<typeof styles>;
|
|
16
16
|
|
|
@@ -107,15 +107,22 @@ const SkeletonGroup = ({
|
|
|
107
107
|
|
|
108
108
|
Skeleton.Group = SkeletonGroup;
|
|
109
109
|
|
|
110
|
-
const styles = StyleSheet.create(theme =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
const styles = StyleSheet.create((theme, rt) => {
|
|
111
|
+
// Reading rt.screen registers the dependency that has this sheet
|
|
112
|
+
// recomputed on a window resize, so scaled sizes follow rotation,
|
|
113
|
+
// split-screen and foldables instead of freezing at first render.
|
|
114
|
+
const moderateScale = scaleFor(rt.screen);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
base: {
|
|
118
|
+
backgroundColor: theme.colors.palette.grey200,
|
|
119
|
+
variants: {
|
|
120
|
+
variant: {
|
|
121
|
+
rect: { borderRadius: moderateScale(6) },
|
|
122
|
+
line: { borderRadius: moderateScale(4) },
|
|
123
|
+
circle: { borderRadius: 9999 },
|
|
124
|
+
},
|
|
118
125
|
},
|
|
119
126
|
},
|
|
120
|
-
}
|
|
121
|
-
})
|
|
127
|
+
};
|
|
128
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { Pressable, View, useWindowDimensions } from "react-native";
|
|
3
|
-
import
|
|
3
|
+
import Close from "@bigbinary/neeto-icons-rn/icons/Close";
|
|
4
4
|
import ToastMessage, {
|
|
5
5
|
type ToastConfig,
|
|
6
6
|
type ToastConfigParams,
|
|
@@ -9,7 +9,7 @@ import ToastMessage, {
|
|
|
9
9
|
} from "react-native-toast-message";
|
|
10
10
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
11
11
|
|
|
12
|
-
import { moderateScale } from "../../utils/scale";
|
|
12
|
+
import { moderateScale, scaleFor } from "../../utils/scale";
|
|
13
13
|
import { Typography } from "../Typography";
|
|
14
14
|
import { ToastGlyph } from "./ToastGlyph";
|
|
15
15
|
|
|
@@ -126,32 +126,39 @@ Toast.show = (params: ToastShowParams) =>
|
|
|
126
126
|
ToastMessage.show({ autoHide: false, ...params });
|
|
127
127
|
Toast.hide = () => ToastMessage.hide();
|
|
128
128
|
|
|
129
|
-
const styles = StyleSheet.create(theme =>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
129
|
+
const styles = StyleSheet.create((theme, rt) => {
|
|
130
|
+
// Reading rt.screen registers the dependency that has this sheet
|
|
131
|
+
// recomputed on a window resize, so scaled sizes follow rotation,
|
|
132
|
+
// split-screen and foldables instead of freezing at first render.
|
|
133
|
+
const moderateScale = scaleFor(rt.screen);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
card: {
|
|
137
|
+
alignItems: "center",
|
|
138
|
+
backgroundColor: theme.colors.background.secondary,
|
|
139
|
+
borderRadius: moderateScale(16),
|
|
140
|
+
flexDirection: "row",
|
|
141
|
+
gap: moderateScale(10),
|
|
142
|
+
marginHorizontal: moderateScale(10),
|
|
143
|
+
minHeight: moderateScale(60),
|
|
144
|
+
paddingHorizontal: moderateScale(10),
|
|
145
|
+
paddingVertical: moderateScale(8),
|
|
146
|
+
...theme.elevation.high,
|
|
147
|
+
},
|
|
148
|
+
iconSquare: {
|
|
149
|
+
alignItems: "center",
|
|
150
|
+
borderRadius: moderateScale(10),
|
|
151
|
+
height: moderateScale(42),
|
|
152
|
+
justifyContent: "center",
|
|
153
|
+
width: moderateScale(42),
|
|
154
|
+
},
|
|
155
|
+
textBlock: {
|
|
156
|
+
flex: 1,
|
|
157
|
+
gap: moderateScale(2),
|
|
158
|
+
paddingVertical: moderateScale(3),
|
|
159
|
+
},
|
|
160
|
+
description: {
|
|
161
|
+
color: theme.colors.text.secondary,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
});
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Platform,
|
|
3
|
+
StyleSheet as RNStyleSheet,
|
|
4
|
+
Text,
|
|
5
|
+
type TextProps,
|
|
6
|
+
type TextStyle,
|
|
7
|
+
} from "react-native";
|
|
2
8
|
import { StyleSheet, type UnistylesVariants } from "react-native-unistyles";
|
|
3
9
|
|
|
4
10
|
type TypographyVariants = UnistylesVariants<typeof styles>;
|
|
5
11
|
|
|
12
|
+
/**
|
|
13
|
+
* v2's Android guard multiplier, shared with InputFrame so the floating
|
|
14
|
+
* label and the text it stands in for never disagree on line metrics.
|
|
15
|
+
*/
|
|
16
|
+
export const LINE_HEIGHT_RATIO = 1.3;
|
|
17
|
+
|
|
6
18
|
export type TypographyProps = TextProps &
|
|
7
19
|
TypographyVariants & {
|
|
8
20
|
color?: string;
|
|
@@ -19,8 +31,25 @@ export const Typography = ({
|
|
|
19
31
|
}: TypographyProps) => {
|
|
20
32
|
styles.useVariants({ variant, size, weight });
|
|
21
33
|
|
|
34
|
+
const mergedStyle = [styles.text, !!color && { color }, style];
|
|
35
|
+
|
|
36
|
+
// v2's Typography always rendered Android text with includeFontPadding
|
|
37
|
+
// off, vertical centering, and a 1.3x lineHeight computed from the
|
|
38
|
+
// resolved fontSize. Without that guard, SF Pro's tight metrics clip
|
|
39
|
+
// descenders (g/j/y) on Android 15+ (RN >= 0.78 text rendering).
|
|
40
|
+
let androidGuard: TextStyle | false = false;
|
|
41
|
+
if (Platform.OS === "android") {
|
|
42
|
+
const flat = (RNStyleSheet.flatten(mergedStyle) ?? {}) as TextStyle;
|
|
43
|
+
const fontSize = typeof flat.fontSize === "number" ? flat.fontSize : 14;
|
|
44
|
+
androidGuard = {
|
|
45
|
+
includeFontPadding: false,
|
|
46
|
+
textAlignVertical: "center",
|
|
47
|
+
lineHeight: flat.lineHeight ?? Math.floor(fontSize * LINE_HEIGHT_RATIO),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
22
51
|
return (
|
|
23
|
-
<Text {...rest} style={[
|
|
52
|
+
<Text {...rest} style={[...mergedStyle, androidGuard]}>
|
|
24
53
|
{children}
|
|
25
54
|
</Text>
|
|
26
55
|
);
|
package/src/index.ts
CHANGED
|
@@ -39,7 +39,12 @@ export {
|
|
|
39
39
|
type HeaderProps,
|
|
40
40
|
type ScreenHeaderProps,
|
|
41
41
|
} from "./components/Header";
|
|
42
|
-
export {
|
|
42
|
+
export {
|
|
43
|
+
Input,
|
|
44
|
+
InputFrame,
|
|
45
|
+
type InputProps,
|
|
46
|
+
type InputFrameProps,
|
|
47
|
+
} from "./components/Input";
|
|
43
48
|
export { Loader, type LoaderProps } from "./components/Loader";
|
|
44
49
|
export {
|
|
45
50
|
NeetoUIProvider,
|
|
@@ -64,5 +69,5 @@ export {
|
|
|
64
69
|
lightTheme,
|
|
65
70
|
type NeetoTheme,
|
|
66
71
|
} from "./theme/tokens";
|
|
67
|
-
export { configureNeetoUI } from "./unistyles";
|
|
72
|
+
export { configureNeetoUI, type ConfigureNeetoUIOptions } from "./unistyles";
|
|
68
73
|
export { getShadowStyles, isValidEmail, moderateScale } from "./utils";
|
package/src/unistyles-init.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
//
|
|
12
12
|
// This file (and only this file) is declared in package.json
|
|
13
13
|
// "sideEffects" so bundlers never tree-shake the bare import away.
|
|
14
|
+
//
|
|
15
|
+
// Registering with the defaults (light theme, no OS following) is safe even
|
|
16
|
+
// for an app that wants adaptive themes: its own configureNeetoUI({
|
|
17
|
+
// adaptiveThemes: true }) call is honoured whenever it lands.
|
|
14
18
|
import { configureNeetoUI } from "./unistyles";
|
|
15
19
|
|
|
16
20
|
configureNeetoUI();
|
package/src/unistyles.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StyleSheet } from "react-native-unistyles";
|
|
1
|
+
import { StyleSheet, UnistylesRuntime } from "react-native-unistyles";
|
|
2
2
|
|
|
3
3
|
import { breakpoints, darkTheme, lightTheme } from "./theme/tokens";
|
|
4
4
|
|
|
@@ -14,6 +14,22 @@ declare module "react-native-unistyles" {
|
|
|
14
14
|
export interface UnistylesBreakpoints extends AppBreakpoints {}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export type ConfigureNeetoUIOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* Follow the device colour scheme, swapping to the dark theme whenever the
|
|
20
|
+
* OS is in dark mode. Off by default.
|
|
21
|
+
*
|
|
22
|
+
* An app that has not designed for dark only half-follows: the surfaces
|
|
23
|
+
* styled through theme tokens go dark while everything painted from a
|
|
24
|
+
* literal colour stays light, which reads as dark text on dark
|
|
25
|
+
* backgrounds, light cards adrift on black, and a dark status bar under a
|
|
26
|
+
* `dark-content` bar style. Following the OS is therefore something an app
|
|
27
|
+
* opts into once its own screens are ready, not something the library
|
|
28
|
+
* decides for it.
|
|
29
|
+
*/
|
|
30
|
+
adaptiveThemes?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
17
33
|
let isConfigured = false;
|
|
18
34
|
|
|
19
35
|
/**
|
|
@@ -21,14 +37,46 @@ let isConfigured = false;
|
|
|
21
37
|
* library root is imported (src/unistyles-init.ts) because Unistyles
|
|
22
38
|
* requires configure() before any module-scope StyleSheet.create;
|
|
23
39
|
* NeetoUIProvider and tests may call it again safely.
|
|
40
|
+
*
|
|
41
|
+
* Apps should also call it from their entry point, through the
|
|
42
|
+
* "@bigbinary/neetoui-rn/unistyles" subpath. Metro's inlineRequires defers a
|
|
43
|
+
* named import until the expression that uses it, so importing
|
|
44
|
+
* NeetoUIProvider does not guarantee the library root — and this side
|
|
45
|
+
* effect — runs before the screen modules a default import pulls in eagerly.
|
|
46
|
+
*
|
|
47
|
+
* That ordering is also why an explicit `options` argument is honoured after
|
|
48
|
+
* the first call: the library root usually wins the race and registers the
|
|
49
|
+
* defaults, so an app asking for `adaptiveThemes` would otherwise be
|
|
50
|
+
* silently ignored. Argument-less calls never change an already-applied
|
|
51
|
+
* mode, which keeps NeetoUIProvider's fallback from clobbering that opt-in.
|
|
24
52
|
*/
|
|
25
|
-
export const configureNeetoUI = () => {
|
|
26
|
-
|
|
53
|
+
export const configureNeetoUI = (options?: ConfigureNeetoUIOptions) => {
|
|
54
|
+
const adaptiveThemes = options?.adaptiveThemes ?? false;
|
|
55
|
+
|
|
56
|
+
if (isConfigured) {
|
|
57
|
+
if (options) applyThemeMode(adaptiveThemes);
|
|
58
|
+
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
27
62
|
isConfigured = true;
|
|
28
63
|
|
|
29
64
|
StyleSheet.configure({
|
|
30
65
|
themes: { light: lightTheme, dark: darkTheme },
|
|
31
66
|
breakpoints,
|
|
32
|
-
|
|
67
|
+
// Unistyles requires exactly one of the two: adaptive themes, or a
|
|
68
|
+
// starting theme it can resolve on its own.
|
|
69
|
+
settings: adaptiveThemes
|
|
70
|
+
? { adaptiveThemes: true }
|
|
71
|
+
: { initialTheme: "light" },
|
|
33
72
|
});
|
|
34
73
|
};
|
|
74
|
+
|
|
75
|
+
const applyThemeMode = (adaptiveThemes: boolean) => {
|
|
76
|
+
if (UnistylesRuntime.hasAdaptiveThemes === adaptiveThemes) return;
|
|
77
|
+
|
|
78
|
+
UnistylesRuntime.setAdaptiveThemes(adaptiveThemes);
|
|
79
|
+
// Turning adaptive themes off leaves whichever theme the OS last selected
|
|
80
|
+
// in place, so a device already in dark mode would stay dark.
|
|
81
|
+
if (!adaptiveThemes) UnistylesRuntime.setTheme("light");
|
|
82
|
+
};
|
package/src/utils/scale.ts
CHANGED
|
@@ -1,16 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UnistylesRuntime } from "react-native-unistyles";
|
|
2
2
|
|
|
3
3
|
const GUIDELINE_BASE_WIDTH = 350;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The short screen edge, so a size scales the same in portrait and landscape.
|
|
7
|
+
* Falls back to the baseline before unistyles has measured (and under the jest
|
|
8
|
+
* mock, whose runtime reports 0x0), leaving sizes untouched.
|
|
9
|
+
*/
|
|
10
|
+
const shortDimension = ({ width, height }: { width: number; height: number }) =>
|
|
11
|
+
Math.min(width, height) || GUIDELINE_BASE_WIDTH;
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
/** react-native-size-matters' formula, rounded exactly as v2's did. */
|
|
14
|
+
const scale = (screenSize: number, size: number, factor: number) =>
|
|
15
|
+
Math.round(
|
|
16
|
+
size + ((screenSize / GUIDELINE_BASE_WIDTH) * size - size) * factor
|
|
17
|
+
);
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Scales bound to a given screen. Pass `rt.screen` from a unistyles
|
|
21
|
+
* StyleSheet: reading it is what registers the dependency, so the sheet is
|
|
22
|
+
* recomputed when the window resizes — rotation, split-screen, foldables,
|
|
23
|
+
* iPad Slide Over.
|
|
24
|
+
*
|
|
25
|
+
* const styles = StyleSheet.create((theme, rt) => {
|
|
26
|
+
* const moderateScale = scaleFor(rt.screen);
|
|
27
|
+
*
|
|
28
|
+
* return { card: { padding: moderateScale(16) } };
|
|
29
|
+
* });
|
|
30
|
+
*/
|
|
31
|
+
export const scaleFor =
|
|
32
|
+
(screen: { width: number; height: number }) =>
|
|
33
|
+
(size: number, factor = 0.5) =>
|
|
34
|
+
scale(shortDimension(screen), size, factor);
|
|
13
35
|
|
|
14
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Screen-independent entry point, for sizes computed outside a stylesheet —
|
|
38
|
+
* icon props, measured layouts. Reads the runtime on every call, so it is
|
|
39
|
+
* current whenever the component re-renders, but it cannot subscribe a
|
|
40
|
+
* stylesheet to resizes: use scaleFor inside StyleSheet.create.
|
|
41
|
+
*/
|
|
15
42
|
export const moderateScale = (size: number, factor = 0.5) =>
|
|
16
|
-
|
|
43
|
+
scale(shortDimension(UnistylesRuntime.screen), size, factor);
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// @bigbinary/neeto-icons-rn ships no TypeScript types (and an exports map
|
|
2
|
-
// whose "." entry is an extensionless directory only the metro resolver
|
|
3
|
-
// accepts). Minimal ambient typing until the icons package ships its own.
|
|
4
|
-
declare module "@bigbinary/neeto-icons-rn" {
|
|
5
|
-
import { type ComponentType } from "react";
|
|
6
|
-
|
|
7
|
-
export type NeetoIconProps = {
|
|
8
|
-
size?: number;
|
|
9
|
-
color?: string;
|
|
10
|
-
strokeWidth?: number;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const Attachment: ComponentType<NeetoIconProps>;
|
|
14
|
-
export const BookOpen: ComponentType<NeetoIconProps>;
|
|
15
|
-
export const CannedResponses: ComponentType<NeetoIconProps>;
|
|
16
|
-
export const ChatBubble: ComponentType<NeetoIconProps>;
|
|
17
|
-
export const Check: ComponentType<NeetoIconProps>;
|
|
18
|
-
export const Delete: ComponentType<NeetoIconProps>;
|
|
19
|
-
export const Down: ComponentType<NeetoIconProps>;
|
|
20
|
-
export const Edit: ComponentType<NeetoIconProps>;
|
|
21
|
-
export const Expand: ComponentType<NeetoIconProps>;
|
|
22
|
-
export const Forward: ComponentType<NeetoIconProps>;
|
|
23
|
-
export const LeftArrow: ComponentType<NeetoIconProps>;
|
|
24
|
-
export const Minimize: ComponentType<NeetoIconProps>;
|
|
25
|
-
export const Notes: ComponentType<NeetoIconProps>;
|
|
26
|
-
export const Radio: ComponentType<NeetoIconProps>;
|
|
27
|
-
export const SendPlane: ComponentType<NeetoIconProps>;
|
|
28
|
-
export const RadioInactive: ComponentType<NeetoIconProps>;
|
|
29
|
-
export const Checkbox: ComponentType<NeetoIconProps>;
|
|
30
|
-
export const CheckboxInactive: ComponentType<NeetoIconProps>;
|
|
31
|
-
export const Search: ComponentType<NeetoIconProps>;
|
|
32
|
-
export const Close: ComponentType<NeetoIconProps>;
|
|
33
|
-
}
|