@mrmeg/expo-ui 0.14.0 → 0.16.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 +81 -0
- package/README.md +50 -6
- package/dist/components/Accordion.d.ts +1 -1
- package/dist/components/Accordion.js +25 -19
- package/dist/components/Badge.js +10 -7
- package/dist/components/BottomSheet.js +21 -19
- package/dist/components/Button.d.ts +2 -1
- package/dist/components/Button.js +22 -6
- package/dist/components/Card.js +5 -3
- 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 +26 -9
- package/dist/components/InputOTP.js +10 -4
- package/dist/components/Item.d.ts +93 -0
- package/dist/components/Item.js +125 -0
- package/dist/components/Label.js +10 -4
- package/dist/components/Notification.js +14 -24
- 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 +5 -3
- 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 +85 -29
- package/dist/components/Switch.js +84 -57
- package/dist/components/Tabs.js +43 -12
- package/dist/components/TextInput.js +17 -11
- 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 +44 -5
- package/dist/constants/fonts.js +42 -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/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/themedStyles.d.ts +21 -0
- package/dist/lib/themedStyles.js +33 -0
- package/dist/state/themeStore.d.ts +17 -0
- package/dist/state/themeStore.js +6 -0
- package/package.json +20 -19
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
3
|
import { Icon } from "./Icon.js";
|
|
4
4
|
import { TextClassContext, TextColorContext, TextSelectabilityContext } from "./StyledText.context";
|
|
5
5
|
import { useTheme } from "../hooks/useTheme.js";
|
|
6
6
|
import { spacing } from "../constants/spacing.js";
|
|
7
|
+
import { useScalePress } from "../hooks/useScalePress.js";
|
|
7
8
|
import * as TogglePrimitive from "@rn-primitives/toggle";
|
|
8
|
-
import { Platform, StyleSheet, ActivityIndicator } from "react-native";
|
|
9
|
+
import { Platform, StyleSheet, ActivityIndicator, Animated } from "react-native";
|
|
9
10
|
import { palette } from "../constants/colors.js";
|
|
10
11
|
const DEFAULT_HIT_SLOP = 8;
|
|
11
12
|
// Size configurations
|
|
@@ -73,8 +74,10 @@ const TOGGLE_SIZES = {
|
|
|
73
74
|
* ```
|
|
74
75
|
*/
|
|
75
76
|
function Toggle({ variant = "default", size = "default", shape = "default", loading = false, iconOnly = false, style: styleOverride, ...props }) {
|
|
76
|
-
const { theme, getContrastingColor, withAlpha } = useTheme();
|
|
77
|
+
const { theme, getContrastingColor, getFocusRingStyle, withAlpha } = useTheme();
|
|
77
78
|
const sizeConfig = TOGGLE_SIZES[size];
|
|
79
|
+
const focusRingStyle = getFocusRingStyle();
|
|
80
|
+
const [focused, setFocused] = useState(false);
|
|
78
81
|
// Calculate text color based on state and variant
|
|
79
82
|
const getTextColor = () => {
|
|
80
83
|
if (props.pressed) {
|
|
@@ -101,45 +104,69 @@ function Toggle({ variant = "default", size = "default", shape = "default", load
|
|
|
101
104
|
const flattenedStyle = styleOverride ? StyleSheet.flatten(styleOverride) : undefined;
|
|
102
105
|
const isDisabled = props.disabled || loading;
|
|
103
106
|
const children = props.children;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
107
|
+
const { animatedStyle: scaleStyle, pressHandlers } = useScalePress({
|
|
108
|
+
disabled: !!isDisabled,
|
|
109
|
+
scaleTo: 0.92,
|
|
110
|
+
haptic: false,
|
|
111
|
+
});
|
|
112
|
+
const showFocusRing = (event) => {
|
|
113
|
+
let ringVisible = true;
|
|
114
|
+
if (Platform.OS === "web") {
|
|
115
|
+
const target = event?.nativeEvent?.target;
|
|
116
|
+
if (target && typeof target.matches === "function") {
|
|
117
|
+
try {
|
|
118
|
+
ringVisible = target.matches(":focus-visible");
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
ringVisible = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
setFocused(ringVisible);
|
|
126
|
+
};
|
|
127
|
+
const hideFocusRing = () => {
|
|
128
|
+
setFocused(false);
|
|
129
|
+
};
|
|
130
|
+
return (_jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(Animated.View, { style: scaleStyle, children: _jsx(TogglePrimitive.Root, { ...props, disabled: isDisabled, onPressIn: pressHandlers.onPressIn, onPressOut: pressHandlers.onPressOut, onFocus: showFocusRing, onBlur: hideFocusRing, style: {
|
|
131
|
+
...styles.root,
|
|
132
|
+
height: sizeConfig.height,
|
|
133
|
+
minWidth: sizeConfig.minWidth,
|
|
134
|
+
paddingHorizontal: iconOnly ? sizeConfig.height / 2 - sizeConfig.iconSize / 2 : sizeConfig.paddingHorizontal,
|
|
135
|
+
borderRadius: getBorderRadius(),
|
|
136
|
+
// Base variant styles
|
|
137
|
+
...(variant === "default" && !props.pressed && {
|
|
138
|
+
backgroundColor: "transparent",
|
|
139
|
+
borderColor: theme.colors.border,
|
|
140
|
+
}),
|
|
141
|
+
...(variant === "default" && props.pressed && {
|
|
142
|
+
backgroundColor: withAlpha(theme.colors.primary, 0.1),
|
|
143
|
+
borderColor: theme.colors.primary,
|
|
144
|
+
}),
|
|
145
|
+
// Outline variant styles
|
|
146
|
+
...(variant === "outline" && !props.pressed && {
|
|
147
|
+
backgroundColor: "transparent",
|
|
148
|
+
borderColor: theme.colors.primary,
|
|
149
|
+
}),
|
|
150
|
+
...(variant === "outline" && props.pressed && {
|
|
151
|
+
backgroundColor: theme.colors.primary,
|
|
152
|
+
borderColor: theme.colors.primary,
|
|
153
|
+
}),
|
|
154
|
+
// Disabled state
|
|
155
|
+
opacity: isDisabled ? 0.5 : 1,
|
|
156
|
+
// Web-specific styles
|
|
157
|
+
...(Platform.OS === "web" && {
|
|
158
|
+
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
159
|
+
transition: "all 150ms",
|
|
160
|
+
userSelect: "none",
|
|
161
|
+
}),
|
|
162
|
+
...(focused && !isDisabled ? focusRingStyle : null),
|
|
163
|
+
// Apply custom style override
|
|
164
|
+
...(flattenedStyle || {}),
|
|
165
|
+
}, hitSlop: DEFAULT_HIT_SLOP, accessibilityRole: "button", accessibilityState: {
|
|
166
|
+
selected: props.pressed,
|
|
167
|
+
disabled: !!isDisabled,
|
|
168
|
+
busy: loading,
|
|
169
|
+
}, children: loading ? (_jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(ActivityIndicator, { size: "small", color: textColor }) })) : typeof children === "function" ? ((state) => (_jsx(TextSelectabilityContext.Provider, { value: false, children: children(state) }))) : (_jsx(TextSelectabilityContext.Provider, { value: false, children: children })) }) }) }) }));
|
|
143
170
|
}
|
|
144
171
|
function ToggleIcon({ name, size, color }) {
|
|
145
172
|
const contextColor = React.use(TextColorContext);
|
|
@@ -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";
|
|
@@ -9,16 +9,55 @@ interface NavigationFonts {
|
|
|
9
9
|
bold: FontStyle;
|
|
10
10
|
heavy: FontStyle;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
/** Weight slots every sans-serif family map must provide. */
|
|
13
|
+
export type FontFamilyWeight = "light" | "regular" | "medium" | "semibold" | "bold";
|
|
14
|
+
/**
|
|
15
|
+
* How a platform expresses weight for a given family map.
|
|
16
|
+
*
|
|
17
|
+
* - `"numeric"` — one multi-weight CSS family (the package's own web default:
|
|
18
|
+
* a single `"Inter"` family where a numeric `fontWeight` selects the
|
|
19
|
+
* `@font-face` variant).
|
|
20
|
+
* - `"family"` — each weight is its own separately-registered single-face
|
|
21
|
+
* family, so the family name alone carries the weight. Emitting a numeric
|
|
22
|
+
* `fontWeight` on top of one makes the renderer synthesise a second layer of
|
|
23
|
+
* bold, so `StyledText` suppresses it.
|
|
24
|
+
*
|
|
25
|
+
* Native is always `"family"` (discrete static font files). Web is `"numeric"`
|
|
26
|
+
* by default, but apps that load per-weight faces through `expo-font` — which
|
|
27
|
+
* registers each file as its own family on web too — must declare `"family"`.
|
|
28
|
+
*/
|
|
29
|
+
export type FontWeightStrategy = "numeric" | "family";
|
|
30
|
+
export type FontFamilyMap = {
|
|
13
31
|
serif: {
|
|
14
32
|
regular: string;
|
|
15
33
|
bold: string;
|
|
16
34
|
};
|
|
17
|
-
sansSerif:
|
|
18
|
-
regular: string;
|
|
19
|
-
bold: string;
|
|
20
|
-
};
|
|
35
|
+
sansSerif: Record<FontFamilyWeight, string>;
|
|
21
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Font overrides a host app can inject to brand the package.
|
|
39
|
+
*
|
|
40
|
+
* Mirrors `setColors`: the package ships neutral defaults, but a consuming app
|
|
41
|
+
* almost always bundles its own faces. Without an injection point, package
|
|
42
|
+
* components render the package's fonts while app-authored siblings render the
|
|
43
|
+
* app's — and the only remaining lever is patching `node_modules`, which is
|
|
44
|
+
* keyed to an exact version and silently stops applying on the next bump.
|
|
45
|
+
*
|
|
46
|
+
* Partial by design: override `sansSerif` alone and `serif` keeps its default.
|
|
47
|
+
*/
|
|
48
|
+
export type FontOverrides = {
|
|
49
|
+
families?: Partial<FontFamilyMap>;
|
|
50
|
+
/** Defaults to the package's own per-platform behaviour when omitted. */
|
|
51
|
+
webWeightStrategy?: FontWeightStrategy;
|
|
52
|
+
};
|
|
53
|
+
/** The package's built-in families. Override per-app via `setFonts`. */
|
|
54
|
+
export declare const fontFamilies: FontFamilyMap;
|
|
55
|
+
/**
|
|
56
|
+
* The package's own web weight strategy: one multi-weight "Inter" family, so
|
|
57
|
+
* weight comes from a numeric `fontWeight`. Native always behaves as
|
|
58
|
+
* `"family"` regardless of this value.
|
|
59
|
+
*/
|
|
60
|
+
export declare const defaultWebWeightStrategy: FontWeightStrategy;
|
|
22
61
|
/**
|
|
23
62
|
* Typography scale — Tailwind-matching sizes for consistent text hierarchy.
|
|
24
63
|
* Components can progressively adopt these tokens.
|
package/dist/constants/fonts.js
CHANGED
|
@@ -11,24 +11,48 @@ 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
|
};
|
|
45
|
+
/** The package's built-in families. Override per-app via `setFonts`. */
|
|
28
46
|
export const fontFamilies = {
|
|
29
47
|
serif: serifFamilies,
|
|
30
48
|
sansSerif: sansSerifFamilies,
|
|
31
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The package's own web weight strategy: one multi-weight "Inter" family, so
|
|
52
|
+
* weight comes from a numeric `fontWeight`. Native always behaves as
|
|
53
|
+
* `"family"` regardless of this value.
|
|
54
|
+
*/
|
|
55
|
+
export const defaultWebWeightStrategy = "numeric";
|
|
32
56
|
export const typography = {
|
|
33
57
|
xs: { fontSize: 12, lineHeight: 16 },
|
|
34
58
|
sm: { fontSize: 14, lineHeight: 20 },
|
|
@@ -39,17 +63,19 @@ export const typography = {
|
|
|
39
63
|
"3xl": { fontSize: 30, lineHeight: 36 },
|
|
40
64
|
"4xl": { fontSize: 36, lineHeight: 40 },
|
|
41
65
|
};
|
|
66
|
+
// Web: single "Inter" family per weight slot, numeric fontWeight picks the
|
|
67
|
+
// right @font-face variant from the multi-weight stylesheet (see useResources).
|
|
42
68
|
const webNavigationFonts = {
|
|
43
69
|
regular: {
|
|
44
70
|
fontFamily: fontFamilies.sansSerif.regular || WEB_FONT_STACK,
|
|
45
71
|
fontWeight: "400",
|
|
46
72
|
},
|
|
47
73
|
medium: {
|
|
48
|
-
fontFamily: fontFamilies.sansSerif.
|
|
74
|
+
fontFamily: fontFamilies.sansSerif.medium || WEB_FONT_STACK,
|
|
49
75
|
fontWeight: "500",
|
|
50
76
|
},
|
|
51
77
|
bold: {
|
|
52
|
-
fontFamily: fontFamilies.sansSerif.
|
|
78
|
+
fontFamily: fontFamilies.sansSerif.semibold || WEB_FONT_STACK,
|
|
53
79
|
fontWeight: "600",
|
|
54
80
|
},
|
|
55
81
|
heavy: {
|
|
@@ -57,22 +83,26 @@ const webNavigationFonts = {
|
|
|
57
83
|
fontWeight: "700",
|
|
58
84
|
},
|
|
59
85
|
};
|
|
86
|
+
// Native: each slot points at its own real Inter static file, so the family
|
|
87
|
+
// name alone carries the weight. No numeric fontWeight here — pairing one
|
|
88
|
+
// with an already-weighted static file triggers faux-bold on top of a real
|
|
89
|
+
// weight file (same rule as StyledText's getFontFamilyWeight).
|
|
60
90
|
const nativeNavigationFonts = {
|
|
61
91
|
regular: {
|
|
62
92
|
fontFamily: fontFamilies.sansSerif.regular || "System",
|
|
63
|
-
fontWeight: "
|
|
93
|
+
fontWeight: "normal",
|
|
64
94
|
},
|
|
65
95
|
medium: {
|
|
66
|
-
fontFamily: fontFamilies.sansSerif.
|
|
67
|
-
fontWeight: "
|
|
96
|
+
fontFamily: fontFamilies.sansSerif.medium || "System",
|
|
97
|
+
fontWeight: "normal",
|
|
68
98
|
},
|
|
69
99
|
bold: {
|
|
70
|
-
fontFamily: fontFamilies.sansSerif.
|
|
71
|
-
fontWeight: "
|
|
100
|
+
fontFamily: fontFamilies.sansSerif.semibold || "System",
|
|
101
|
+
fontWeight: "normal",
|
|
72
102
|
},
|
|
73
103
|
heavy: {
|
|
74
104
|
fontFamily: fontFamilies.sansSerif.bold || "System",
|
|
75
|
-
fontWeight: "
|
|
105
|
+
fontWeight: "normal",
|
|
76
106
|
},
|
|
77
107
|
};
|
|
78
108
|
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 {};
|