@mrmeg/expo-ui 0.14.0 → 0.15.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/CHANGELOG.md +23 -0
- package/README.md +6 -6
- package/dist/components/Accordion.d.ts +1 -1
- package/dist/components/Accordion.js +25 -19
- package/dist/components/Badge.js +6 -5
- package/dist/components/BottomSheet.js +21 -19
- package/dist/components/Button.d.ts +2 -1
- package/dist/components/Button.js +14 -4
- package/dist/components/Checkbox.js +49 -19
- package/dist/components/Collapsible.d.ts +1 -1
- package/dist/components/Collapsible.js +22 -16
- package/dist/components/Drawer.js +8 -7
- package/dist/components/DropdownMenu.d.ts +1 -1
- package/dist/components/DropdownMenu.js +80 -59
- package/dist/components/EmptyState.d.ts +7 -1
- package/dist/components/EmptyState.js +23 -7
- package/dist/components/InputOTP.js +6 -2
- package/dist/components/Item.d.ts +93 -0
- package/dist/components/Item.js +125 -0
- package/dist/components/Label.js +7 -2
- package/dist/components/Notification.js +10 -22
- package/dist/components/Popover.d.ts +3 -18
- package/dist/components/Popover.js +10 -6
- package/dist/components/RadioGroup.js +49 -22
- package/dist/components/SectionHeader.d.ts +42 -0
- package/dist/components/SectionHeader.js +41 -0
- package/dist/components/Select.js +73 -41
- package/dist/components/Skeleton.js +2 -2
- package/dist/components/StatCard.d.ts +45 -0
- package/dist/components/StatCard.js +41 -0
- package/dist/components/StyledText.d.ts +20 -10
- package/dist/components/StyledText.js +64 -25
- package/dist/components/Switch.js +84 -57
- package/dist/components/Tabs.js +43 -12
- package/dist/components/TextInput.js +8 -9
- package/dist/components/Toggle.js +69 -42
- package/dist/components/ToggleGroup.js +82 -55
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +3 -0
- package/dist/constants/fonts.d.ts +3 -0
- package/dist/constants/fonts.js +35 -12
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/motion.d.ts +24 -0
- package/dist/constants/motion.js +24 -0
- package/dist/constants/spacing.d.ts +7 -7
- package/dist/constants/spacing.js +7 -7
- package/dist/hooks/useResources.d.ts +4 -2
- package/dist/hooks/useResources.js +30 -8
- package/dist/hooks/useStaggeredEntrance.d.ts +1 -1
- package/dist/hooks/useStaggeredEntrance.js +2 -1
- package/dist/hooks/useTheme.d.ts +1 -1
- package/dist/hooks/useTheme.js +53 -16
- package/package.json +20 -19
|
@@ -3,9 +3,10 @@ import { Icon } from "./Icon.js";
|
|
|
3
3
|
import { TextClassContext, TextColorContext, TextSelectabilityContext } from "./StyledText.context";
|
|
4
4
|
import { spacing } from "../constants/spacing.js";
|
|
5
5
|
import { useTheme } from "../hooks/useTheme.js";
|
|
6
|
+
import { useScalePress } from "../hooks/useScalePress.js";
|
|
6
7
|
import * as ToggleGroupPrimitive from "@rn-primitives/toggle-group";
|
|
7
8
|
import * as React from "react";
|
|
8
|
-
import { Platform, StyleSheet } from "react-native";
|
|
9
|
+
import { Animated, Platform, StyleSheet } from "react-native";
|
|
9
10
|
const DEFAULT_HIT_SLOP = 8;
|
|
10
11
|
// Size configurations (same as Toggle)
|
|
11
12
|
const TOGGLE_GROUP_SIZES = {
|
|
@@ -101,10 +102,35 @@ function ToggleGroup({ variant = "default", size = "default", children, ...props
|
|
|
101
102
|
* Position (first/last) is automatically detected for rounded corners
|
|
102
103
|
*/
|
|
103
104
|
function ToggleGroupItem({ isFirst = false, isLast = false, children, ...props }) {
|
|
104
|
-
const { theme, withAlpha, getContrastingColor } = useTheme();
|
|
105
|
+
const { theme, withAlpha, getContrastingColor, getFocusRingStyle } = useTheme();
|
|
105
106
|
const context = useToggleGroupContext();
|
|
106
107
|
const { value: groupValue } = ToggleGroupPrimitive.useRootContext();
|
|
107
108
|
const sizeConfig = TOGGLE_GROUP_SIZES[context.size];
|
|
109
|
+
const focusRingStyle = getFocusRingStyle();
|
|
110
|
+
const [focused, setFocused] = React.useState(false);
|
|
111
|
+
const { animatedStyle: scaleStyle, pressHandlers } = useScalePress({
|
|
112
|
+
disabled: !!props.disabled,
|
|
113
|
+
scaleTo: 0.97,
|
|
114
|
+
haptic: false,
|
|
115
|
+
});
|
|
116
|
+
const showFocusRing = (event) => {
|
|
117
|
+
let ringVisible = true;
|
|
118
|
+
if (Platform.OS === "web") {
|
|
119
|
+
const target = event?.nativeEvent?.target;
|
|
120
|
+
if (target && typeof target.matches === "function") {
|
|
121
|
+
try {
|
|
122
|
+
ringVisible = target.matches(":focus-visible");
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
ringVisible = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
setFocused(ringVisible);
|
|
130
|
+
};
|
|
131
|
+
const hideFocusRing = () => {
|
|
132
|
+
setFocused(false);
|
|
133
|
+
};
|
|
108
134
|
// Check if this item is selected
|
|
109
135
|
const isSelected = ToggleGroupPrimitive.utils.getIsSelected(groupValue, props.value);
|
|
110
136
|
// Determine the actual background color for this item
|
|
@@ -119,60 +145,61 @@ function ToggleGroupItem({ isFirst = false, isLast = false, children, ...props }
|
|
|
119
145
|
const textColor = isSelected
|
|
120
146
|
? getContrastingColor(itemBgColor, theme.colors.foreground, theme.colors.background)
|
|
121
147
|
: theme.colors.foreground;
|
|
122
|
-
return (_jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(ToggleGroupPrimitive.Item, { ...props, style: {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
148
|
+
return (_jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(Animated.View, { style: scaleStyle, children: _jsx(ToggleGroupPrimitive.Item, { ...props, onPressIn: pressHandlers.onPressIn, onPressOut: pressHandlers.onPressOut, onFocus: showFocusRing, onBlur: hideFocusRing, style: {
|
|
149
|
+
...styles.item,
|
|
150
|
+
height: sizeConfig.height,
|
|
151
|
+
minWidth: sizeConfig.minWidth,
|
|
152
|
+
paddingHorizontal: sizeConfig.paddingHorizontal,
|
|
153
|
+
// Base variant styles
|
|
154
|
+
...(context.variant === "default" && !isSelected && {
|
|
155
|
+
backgroundColor: "transparent",
|
|
156
|
+
borderColor: theme.colors.border,
|
|
157
|
+
}),
|
|
158
|
+
...(context.variant === "default" && isSelected && {
|
|
159
|
+
backgroundColor: withAlpha(theme.colors.primary, 0.1),
|
|
160
|
+
borderColor: theme.colors.primary,
|
|
161
|
+
}),
|
|
162
|
+
// Outline variant styles
|
|
163
|
+
...(context.variant === "outline" && !isSelected && {
|
|
164
|
+
backgroundColor: "transparent",
|
|
165
|
+
borderColor: theme.colors.border,
|
|
166
|
+
}),
|
|
167
|
+
...(context.variant === "outline" && isSelected && {
|
|
168
|
+
backgroundColor: theme.colors.primary,
|
|
169
|
+
borderColor: theme.colors.primary,
|
|
170
|
+
}),
|
|
171
|
+
// Remove borders between items (except first)
|
|
172
|
+
...(context.variant === "outline" && !isFirst && {
|
|
173
|
+
borderLeftWidth: 0,
|
|
174
|
+
}),
|
|
175
|
+
// Border radius handling
|
|
176
|
+
borderRadius: 0, // Remove all radius by default
|
|
177
|
+
...(isFirst && {
|
|
178
|
+
borderTopLeftRadius: spacing.radiusMd,
|
|
179
|
+
borderBottomLeftRadius: spacing.radiusMd,
|
|
180
|
+
}),
|
|
181
|
+
...(isLast && {
|
|
182
|
+
borderTopRightRadius: spacing.radiusMd,
|
|
183
|
+
borderBottomRightRadius: spacing.radiusMd,
|
|
184
|
+
}),
|
|
185
|
+
// Single item (both first and last)
|
|
186
|
+
...(isFirst && isLast && {
|
|
187
|
+
borderRadius: spacing.radiusMd,
|
|
188
|
+
}),
|
|
189
|
+
// Disabled state
|
|
190
|
+
opacity: props.disabled ? 0.5 : 1,
|
|
191
|
+
// Web-specific styles
|
|
192
|
+
...(Platform.OS === "web" && {
|
|
193
|
+
cursor: props.disabled ? "not-allowed" : "pointer",
|
|
194
|
+
transition: "all 150ms",
|
|
195
|
+
userSelect: "none",
|
|
196
|
+
// Ensure proper z-index on focus
|
|
197
|
+
...(isSelected && {
|
|
198
|
+
zIndex: 10,
|
|
199
|
+
}),
|
|
173
200
|
}),
|
|
174
|
-
|
|
175
|
-
|
|
201
|
+
...(focused && !props.disabled ? focusRingStyle : null),
|
|
202
|
+
}, hitSlop: DEFAULT_HIT_SLOP, children: typeof children === "function" ? ((state) => (_jsx(TextSelectabilityContext.Provider, { value: false, children: children(state) }))) : (_jsx(TextSelectabilityContext.Provider, { value: false, children: children })) }) }) }) }));
|
|
176
203
|
}
|
|
177
204
|
function ToggleGroupIcon({ name, size, color }) {
|
|
178
205
|
const contextColor = React.use(TextColorContext);
|
|
@@ -15,6 +15,7 @@ export * from "./EmptyState";
|
|
|
15
15
|
export * from "./ErrorBoundary";
|
|
16
16
|
export * from "./Icon";
|
|
17
17
|
export * from "./InputOTP";
|
|
18
|
+
export * from "./Item";
|
|
18
19
|
export * from "./KeyboardAvoidingView";
|
|
19
20
|
export * from "./Label";
|
|
20
21
|
export * from "./MaxWidthContainer";
|
|
@@ -22,11 +23,13 @@ export * from "./Notification";
|
|
|
22
23
|
export * from "./Popover";
|
|
23
24
|
export * from "./Progress";
|
|
24
25
|
export * from "./RadioGroup";
|
|
26
|
+
export * from "./SectionHeader";
|
|
25
27
|
export * from "./SegmentedControl";
|
|
26
28
|
export * from "./Select";
|
|
27
29
|
export * from "./Separator";
|
|
28
30
|
export * from "./Skeleton";
|
|
29
31
|
export * from "./Slider";
|
|
32
|
+
export * from "./StatCard";
|
|
30
33
|
export * from "./StatusBar";
|
|
31
34
|
export * from "./StyledText";
|
|
32
35
|
export * from "./Switch";
|
package/dist/components/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./EmptyState.js";
|
|
|
15
15
|
export * from "./ErrorBoundary.js";
|
|
16
16
|
export * from "./Icon.js";
|
|
17
17
|
export * from "./InputOTP.js";
|
|
18
|
+
export * from "./Item.js";
|
|
18
19
|
export * from "./KeyboardAvoidingView.js";
|
|
19
20
|
export * from "./Label.js";
|
|
20
21
|
export * from "./MaxWidthContainer.js";
|
|
@@ -22,11 +23,13 @@ export * from "./Notification.js";
|
|
|
22
23
|
export * from "./Popover.js";
|
|
23
24
|
export * from "./Progress.js";
|
|
24
25
|
export * from "./RadioGroup.js";
|
|
26
|
+
export * from "./SectionHeader.js";
|
|
25
27
|
export * from "./SegmentedControl.js";
|
|
26
28
|
export * from "./Select.js";
|
|
27
29
|
export * from "./Separator.js";
|
|
28
30
|
export * from "./Skeleton.js";
|
|
29
31
|
export * from "./Slider.js";
|
|
32
|
+
export * from "./StatCard.js";
|
|
30
33
|
export * from "./StatusBar.js";
|
|
31
34
|
export * from "./StyledText.js";
|
|
32
35
|
export * from "./Switch.js";
|
package/dist/constants/fonts.js
CHANGED
|
@@ -11,18 +11,35 @@ const isReactNativeRuntime = Platform.OS !== "web";
|
|
|
11
11
|
const serifFamilies = isWebRuntime
|
|
12
12
|
? { regular: "Georgia, 'Times New Roman', serif", bold: "Georgia, 'Times New Roman', serif" }
|
|
13
13
|
: { regular: "Georgia", bold: "Georgia" };
|
|
14
|
+
// Web ships one Inter CSS family for every weight — weight differentiation
|
|
15
|
+
// comes from a numeric `fontWeight` set alongside this family (see
|
|
16
|
+
// StyledText's getFontFamilyWeight). Native ships four discrete Inter static
|
|
17
|
+
// font files (loaded by useResources via @expo-google-fonts/inter), each a
|
|
18
|
+
// real weight, so the family name alone carries the weight — do NOT also set
|
|
19
|
+
// `fontWeight` on native or it faux-bolds on top of an already-bold file.
|
|
20
|
+
// `light` maps to the regular file on native: a 5th (300) static weight isn't
|
|
21
|
+
// worth shipping for how rarely `light` is used.
|
|
14
22
|
const sansSerifFamilies = isWebRuntime
|
|
15
23
|
? {
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
light: `"Inter", ${WEB_FONT_STACK}`,
|
|
25
|
+
regular: `"Inter", ${WEB_FONT_STACK}`,
|
|
26
|
+
medium: `"Inter", ${WEB_FONT_STACK}`,
|
|
27
|
+
semibold: `"Inter", ${WEB_FONT_STACK}`,
|
|
28
|
+
bold: `"Inter", ${WEB_FONT_STACK}`,
|
|
18
29
|
}
|
|
19
30
|
: isReactNativeRuntime
|
|
20
31
|
? {
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
light: "Inter_400Regular",
|
|
33
|
+
regular: "Inter_400Regular",
|
|
34
|
+
medium: "Inter_500Medium",
|
|
35
|
+
semibold: "Inter_600SemiBold",
|
|
36
|
+
bold: "Inter_700Bold",
|
|
23
37
|
}
|
|
24
38
|
: {
|
|
39
|
+
light: "sans-serif",
|
|
25
40
|
regular: "sans-serif",
|
|
41
|
+
medium: "sans-serif",
|
|
42
|
+
semibold: "sans-serif",
|
|
26
43
|
bold: "sans-serif",
|
|
27
44
|
};
|
|
28
45
|
export const fontFamilies = {
|
|
@@ -39,17 +56,19 @@ export const typography = {
|
|
|
39
56
|
"3xl": { fontSize: 30, lineHeight: 36 },
|
|
40
57
|
"4xl": { fontSize: 36, lineHeight: 40 },
|
|
41
58
|
};
|
|
59
|
+
// Web: single "Inter" family per weight slot, numeric fontWeight picks the
|
|
60
|
+
// right @font-face variant from the multi-weight stylesheet (see useResources).
|
|
42
61
|
const webNavigationFonts = {
|
|
43
62
|
regular: {
|
|
44
63
|
fontFamily: fontFamilies.sansSerif.regular || WEB_FONT_STACK,
|
|
45
64
|
fontWeight: "400",
|
|
46
65
|
},
|
|
47
66
|
medium: {
|
|
48
|
-
fontFamily: fontFamilies.sansSerif.
|
|
67
|
+
fontFamily: fontFamilies.sansSerif.medium || WEB_FONT_STACK,
|
|
49
68
|
fontWeight: "500",
|
|
50
69
|
},
|
|
51
70
|
bold: {
|
|
52
|
-
fontFamily: fontFamilies.sansSerif.
|
|
71
|
+
fontFamily: fontFamilies.sansSerif.semibold || WEB_FONT_STACK,
|
|
53
72
|
fontWeight: "600",
|
|
54
73
|
},
|
|
55
74
|
heavy: {
|
|
@@ -57,22 +76,26 @@ const webNavigationFonts = {
|
|
|
57
76
|
fontWeight: "700",
|
|
58
77
|
},
|
|
59
78
|
};
|
|
79
|
+
// Native: each slot points at its own real Inter static file, so the family
|
|
80
|
+
// name alone carries the weight. No numeric fontWeight here — pairing one
|
|
81
|
+
// with an already-weighted static file triggers faux-bold on top of a real
|
|
82
|
+
// weight file (same rule as StyledText's getFontFamilyWeight).
|
|
60
83
|
const nativeNavigationFonts = {
|
|
61
84
|
regular: {
|
|
62
85
|
fontFamily: fontFamilies.sansSerif.regular || "System",
|
|
63
|
-
fontWeight: "
|
|
86
|
+
fontWeight: "normal",
|
|
64
87
|
},
|
|
65
88
|
medium: {
|
|
66
|
-
fontFamily: fontFamilies.sansSerif.
|
|
67
|
-
fontWeight: "
|
|
89
|
+
fontFamily: fontFamilies.sansSerif.medium || "System",
|
|
90
|
+
fontWeight: "normal",
|
|
68
91
|
},
|
|
69
92
|
bold: {
|
|
70
|
-
fontFamily: fontFamilies.sansSerif.
|
|
71
|
-
fontWeight: "
|
|
93
|
+
fontFamily: fontFamilies.sansSerif.semibold || "System",
|
|
94
|
+
fontWeight: "normal",
|
|
72
95
|
},
|
|
73
96
|
heavy: {
|
|
74
97
|
fontFamily: fontFamilies.sansSerif.bold || "System",
|
|
75
|
-
fontWeight: "
|
|
98
|
+
fontWeight: "normal",
|
|
76
99
|
},
|
|
77
100
|
};
|
|
78
101
|
const defaultNavigationFonts = {
|
package/dist/constants/index.js
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion Duration Tokens
|
|
3
|
+
*
|
|
4
|
+
* Standard durations (ms) for `Animated.timing` transitions. Convention:
|
|
5
|
+
* ease-out on enter (element appearing/growing), ease-in on exit
|
|
6
|
+
* (element disappearing/shrinking). Pick the token closest to the intended
|
|
7
|
+
* feel rather than hand-picking a duration inline.
|
|
8
|
+
*
|
|
9
|
+
* - instant: near-immediate feedback (e.g. checkmark/dot toggles)
|
|
10
|
+
* - fast: small, frequent UI transitions (e.g. switches, accordions)
|
|
11
|
+
* - normal: default entrance/exit transitions (e.g. drawers, dialogs, toasts)
|
|
12
|
+
* - slow: larger or more deliberate transitions
|
|
13
|
+
* - slower: pronounced, attention-getting transitions
|
|
14
|
+
*
|
|
15
|
+
* Deliberate outliers (e.g. skeleton/indeterminate pulse loops) are not
|
|
16
|
+
* expected to use these tokens.
|
|
17
|
+
*/
|
|
18
|
+
export declare const durations: {
|
|
19
|
+
readonly instant: 75;
|
|
20
|
+
readonly fast: 150;
|
|
21
|
+
readonly normal: 200;
|
|
22
|
+
readonly slow: 300;
|
|
23
|
+
readonly slower: 500;
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion Duration Tokens
|
|
3
|
+
*
|
|
4
|
+
* Standard durations (ms) for `Animated.timing` transitions. Convention:
|
|
5
|
+
* ease-out on enter (element appearing/growing), ease-in on exit
|
|
6
|
+
* (element disappearing/shrinking). Pick the token closest to the intended
|
|
7
|
+
* feel rather than hand-picking a duration inline.
|
|
8
|
+
*
|
|
9
|
+
* - instant: near-immediate feedback (e.g. checkmark/dot toggles)
|
|
10
|
+
* - fast: small, frequent UI transitions (e.g. switches, accordions)
|
|
11
|
+
* - normal: default entrance/exit transitions (e.g. drawers, dialogs, toasts)
|
|
12
|
+
* - slow: larger or more deliberate transitions
|
|
13
|
+
* - slower: pronounced, attention-getting transitions
|
|
14
|
+
*
|
|
15
|
+
* Deliberate outliers (e.g. skeleton/indeterminate pulse loops) are not
|
|
16
|
+
* expected to use these tokens.
|
|
17
|
+
*/
|
|
18
|
+
export const durations = {
|
|
19
|
+
instant: 75,
|
|
20
|
+
fast: 150,
|
|
21
|
+
normal: 200,
|
|
22
|
+
slow: 300,
|
|
23
|
+
slower: 500,
|
|
24
|
+
};
|
|
@@ -24,12 +24,12 @@ export declare const spacing: {
|
|
|
24
24
|
readonly listItemSpacing: 8;
|
|
25
25
|
readonly touchTarget: 44;
|
|
26
26
|
readonly radiusNone: 0;
|
|
27
|
-
readonly radiusXs:
|
|
28
|
-
readonly radiusSm:
|
|
29
|
-
readonly radiusMd:
|
|
30
|
-
readonly radiusLg:
|
|
31
|
-
readonly radiusXl:
|
|
32
|
-
readonly radius2xl:
|
|
27
|
+
readonly radiusXs: 4;
|
|
28
|
+
readonly radiusSm: 8;
|
|
29
|
+
readonly radiusMd: 12;
|
|
30
|
+
readonly radiusLg: 14;
|
|
31
|
+
readonly radiusXl: 18;
|
|
32
|
+
readonly radius2xl: 24;
|
|
33
33
|
readonly radiusFull: 9999;
|
|
34
34
|
readonly iconXs: 12;
|
|
35
35
|
readonly iconSm: 16;
|
|
@@ -37,5 +37,5 @@ export declare const spacing: {
|
|
|
37
37
|
readonly iconLg: 32;
|
|
38
38
|
readonly iconXl: 48;
|
|
39
39
|
};
|
|
40
|
-
export declare const base: 8, xxs: 2, xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48, xxxl: 64, gutter: 16, gutterVertical: 24, screenPadding: 16, buttonPadding: 10, inputPadding: 10, cardPadding: 16, sectionSpacing: 32, listItemSpacing: 8, touchTarget: 44, radiusNone: 0, radiusXs:
|
|
40
|
+
export declare const base: 8, xxs: 2, xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48, xxxl: 64, gutter: 16, gutterVertical: 24, screenPadding: 16, buttonPadding: 10, inputPadding: 10, cardPadding: 16, sectionSpacing: 32, listItemSpacing: 8, touchTarget: 44, radiusNone: 0, radiusXs: 4, radiusSm: 8, radiusMd: 12, radiusLg: 14, radiusXl: 18, radius2xl: 24, radiusFull: 9999, iconXs: 12, iconSm: 16, iconMd: 24, iconLg: 32, iconXl: 48;
|
|
41
41
|
export declare const space: (multiplier: number) => number;
|
|
@@ -27,14 +27,14 @@ export const spacing = {
|
|
|
27
27
|
sectionSpacing: 32, // Space between major sections
|
|
28
28
|
listItemSpacing: 8, // Space between list items
|
|
29
29
|
touchTarget: 44, // Minimum comfortable native touch target
|
|
30
|
-
// Border radius — shadcn-inspired scale (radiusMd =
|
|
30
|
+
// Border radius — shadcn-inspired scale (radiusMd = 12px default)
|
|
31
31
|
radiusNone: 0,
|
|
32
|
-
radiusXs:
|
|
33
|
-
radiusSm:
|
|
34
|
-
radiusMd:
|
|
35
|
-
radiusLg:
|
|
36
|
-
radiusXl:
|
|
37
|
-
radius2xl:
|
|
32
|
+
radiusXs: 4,
|
|
33
|
+
radiusSm: 8,
|
|
34
|
+
radiusMd: 12,
|
|
35
|
+
radiusLg: 14,
|
|
36
|
+
radiusXl: 18,
|
|
37
|
+
radius2xl: 24,
|
|
38
38
|
radiusFull: 9999, // For circular elements
|
|
39
39
|
// Icon sizes
|
|
40
40
|
iconXs: 12,
|
|
@@ -5,8 +5,10 @@ interface LoadResourcesResult {
|
|
|
5
5
|
/**
|
|
6
6
|
* Loads essential app resources on startup.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Native platforms load four static Inter weights (via
|
|
9
|
+
* @expo-google-fonts/inter) so StyledText's weight range resolves to real
|
|
10
|
+
* font files. Web loads Inter from Google Fonts as a single CSS family;
|
|
11
|
+
* weight differentiation there comes from a numeric fontWeight instead.
|
|
10
12
|
*/
|
|
11
13
|
export declare const useResources: () => LoadResourcesResult;
|
|
12
14
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import * as Font from "expo-font";
|
|
3
3
|
import Feather from "@expo/vector-icons/Feather";
|
|
4
|
+
import { Inter_400Regular, Inter_500Medium, Inter_600SemiBold, Inter_700Bold, } from "@expo-google-fonts/inter";
|
|
4
5
|
import { Platform } from "react-native";
|
|
5
6
|
// Eager, module-scope load so expo-font registers Feather in its SSR
|
|
6
7
|
// serverContext. When the server renders the HTML document, expo-font emits
|
|
@@ -11,30 +12,50 @@ import { Platform } from "react-native";
|
|
|
11
12
|
// the effect; the effect's loadAsync becomes a no-op for the already-loaded
|
|
12
13
|
// font.
|
|
13
14
|
void Font.loadAsync(Feather.font);
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
// The four static Inter weights StyledText's native family keys point at
|
|
16
|
+
// (see constants/fonts.ts). Native-only: web never renders these family names
|
|
17
|
+
// ("Inter_400Regular" etc.) — fontFamilies.sansSerif resolves every weight to
|
|
18
|
+
// the single "Inter" CSS family on web (loaded via ensureWebFontStylesheet
|
|
19
|
+
// below), so fetching these .ttf assets there would just be ~1.3MB of dead
|
|
20
|
+
// weight with nothing pointing at them.
|
|
21
|
+
const interFontMap = {
|
|
22
|
+
Inter_400Regular,
|
|
23
|
+
Inter_500Medium,
|
|
24
|
+
Inter_600SemiBold,
|
|
25
|
+
Inter_700Bold,
|
|
26
|
+
};
|
|
27
|
+
function loadNativeInterFonts() {
|
|
28
|
+
if (Platform.OS === "web") {
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
}
|
|
31
|
+
return Font.loadAsync(interFontMap);
|
|
32
|
+
}
|
|
33
|
+
const INTER_STYLESHEET_ID = "mrmeg-expo-ui-inter";
|
|
34
|
+
const INTER_STYLESHEET_URL = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap";
|
|
16
35
|
function ensureWebFontStylesheet() {
|
|
17
36
|
if (Platform.OS !== "web" || typeof document === "undefined") {
|
|
18
37
|
return Promise.resolve();
|
|
19
38
|
}
|
|
20
|
-
if (document.getElementById(
|
|
39
|
+
if (document.getElementById(INTER_STYLESHEET_ID)) {
|
|
21
40
|
return Promise.resolve();
|
|
22
41
|
}
|
|
23
42
|
return new Promise((resolve, reject) => {
|
|
24
43
|
const link = document.createElement("link");
|
|
25
|
-
link.id =
|
|
44
|
+
link.id = INTER_STYLESHEET_ID;
|
|
26
45
|
link.rel = "stylesheet";
|
|
27
|
-
link.href =
|
|
46
|
+
link.href = INTER_STYLESHEET_URL;
|
|
28
47
|
link.onload = () => resolve();
|
|
29
|
-
link.onerror = () => reject(new Error("
|
|
48
|
+
link.onerror = () => reject(new Error("Inter stylesheet failed to load"));
|
|
30
49
|
document.head.appendChild(link);
|
|
31
50
|
});
|
|
32
51
|
}
|
|
33
52
|
/**
|
|
34
53
|
* Loads essential app resources on startup.
|
|
35
54
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
55
|
+
* Native platforms load four static Inter weights (via
|
|
56
|
+
* @expo-google-fonts/inter) so StyledText's weight range resolves to real
|
|
57
|
+
* font files. Web loads Inter from Google Fonts as a single CSS family;
|
|
58
|
+
* weight differentiation there comes from a numeric fontWeight instead.
|
|
38
59
|
*/
|
|
39
60
|
export const useResources = () => {
|
|
40
61
|
const [loaded, setLoaded] = useState(false);
|
|
@@ -45,6 +66,7 @@ export const useResources = () => {
|
|
|
45
66
|
try {
|
|
46
67
|
const fontPromise = Promise.all([
|
|
47
68
|
Font.loadAsync(Feather.font),
|
|
69
|
+
loadNativeInterFonts(),
|
|
48
70
|
ensureWebFontStylesheet(),
|
|
49
71
|
]);
|
|
50
72
|
// Timeout after 5 seconds — proceed with system fallback fonts
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import { Animated, Easing } from "react-native";
|
|
3
3
|
import { useReducedMotion } from "./useReduceMotion.js";
|
|
4
|
+
import { durations } from "../constants/motion.js";
|
|
4
5
|
/**
|
|
5
6
|
* Hook for entrance animations with stagger support using React Native Animated.
|
|
6
7
|
*
|
|
@@ -21,7 +22,7 @@ import { useReducedMotion } from "./useReduceMotion.js";
|
|
|
21
22
|
* ```
|
|
22
23
|
*/
|
|
23
24
|
export function useStaggeredEntrance(options = {}) {
|
|
24
|
-
const { type = "fadeSlideUp", delay = 0, duration =
|
|
25
|
+
const { type = "fadeSlideUp", delay = 0, duration = durations.normal, slideDistance = 8, initialScale = 0.95, } = options;
|
|
25
26
|
const reduceMotion = useReducedMotion();
|
|
26
27
|
const opacity = useRef(new Animated.Value(reduceMotion ? 1 : 0)).current;
|
|
27
28
|
const translateY = useRef(new Animated.Value(reduceMotion
|
package/dist/hooks/useTheme.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ interface ExtendedColorScheme {
|
|
|
32
32
|
* - getTextColorForBackground("#000") → "light"
|
|
33
33
|
* - getContrastingColor("#f4f4f4", "#222", "#fff") → "#222"
|
|
34
34
|
* - withAlpha("#336699", 0.6) → "rgba(51,102,153,0.6)"
|
|
35
|
-
* - getShadowStyle('base') → { boxShadow: "0px
|
|
35
|
+
* - getShadowStyle('base') → { boxShadow: "0px 2px 6px rgba(0, 0, 0, 0.05), 0px 4px 12px rgba(0, 0, 0, 0.03)" }
|
|
36
36
|
*/
|
|
37
37
|
export declare function useTheme(): ExtendedColorScheme & {
|
|
38
38
|
toggleTheme: () => void;
|