rnnovaui 0.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 ADDED
@@ -0,0 +1,24 @@
1
+ # rnnovaui
2
+
3
+ High-performance, reusable UI component library for React Native and Expo.
4
+
5
+ Built with plain JavaScript and designed for:
6
+
7
+ - React Native
8
+ - Expo
9
+ - Expo Router
10
+ - Light / Dark / System themes
11
+ - Reanimated-powered components
12
+ - Responsive layouts
13
+ - Performance-focused rendering
14
+ - Android and iOS
15
+ - JavaScript projects
16
+ - TypeScript consumer projects
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install rnnovaui
24
+ ```
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "rnnovaui",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "main": "src/index.js",
6
+ "react-native": "src/index.js",
7
+ "module": "src/index.js",
8
+ "files": [
9
+ "src",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "scripts": {
14
+ "test": "echo \"Error: no test specified\" && exit 1",
15
+ "prepare": "echo \"Package ready\""
16
+ },
17
+ "keywords": [
18
+ "react-native",
19
+ "expo",
20
+ "react-native-ui",
21
+ "ui-components",
22
+ "component-library",
23
+ "mobile-ui",
24
+ "dark-mode",
25
+ "light-mode"
26
+ ],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "type": "commonjs",
30
+ "devDependencies": {
31
+ "react": "19.2.3",
32
+ "react-native": "0.86.2",
33
+ "react-native-reanimated": "^4.5.3"
34
+ },
35
+ "peerDependencies": {
36
+ "react": "^19.2.3",
37
+ "react-native": "^0.86.2"
38
+ }
39
+ }
@@ -0,0 +1,418 @@
1
+ import React, { memo, useCallback } from "react";
2
+
3
+ import { ActivityIndicator, Pressable, StyleSheet, View } from "react-native";
4
+
5
+ import { useUITheme } from "../../theme";
6
+
7
+ import { UIText } from "../Text";
8
+
9
+ /**
10
+ * Button variants
11
+ */
12
+ const VARIANTS = {
13
+ solid: "solid",
14
+ outline: "outline",
15
+ ghost: "ghost",
16
+ soft: "soft",
17
+ danger: "danger",
18
+ success: "success",
19
+ };
20
+
21
+ /**
22
+ * Button sizes
23
+ */
24
+ const SIZES = {
25
+ sm: "sm",
26
+ md: "md",
27
+ lg: "lg",
28
+ xl: "xl",
29
+ };
30
+
31
+ const UIButtonComponent = ({
32
+ title,
33
+
34
+ children,
35
+
36
+ variant = VARIANTS.solid,
37
+
38
+ size = SIZES.md,
39
+
40
+ disabled = false,
41
+
42
+ loading = false,
43
+
44
+ fullWidth = false,
45
+
46
+ leftIcon = null,
47
+
48
+ rightIcon = null,
49
+
50
+ onPress,
51
+
52
+ onLongPress,
53
+
54
+ onPressIn,
55
+
56
+ onPressOut,
57
+
58
+ style,
59
+
60
+ textStyle,
61
+
62
+ contentStyle,
63
+
64
+ testID,
65
+
66
+ accessibilityLabel,
67
+
68
+ accessibilityHint,
69
+
70
+ accessibilityRole = "button",
71
+
72
+ hitSlop,
73
+
74
+ pressRetentionOffset,
75
+
76
+ android_ripple = true,
77
+
78
+ activeOpacity = 0.9,
79
+
80
+ ...props
81
+ }) => {
82
+ const { theme } = useUITheme();
83
+
84
+ /**
85
+ * Prevent callbacks when button
86
+ * cannot currently be interacted with.
87
+ */
88
+ const isDisabled = disabled || loading;
89
+
90
+ /**
91
+ * Resolve invalid variant safely.
92
+ */
93
+ const safeVariant = VARIANTS[variant] ? variant : VARIANTS.solid;
94
+
95
+ /**
96
+ * Resolve invalid size safely.
97
+ */
98
+ const safeSize = SIZES[size] ? size : SIZES.md;
99
+
100
+ /**
101
+ * Theme-dependent colors.
102
+ */
103
+ const colors = getVariantColors(safeVariant, theme.colors);
104
+
105
+ /**
106
+ * Theme-dependent dimensions.
107
+ */
108
+ const dimensions = getSizeDimensions(safeSize, theme);
109
+
110
+ /**
111
+ * Stable press callback.
112
+ */
113
+ const handlePress = useCallback(
114
+ (event) => {
115
+ if (isDisabled) {
116
+ return;
117
+ }
118
+
119
+ if (onPress) {
120
+ onPress(event);
121
+ }
122
+ },
123
+ [isDisabled, onPress],
124
+ );
125
+
126
+ const handleLongPress = useCallback(
127
+ (event) => {
128
+ if (isDisabled) {
129
+ return;
130
+ }
131
+
132
+ if (onLongPress) {
133
+ onLongPress(event);
134
+ }
135
+ },
136
+ [isDisabled, onLongPress],
137
+ );
138
+
139
+ const handlePressIn = useCallback(
140
+ (event) => {
141
+ if (isDisabled) {
142
+ return;
143
+ }
144
+
145
+ if (onPressIn) {
146
+ onPressIn(event);
147
+ }
148
+ },
149
+ [isDisabled, onPressIn],
150
+ );
151
+
152
+ const handlePressOut = useCallback(
153
+ (event) => {
154
+ if (isDisabled) {
155
+ return;
156
+ }
157
+
158
+ if (onPressOut) {
159
+ onPressOut(event);
160
+ }
161
+ },
162
+ [isDisabled, onPressOut],
163
+ );
164
+
165
+ /**
166
+ * Loading indicator color.
167
+ */
168
+ const indicatorColor = colors.text;
169
+
170
+ /**
171
+ * Android ripple.
172
+ */
173
+ const ripple = android_ripple
174
+ ? {
175
+ color: colors.ripple,
176
+
177
+ borderless: false,
178
+ }
179
+ : undefined;
180
+
181
+ const label = title ?? children;
182
+
183
+ return (
184
+ <Pressable
185
+ {...props}
186
+ testID={testID}
187
+ disabled={isDisabled}
188
+ onPress={handlePress}
189
+ onLongPress={handleLongPress}
190
+ onPressIn={handlePressIn}
191
+ onPressOut={handlePressOut}
192
+ hitSlop={hitSlop}
193
+ pressRetentionOffset={pressRetentionOffset}
194
+ android_ripple={ripple}
195
+ accessibilityRole={accessibilityRole}
196
+ accessibilityLabel={
197
+ accessibilityLabel ?? (typeof label === "string" ? label : undefined)
198
+ }
199
+ accessibilityHint={accessibilityHint}
200
+ accessibilityState={{
201
+ disabled: isDisabled,
202
+ busy: loading,
203
+ }}
204
+ style={({ pressed }) => [
205
+ styles.base,
206
+
207
+ {
208
+ minHeight: dimensions.height,
209
+
210
+ paddingHorizontal: dimensions.paddingHorizontal,
211
+
212
+ borderRadius: theme.radius.button,
213
+
214
+ backgroundColor: colors.background,
215
+
216
+ borderColor: colors.border,
217
+
218
+ borderWidth: colors.border ? 1 : 0,
219
+
220
+ opacity: isDisabled ? 0.55 : pressed ? activeOpacity : 1,
221
+
222
+ width: fullWidth ? "100%" : undefined,
223
+ },
224
+
225
+ style,
226
+ ]}
227
+ >
228
+ <View
229
+ style={[
230
+ styles.content,
231
+
232
+ {
233
+ minHeight: dimensions.height - 2,
234
+
235
+ gap: dimensions.gap,
236
+ },
237
+
238
+ contentStyle,
239
+ ]}
240
+ >
241
+ {loading ? (
242
+ <ActivityIndicator size="small" color={indicatorColor} />
243
+ ) : (
244
+ leftIcon
245
+ )}
246
+
247
+ {!loading && label != null && (
248
+ <UIText
249
+ variant={dimensions.textVariant}
250
+ color={colors.text}
251
+ style={[styles.label, textStyle]}
252
+ numberOfLines={1}
253
+ >
254
+ {label}
255
+ </UIText>
256
+ )}
257
+
258
+ {!loading && rightIcon && <View>{rightIcon}</View>}
259
+ </View>
260
+ </Pressable>
261
+ );
262
+ };
263
+
264
+ /**
265
+ * Variant colors.
266
+ */
267
+ function getVariantColors(variant, colors) {
268
+ switch (variant) {
269
+ case VARIANTS.outline:
270
+ return {
271
+ background: colors.transparent,
272
+
273
+ border: colors.primary,
274
+
275
+ text: colors.primary,
276
+
277
+ ripple: colors.primarySoft,
278
+ };
279
+
280
+ case VARIANTS.ghost:
281
+ return {
282
+ background: colors.transparent,
283
+
284
+ border: null,
285
+
286
+ text: colors.primary,
287
+
288
+ ripple: colors.primarySoft,
289
+ };
290
+
291
+ case VARIANTS.soft:
292
+ return {
293
+ background: colors.primarySoft,
294
+
295
+ border: null,
296
+
297
+ text: colors.primary,
298
+
299
+ ripple: colors.primary,
300
+ };
301
+
302
+ case VARIANTS.danger:
303
+ return {
304
+ background: colors.danger,
305
+
306
+ border: colors.danger,
307
+
308
+ text: colors.onPrimary,
309
+
310
+ ripple: colors.dangerSoft,
311
+ };
312
+
313
+ case VARIANTS.success:
314
+ return {
315
+ background: colors.success,
316
+
317
+ border: colors.success,
318
+
319
+ text: colors.onPrimary,
320
+
321
+ ripple: colors.successSoft,
322
+ };
323
+
324
+ case VARIANTS.solid:
325
+ default:
326
+ return {
327
+ background: colors.primary,
328
+
329
+ border: colors.primary,
330
+
331
+ text: colors.onPrimary,
332
+
333
+ ripple: colors.primaryPressed,
334
+ };
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Size dimensions.
340
+ */
341
+ function getSizeDimensions(size, theme) {
342
+ switch (size) {
343
+ case SIZES.sm:
344
+ return {
345
+ height: theme.sizes.button.sm,
346
+
347
+ paddingHorizontal: theme.spacing.md,
348
+
349
+ gap: theme.spacing.xs,
350
+
351
+ textVariant: "label",
352
+ };
353
+
354
+ case SIZES.lg:
355
+ return {
356
+ height: theme.sizes.button.lg,
357
+
358
+ paddingHorizontal: theme.spacing.xl,
359
+
360
+ gap: theme.spacing.sm,
361
+
362
+ textVariant: "bodyMedium",
363
+ };
364
+
365
+ case SIZES.xl:
366
+ return {
367
+ height: theme.sizes.button.xl,
368
+
369
+ paddingHorizontal: theme.spacing.xl2,
370
+
371
+ gap: theme.spacing.sm,
372
+
373
+ textVariant: "bodyMedium",
374
+ };
375
+
376
+ case SIZES.md:
377
+ default:
378
+ return {
379
+ height: theme.sizes.button.md,
380
+
381
+ paddingHorizontal: theme.spacing.lg,
382
+
383
+ gap: theme.spacing.sm,
384
+
385
+ textVariant: "label",
386
+ };
387
+ }
388
+ }
389
+
390
+ const styles = StyleSheet.create({
391
+ base: {
392
+ alignItems: "center",
393
+ justifyContent: "center",
394
+ overflow: "hidden",
395
+ },
396
+
397
+ content: {
398
+ width: "100%",
399
+
400
+ flexDirection: "row",
401
+
402
+ alignItems: "center",
403
+
404
+ justifyContent: "center",
405
+ },
406
+
407
+ label: {
408
+ flexShrink: 1,
409
+
410
+ textAlign: "center",
411
+ },
412
+ });
413
+
414
+ export const UIButton = memo(UIButtonComponent);
415
+
416
+ UIButton.displayName = "UIButton";
417
+
418
+ export { VARIANTS as UIButtonVariants, SIZES as UIButtonSizes };
@@ -0,0 +1 @@
1
+ export { UIButton, UIButtonVariants, UIButtonSizes } from "./UIButton";
@@ -0,0 +1,324 @@
1
+ import React, { memo, useCallback } from "react";
2
+
3
+ import { ActivityIndicator, Pressable, StyleSheet, View } from "react-native";
4
+
5
+ import { useUITheme } from "../../theme";
6
+
7
+ const SIZES = {
8
+ xs: "xs",
9
+ sm: "sm",
10
+ md: "md",
11
+ lg: "lg",
12
+ xl: "xl",
13
+ };
14
+
15
+ const VARIANTS = {
16
+ solid: "solid",
17
+ outline: "outline",
18
+ ghost: "ghost",
19
+ soft: "soft",
20
+ danger: "danger",
21
+ success: "success",
22
+ };
23
+
24
+ const SHAPES = {
25
+ circle: "circle",
26
+ rounded: "rounded",
27
+ square: "square",
28
+ };
29
+
30
+ const UIIconButtonComponent = ({
31
+ icon,
32
+
33
+ size = "md",
34
+
35
+ variant = "ghost",
36
+
37
+ shape = "circle",
38
+
39
+ disabled = false,
40
+
41
+ loading = false,
42
+
43
+ onPress,
44
+
45
+ onLongPress,
46
+
47
+ onPressIn,
48
+
49
+ onPressOut,
50
+
51
+ style,
52
+
53
+ iconContainerStyle,
54
+
55
+ activeOpacity = 0.82,
56
+
57
+ android_ripple = true,
58
+
59
+ hitSlop = 8,
60
+
61
+ accessibilityLabel,
62
+
63
+ accessibilityHint,
64
+
65
+ testID,
66
+
67
+ ...props
68
+ }) => {
69
+ const { theme } = useUITheme();
70
+
71
+ const safeSize = SIZES[size] ? size : SIZES.md;
72
+
73
+ const safeVariant = VARIANTS[variant] ? variant : VARIANTS.ghost;
74
+
75
+ const safeShape = SHAPES[shape] ? shape : SHAPES.circle;
76
+
77
+ const isDisabled = disabled || loading;
78
+
79
+ const dimensions = getDimensions(safeSize, theme);
80
+
81
+ const colors = getColors(safeVariant, theme.colors);
82
+
83
+ const borderRadius = getBorderRadius(safeShape, theme);
84
+
85
+ const handlePress = useCallback(
86
+ (event) => {
87
+ if (isDisabled) {
88
+ return;
89
+ }
90
+
91
+ onPress?.(event);
92
+ },
93
+ [isDisabled, onPress],
94
+ );
95
+
96
+ const handleLongPress = useCallback(
97
+ (event) => {
98
+ if (isDisabled) {
99
+ return;
100
+ }
101
+
102
+ onLongPress?.(event);
103
+ },
104
+ [isDisabled, onLongPress],
105
+ );
106
+
107
+ const handlePressIn = useCallback(
108
+ (event) => {
109
+ if (isDisabled) {
110
+ return;
111
+ }
112
+
113
+ onPressIn?.(event);
114
+ },
115
+ [isDisabled, onPressIn],
116
+ );
117
+
118
+ const handlePressOut = useCallback(
119
+ (event) => {
120
+ if (isDisabled) {
121
+ return;
122
+ }
123
+
124
+ onPressOut?.(event);
125
+ },
126
+ [isDisabled, onPressOut],
127
+ );
128
+
129
+ const ripple = android_ripple
130
+ ? {
131
+ color: colors.ripple,
132
+ borderless: false,
133
+ }
134
+ : undefined;
135
+
136
+ return (
137
+ <Pressable
138
+ {...props}
139
+ testID={testID}
140
+ disabled={isDisabled}
141
+ onPress={handlePress}
142
+ onLongPress={handleLongPress}
143
+ onPressIn={handlePressIn}
144
+ onPressOut={handlePressOut}
145
+ hitSlop={hitSlop}
146
+ android_ripple={ripple}
147
+ accessibilityRole="button"
148
+ accessibilityLabel={accessibilityLabel}
149
+ accessibilityHint={accessibilityHint}
150
+ accessibilityState={{
151
+ disabled: isDisabled,
152
+ busy: loading,
153
+ }}
154
+ style={({ pressed }) => [
155
+ styles.button,
156
+
157
+ {
158
+ width: dimensions.size,
159
+
160
+ height: dimensions.size,
161
+
162
+ borderRadius,
163
+
164
+ backgroundColor: colors.background,
165
+
166
+ borderColor: colors.border,
167
+
168
+ borderWidth: colors.border ? 1 : 0,
169
+
170
+ opacity: isDisabled ? 0.5 : pressed ? activeOpacity : 1,
171
+ },
172
+
173
+ style,
174
+ ]}
175
+ >
176
+ <View style={[styles.iconContainer, iconContainerStyle]}>
177
+ {loading ? (
178
+ <ActivityIndicator size="small" color={colors.icon} />
179
+ ) : (
180
+ icon
181
+ )}
182
+ </View>
183
+ </Pressable>
184
+ );
185
+ };
186
+
187
+ function getDimensions(size, theme) {
188
+ switch (size) {
189
+ case SIZES.xs:
190
+ return {
191
+ size: theme.sizes.icon.lg + theme.spacing.sm,
192
+ };
193
+
194
+ case SIZES.sm:
195
+ return {
196
+ size: theme.sizes.button.sm,
197
+ };
198
+
199
+ case SIZES.lg:
200
+ return {
201
+ size: theme.sizes.button.lg,
202
+ };
203
+
204
+ case SIZES.xl:
205
+ return {
206
+ size: theme.sizes.button.xl,
207
+ };
208
+
209
+ case SIZES.md:
210
+ default:
211
+ return {
212
+ size: theme.sizes.button.md,
213
+ };
214
+ }
215
+ }
216
+
217
+ function getBorderRadius(shape, theme) {
218
+ switch (shape) {
219
+ case SHAPES.rounded:
220
+ return theme.radius.lg;
221
+
222
+ case SHAPES.square:
223
+ return theme.radius.sm;
224
+
225
+ case SHAPES.circle:
226
+ default:
227
+ return theme.radius.circle;
228
+ }
229
+ }
230
+
231
+ function getColors(variant, colors) {
232
+ switch (variant) {
233
+ case VARIANTS.solid:
234
+ return {
235
+ background: colors.primary,
236
+
237
+ border: colors.primary,
238
+
239
+ icon: colors.onPrimary,
240
+
241
+ ripple: colors.primaryPressed,
242
+ };
243
+
244
+ case VARIANTS.outline:
245
+ return {
246
+ background: colors.transparent,
247
+
248
+ border: colors.borderStrong,
249
+
250
+ icon: colors.text,
251
+
252
+ ripple: colors.surfaceSecondary,
253
+ };
254
+
255
+ case VARIANTS.soft:
256
+ return {
257
+ background: colors.primarySoft,
258
+
259
+ border: null,
260
+
261
+ icon: colors.primary,
262
+
263
+ ripple: colors.primary,
264
+ };
265
+
266
+ case VARIANTS.danger:
267
+ return {
268
+ background: colors.danger,
269
+
270
+ border: colors.danger,
271
+
272
+ icon: colors.onPrimary,
273
+
274
+ ripple: colors.dangerSoft,
275
+ };
276
+
277
+ case VARIANTS.success:
278
+ return {
279
+ background: colors.success,
280
+
281
+ border: colors.success,
282
+
283
+ icon: colors.onPrimary,
284
+
285
+ ripple: colors.successSoft,
286
+ };
287
+
288
+ case VARIANTS.ghost:
289
+ default:
290
+ return {
291
+ background: colors.transparent,
292
+
293
+ border: null,
294
+
295
+ icon: colors.text,
296
+
297
+ ripple: colors.surfaceSecondary,
298
+ };
299
+ }
300
+ }
301
+
302
+ const styles = StyleSheet.create({
303
+ button: {
304
+ alignItems: "center",
305
+ justifyContent: "center",
306
+ overflow: "hidden",
307
+ },
308
+
309
+ iconContainer: {
310
+ alignItems: "center",
311
+ justifyContent: "center",
312
+ flex: 1,
313
+ },
314
+ });
315
+
316
+ export const UIIconButton = memo(UIIconButtonComponent);
317
+
318
+ UIIconButton.displayName = "UIIconButton";
319
+
320
+ export {
321
+ SIZES as UIIconButtonSizes,
322
+ VARIANTS as UIIconButtonVariants,
323
+ SHAPES as UIIconButtonShapes,
324
+ };
@@ -0,0 +1,6 @@
1
+ export {
2
+ UIIconButton,
3
+ UIIconButtonSizes,
4
+ UIIconButtonVariants,
5
+ UIIconButtonShapes,
6
+ } from "./UIIconButton";
@@ -0,0 +1,60 @@
1
+ import React, { memo } from "react";
2
+
3
+ import { Text } from "react-native";
4
+
5
+ import { useUITheme } from "../../theme";
6
+
7
+ const UITextComponent = ({
8
+ children,
9
+
10
+ variant = "body",
11
+
12
+ color,
13
+
14
+ fontSize,
15
+
16
+ fontWeight,
17
+
18
+ lineHeight,
19
+
20
+ letterSpacing,
21
+
22
+ align,
23
+
24
+ style,
25
+
26
+ ...props
27
+ }) => {
28
+ const { theme } = useUITheme();
29
+
30
+ const typography = theme.typography[variant] || theme.typography.body;
31
+
32
+ return (
33
+ <Text
34
+ {...props}
35
+ style={[
36
+ {
37
+ color: color ?? theme.colors.text,
38
+
39
+ fontSize: fontSize ?? typography.fontSize,
40
+
41
+ lineHeight: lineHeight ?? typography.lineHeight,
42
+
43
+ fontWeight: fontWeight ?? typography.fontWeight,
44
+
45
+ letterSpacing,
46
+
47
+ textAlign: align,
48
+ },
49
+
50
+ style,
51
+ ]}
52
+ >
53
+ {children}
54
+ </Text>
55
+ );
56
+ };
57
+
58
+ export const UIText = memo(UITextComponent);
59
+
60
+ UIText.displayName = "UIText";
@@ -0,0 +1 @@
1
+ export { UIText } from "./UIText";
@@ -0,0 +1,8 @@
1
+ export { UIText } from "./Text";
2
+ export { UIButton, UIButtonVariants, UIButtonSizes } from "./Button";
3
+ export {
4
+ UIIconButton,
5
+ UIIconButtonSizes,
6
+ UIIconButtonVariants,
7
+ UIIconButtonShapes,
8
+ } from "./IconButton";
package/src/index.js ADDED
@@ -0,0 +1,27 @@
1
+ export { UIProvider, useUITheme } from "./theme";
2
+
3
+ export {
4
+ lightColors,
5
+ darkColors,
6
+ spacing,
7
+ radius,
8
+ typography,
9
+ fontSizes,
10
+ lineHeights,
11
+ fontWeights,
12
+ sizes,
13
+ shadows,
14
+ animation,
15
+ breakpoints,
16
+ } from "./theme";
17
+
18
+ export {
19
+ UIText,
20
+ UIButton,
21
+ UIButtonVariants,
22
+ UIButtonSizes,
23
+ UIIconButton,
24
+ UIIconButtonSizes,
25
+ UIIconButtonVariants,
26
+ UIIconButtonShapes,
27
+ } from "./components";
@@ -0,0 +1,86 @@
1
+ import React, { createContext, useContext, useMemo } from "react";
2
+
3
+ import { useColorScheme } from "react-native";
4
+
5
+ import { lightColors, darkColors } from "./colors";
6
+
7
+ import { spacing } from "./spacing";
8
+
9
+ import { radius } from "./radius";
10
+
11
+ import { typography } from "./typography";
12
+
13
+ import { sizes } from "./sizes";
14
+
15
+ import { shadows } from "./shadows";
16
+
17
+ import { animation } from "./animation";
18
+
19
+ import { breakpoints } from "./breakpoints";
20
+
21
+ const UIContext = createContext(null);
22
+
23
+ function createTheme(mode, overrides) {
24
+ const colors = mode === "dark" ? darkColors : lightColors;
25
+
26
+ return {
27
+ mode,
28
+
29
+ colors: {
30
+ ...colors,
31
+ ...overrides?.colors,
32
+ },
33
+
34
+ spacing,
35
+ radius,
36
+ typography,
37
+ sizes,
38
+ shadows,
39
+ animation,
40
+ breakpoints,
41
+ };
42
+ }
43
+
44
+ export function UIProvider({ children, mode = "system", onModeChange, theme }) {
45
+ const systemScheme = useColorScheme();
46
+
47
+ const resolvedMode =
48
+ mode === "system" ? (systemScheme === "dark" ? "dark" : "light") : mode;
49
+
50
+ const setMode = (nextMode) => {
51
+ if (nextMode !== "light" && nextMode !== "dark" && nextMode !== "system") {
52
+ return;
53
+ }
54
+
55
+ if (onModeChange) {
56
+ onModeChange(nextMode);
57
+ }
58
+ };
59
+
60
+ const contextValue = useMemo(
61
+ () => ({
62
+ theme: createTheme(resolvedMode, theme),
63
+
64
+ mode: resolvedMode,
65
+
66
+ selectedMode: mode,
67
+
68
+ setMode,
69
+ }),
70
+ [resolvedMode, mode, theme, onModeChange],
71
+ );
72
+
73
+ return (
74
+ <UIContext.Provider value={contextValue}>{children}</UIContext.Provider>
75
+ );
76
+ }
77
+
78
+ export function useUITheme() {
79
+ const context = useContext(UIContext);
80
+
81
+ if (!context) {
82
+ throw new Error("useUITheme must be used inside UIProvider.");
83
+ }
84
+
85
+ return context;
86
+ }
@@ -0,0 +1,14 @@
1
+ export const animation = {
2
+ instant: 0,
3
+
4
+ fast: 120,
5
+ normal: 200,
6
+ medium: 280,
7
+ slow: 400,
8
+
9
+ spring: {
10
+ damping: 18,
11
+ stiffness: 180,
12
+ mass: 0.8,
13
+ },
14
+ };
@@ -0,0 +1,10 @@
1
+ export const breakpoints = {
2
+ phoneSmall: 320,
3
+ phone: 360,
4
+ phoneLarge: 480,
5
+
6
+ tablet: 768,
7
+ tabletLarge: 1024,
8
+
9
+ desktop: 1280,
10
+ };
@@ -0,0 +1,99 @@
1
+ export const lightColors = {
2
+ transparent: "transparent",
3
+
4
+ primary: "#FF5A1F",
5
+ primaryPressed: "#E94D17",
6
+ primarySoft: "#FFF0EA",
7
+ onPrimary: "#FFFFFF",
8
+
9
+ secondary: "#7CFF32",
10
+ secondarySoft: "#F0FFE7",
11
+ onSecondary: "#111111",
12
+
13
+ background: "#FFFFFF",
14
+ surface: "#F8F8F8",
15
+ surfaceSecondary: "#F1F1F1",
16
+ card: "#FFFFFF",
17
+
18
+ text: "#111111",
19
+ textSecondary: "#525252",
20
+ textMuted: "#737373",
21
+ textDisabled: "#A3A3A3",
22
+ textInverse: "#FFFFFF",
23
+
24
+ border: "#E5E5E5",
25
+ borderStrong: "#D4D4D4",
26
+ divider: "#EEEEEE",
27
+
28
+ input: "#F7F7F7",
29
+ inputFocused: "#FFFFFF",
30
+ inputDisabled: "#EEEEEE",
31
+
32
+ success: "#16A34A",
33
+ successSoft: "#EAF8EF",
34
+
35
+ warning: "#D97706",
36
+ warningSoft: "#FFF7E6",
37
+
38
+ danger: "#DC2626",
39
+ dangerSoft: "#FDECEC",
40
+
41
+ info: "#2563EB",
42
+ infoSoft: "#EBF2FF",
43
+
44
+ overlay: "rgba(0,0,0,0.45)",
45
+ backdrop: "rgba(0,0,0,0.55)",
46
+
47
+ skeleton: "#E5E5E5",
48
+ skeletonHighlight: "#F5F5F5",
49
+ };
50
+
51
+ export const darkColors = {
52
+ transparent: "transparent",
53
+
54
+ primary: "#FF7040",
55
+ primaryPressed: "#FF5A1F",
56
+ primarySoft: "#3A1C11",
57
+ onPrimary: "#FFFFFF",
58
+
59
+ secondary: "#7CFF32",
60
+ secondarySoft: "#1B3010",
61
+ onSecondary: "#111111",
62
+
63
+ background: "#0B0B0B",
64
+ surface: "#151515",
65
+ surfaceSecondary: "#1D1D1D",
66
+ card: "#181818",
67
+
68
+ text: "#F5F5F5",
69
+ textSecondary: "#D4D4D4",
70
+ textMuted: "#A3A3A3",
71
+ textDisabled: "#666666",
72
+ textInverse: "#111111",
73
+
74
+ border: "#292929",
75
+ borderStrong: "#3A3A3A",
76
+ divider: "#252525",
77
+
78
+ input: "#1C1C1C",
79
+ inputFocused: "#202020",
80
+ inputDisabled: "#242424",
81
+
82
+ success: "#4ADE80",
83
+ successSoft: "#12351F",
84
+
85
+ warning: "#FBBF24",
86
+ warningSoft: "#382B0B",
87
+
88
+ danger: "#F87171",
89
+ dangerSoft: "#3B1616",
90
+
91
+ info: "#60A5FA",
92
+ infoSoft: "#132A46",
93
+
94
+ overlay: "rgba(0,0,0,0.60)",
95
+ backdrop: "rgba(0,0,0,0.72)",
96
+
97
+ skeleton: "#292929",
98
+ skeletonHighlight: "#3A3A3A",
99
+ };
@@ -0,0 +1,17 @@
1
+ export { UIProvider, useUITheme } from "./UIProvider";
2
+
3
+ export { lightColors, darkColors } from "./colors";
4
+
5
+ export { spacing } from "./spacing";
6
+
7
+ export { radius } from "./radius";
8
+
9
+ export { typography, fontSizes, lineHeights, fontWeights } from "./typography";
10
+
11
+ export { sizes } from "./sizes";
12
+
13
+ export { shadows } from "./shadows";
14
+
15
+ export { animation } from "./animation";
16
+
17
+ export { breakpoints } from "./breakpoints";
@@ -0,0 +1,17 @@
1
+ export const radius = {
2
+ none: 0,
3
+
4
+ xs: 4,
5
+ sm: 6,
6
+ md: 10,
7
+ lg: 14,
8
+ xl: 18,
9
+ xxl: 24,
10
+
11
+ card: 18,
12
+ input: 12,
13
+ button: 12,
14
+
15
+ pill: 999,
16
+ circle: 9999,
17
+ };
@@ -0,0 +1,41 @@
1
+ import { Platform } from "react-native";
2
+
3
+ export const shadows = {
4
+ none: {
5
+ shadowOpacity: 0,
6
+ elevation: 0,
7
+ },
8
+
9
+ sm: {
10
+ shadowColor: "#000",
11
+ shadowOffset: {
12
+ width: 0,
13
+ height: 1,
14
+ },
15
+ shadowOpacity: 0.08,
16
+ shadowRadius: 3,
17
+ elevation: Platform.OS === "android" ? 2 : 1,
18
+ },
19
+
20
+ md: {
21
+ shadowColor: "#000",
22
+ shadowOffset: {
23
+ width: 0,
24
+ height: 3,
25
+ },
26
+ shadowOpacity: 0.12,
27
+ shadowRadius: 8,
28
+ elevation: Platform.OS === "android" ? 4 : 2,
29
+ },
30
+
31
+ lg: {
32
+ shadowColor: "#000",
33
+ shadowOffset: {
34
+ width: 0,
35
+ height: 6,
36
+ },
37
+ shadowOpacity: 0.16,
38
+ shadowRadius: 14,
39
+ elevation: Platform.OS === "android" ? 8 : 4,
40
+ },
41
+ };
@@ -0,0 +1,45 @@
1
+ export const sizes = {
2
+ icon: {
3
+ xs: 12,
4
+ sm: 16,
5
+ md: 20,
6
+ lg: 24,
7
+ xl: 32,
8
+ xxl: 40,
9
+ xxxl: 48,
10
+ },
11
+
12
+ button: {
13
+ sm: 36,
14
+ md: 44,
15
+ lg: 52,
16
+ xl: 60,
17
+ },
18
+
19
+ input: {
20
+ sm: 40,
21
+ md: 48,
22
+ lg: 56,
23
+ },
24
+
25
+ avatar: {
26
+ xs: 24,
27
+ sm: 32,
28
+ md: 40,
29
+ lg: 48,
30
+ xl: 64,
31
+ xxl: 80,
32
+ xxxl: 120,
33
+ },
34
+
35
+ touchTarget: 44,
36
+
37
+ header: 56,
38
+
39
+ tabBar: 64,
40
+
41
+ bottomSheetHandle: {
42
+ width: 40,
43
+ height: 4,
44
+ },
45
+ };
@@ -0,0 +1,24 @@
1
+ export const spacing = {
2
+ none: 0,
3
+
4
+ xxs: 2,
5
+ xs: 4,
6
+
7
+ sm: 8,
8
+ sm2: 10,
9
+
10
+ md: 12,
11
+ md2: 14,
12
+
13
+ lg: 16,
14
+ lg2: 20,
15
+
16
+ xl: 24,
17
+ xl2: 28,
18
+
19
+ xxl: 32,
20
+ xxxl: 40,
21
+
22
+ huge: 48,
23
+ massive: 64,
24
+ };
@@ -0,0 +1,107 @@
1
+ export const fontSizes = {
2
+ xs: 10,
3
+ sm: 12,
4
+ md: 14,
5
+ base: 16,
6
+ lg: 18,
7
+ xl: 20,
8
+ xxl: 24,
9
+ xxxl: 28,
10
+ display: 32,
11
+ displayLarge: 40,
12
+ };
13
+
14
+ export const lineHeights = {
15
+ xs: 14,
16
+ sm: 16,
17
+ md: 20,
18
+ base: 24,
19
+ lg: 26,
20
+ xl: 28,
21
+ xxl: 32,
22
+ xxxl: 36,
23
+ display: 40,
24
+ displayLarge: 48,
25
+ };
26
+
27
+ export const fontWeights = {
28
+ regular: "400",
29
+ medium: "500",
30
+ semibold: "600",
31
+ bold: "700",
32
+ extrabold: "800",
33
+ };
34
+
35
+ export const typography = {
36
+ displayLarge: {
37
+ fontSize: fontSizes.displayLarge,
38
+ lineHeight: lineHeights.displayLarge,
39
+ fontWeight: fontWeights.bold,
40
+ },
41
+
42
+ display: {
43
+ fontSize: fontSizes.display,
44
+ lineHeight: lineHeights.display,
45
+ fontWeight: fontWeights.bold,
46
+ },
47
+
48
+ h1: {
49
+ fontSize: fontSizes.xxxl,
50
+ lineHeight: lineHeights.xxxl,
51
+ fontWeight: fontWeights.bold,
52
+ },
53
+
54
+ h2: {
55
+ fontSize: fontSizes.xxl,
56
+ lineHeight: lineHeights.xxl,
57
+ fontWeight: fontWeights.bold,
58
+ },
59
+
60
+ h3: {
61
+ fontSize: fontSizes.xl,
62
+ lineHeight: lineHeights.xl,
63
+ fontWeight: fontWeights.semibold,
64
+ },
65
+
66
+ h4: {
67
+ fontSize: fontSizes.lg,
68
+ lineHeight: lineHeights.lg,
69
+ fontWeight: fontWeights.semibold,
70
+ },
71
+
72
+ bodyLarge: {
73
+ fontSize: fontSizes.lg,
74
+ lineHeight: lineHeights.lg,
75
+ fontWeight: fontWeights.regular,
76
+ },
77
+
78
+ body: {
79
+ fontSize: fontSizes.base,
80
+ lineHeight: lineHeights.base,
81
+ fontWeight: fontWeights.regular,
82
+ },
83
+
84
+ bodyMedium: {
85
+ fontSize: fontSizes.base,
86
+ lineHeight: lineHeights.base,
87
+ fontWeight: fontWeights.medium,
88
+ },
89
+
90
+ label: {
91
+ fontSize: fontSizes.md,
92
+ lineHeight: lineHeights.md,
93
+ fontWeight: fontWeights.semibold,
94
+ },
95
+
96
+ caption: {
97
+ fontSize: fontSizes.sm,
98
+ lineHeight: lineHeights.sm,
99
+ fontWeight: fontWeights.regular,
100
+ },
101
+
102
+ overline: {
103
+ fontSize: fontSizes.xs,
104
+ lineHeight: lineHeights.xs,
105
+ fontWeight: fontWeights.bold,
106
+ },
107
+ };