rnnovaui 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnnovaui",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -1,12 +1,16 @@
1
1
  import React, { memo, useCallback } from "react";
2
2
 
3
- import { ActivityIndicator, Pressable, StyleSheet, View } from "react-native";
3
+ import {
4
+ ActivityIndicator,
5
+ Pressable,
6
+ StyleSheet,
7
+ Text,
8
+ View,
9
+ } from "react-native";
4
10
 
5
11
  import { useUITheme } from "../../theme";
6
12
 
7
- import { UIText } from "../Text";
8
-
9
- const BUTTON_VARIANTS = {
13
+ const VARIANTS = {
10
14
  solid: "solid",
11
15
  outline: "outline",
12
16
  ghost: "ghost",
@@ -15,77 +19,65 @@ const BUTTON_VARIANTS = {
15
19
  success: "success",
16
20
  };
17
21
 
18
- const BUTTON_SIZES = {
22
+ const SIZES = {
19
23
  sm: "sm",
20
24
  md: "md",
21
25
  lg: "lg",
22
26
  xl: "xl",
23
27
  };
24
28
 
25
- const UIButtonComponent = ({
29
+ function UIButtonComponent({
26
30
  title,
27
-
28
31
  children,
29
32
 
30
33
  variant = "solid",
31
-
32
34
  size = "md",
33
35
 
34
36
  disabled = false,
35
-
36
37
  loading = false,
37
-
38
38
  fullWidth = false,
39
39
 
40
- leftIcon = null,
41
-
42
- rightIcon = null,
40
+ leftIcon,
41
+ rightIcon,
43
42
 
44
43
  onPress,
45
-
46
44
  onLongPress,
47
-
48
45
  onPressIn,
49
-
50
46
  onPressOut,
51
47
 
52
48
  style,
53
-
54
49
  contentStyle,
55
-
56
50
  textStyle,
57
51
 
58
- testID,
52
+ activeOpacity = 0.82,
59
53
 
54
+ testID,
60
55
  accessibilityLabel,
61
-
62
56
  accessibilityHint,
63
57
 
64
- accessibilityRole = "button",
65
-
66
- hitSlop,
67
-
68
- pressRetentionOffset,
58
+ ...props
59
+ }) {
60
+ const themeResult = useUITheme();
69
61
 
70
- android_ripple = true,
62
+ /*
63
+ * Support both possible provider shapes:
64
+ *
65
+ * themeResult.theme.colors
66
+ * themeResult.colors
67
+ */
68
+ const theme = themeResult?.theme || themeResult || {};
71
69
 
72
- activeOpacity = 0.82,
70
+ const colors = theme.colors || {};
73
71
 
74
- ...props
75
- }) => {
76
- const { theme } = useUITheme();
72
+ const safeVariant = VARIANTS[variant] || VARIANTS.solid;
77
73
 
78
- const safeVariant = BUTTON_VARIANTS[variant]
79
- ? variant
80
- : BUTTON_VARIANTS.solid;
74
+ const safeSize = SIZES[size] || SIZES.md;
81
75
 
82
- const safeSize = BUTTON_SIZES[size] ? size : BUTTON_SIZES.md;
76
+ const buttonColors = getButtonColors(safeVariant, colors);
83
77
 
84
- const isDisabled = Boolean(disabled) || Boolean(loading);
78
+ const dimensions = getButtonDimensions(safeSize);
85
79
 
86
- const colors = getVariantColors(safeVariant, theme.colors);
87
-
88
- const dimensions = getSizeDimensions(safeSize, theme);
80
+ const isDisabled = disabled || loading;
89
81
 
90
82
  const handlePress = useCallback(
91
83
  (event) => {
@@ -93,9 +85,7 @@ const UIButtonComponent = ({
93
85
  return;
94
86
  }
95
87
 
96
- if (typeof onPress === "function") {
97
- onPress(event);
98
- }
88
+ onPress?.(event);
99
89
  },
100
90
  [isDisabled, onPress],
101
91
  );
@@ -106,9 +96,7 @@ const UIButtonComponent = ({
106
96
  return;
107
97
  }
108
98
 
109
- if (typeof onLongPress === "function") {
110
- onLongPress(event);
111
- }
99
+ onLongPress?.(event);
112
100
  },
113
101
  [isDisabled, onLongPress],
114
102
  );
@@ -119,9 +107,7 @@ const UIButtonComponent = ({
119
107
  return;
120
108
  }
121
109
 
122
- if (typeof onPressIn === "function") {
123
- onPressIn(event);
124
- }
110
+ onPressIn?.(event);
125
111
  },
126
112
  [isDisabled, onPressIn],
127
113
  );
@@ -132,34 +118,12 @@ const UIButtonComponent = ({
132
118
  return;
133
119
  }
134
120
 
135
- if (typeof onPressOut === "function") {
136
- onPressOut(event);
137
- }
121
+ onPressOut?.(event);
138
122
  },
139
123
  [isDisabled, onPressOut],
140
124
  );
141
125
 
142
- const ripple = android_ripple
143
- ? {
144
- color: colors.ripple,
145
- borderless: false,
146
- }
147
- : undefined;
148
-
149
- /*
150
- * Important:
151
- *
152
- * title = text
153
- *
154
- * children = arbitrary JSX
155
- *
156
- * We NEVER put arbitrary children
157
- * inside UIText.
158
- */
159
- const hasCustomChildren = children !== undefined && children !== null;
160
-
161
- const accessibilityText =
162
- accessibilityLabel ?? (typeof title === "string" ? title : undefined);
126
+ const hasChildren = children !== undefined && children !== null;
163
127
 
164
128
  return (
165
129
  <Pressable
@@ -170,35 +134,36 @@ const UIButtonComponent = ({
170
134
  onLongPress={handleLongPress}
171
135
  onPressIn={handlePressIn}
172
136
  onPressOut={handlePressOut}
173
- hitSlop={hitSlop}
174
- pressRetentionOffset={pressRetentionOffset}
175
- android_ripple={ripple}
176
- accessibilityRole={accessibilityRole}
177
- accessibilityLabel={accessibilityText}
137
+ accessibilityRole="button"
138
+ accessibilityLabel={
139
+ accessibilityLabel || (typeof title === "string" ? title : undefined)
140
+ }
178
141
  accessibilityHint={accessibilityHint}
179
142
  accessibilityState={{
180
143
  disabled: isDisabled,
181
- busy: Boolean(loading),
144
+ busy: loading,
182
145
  }}
183
146
  style={({ pressed }) => [
184
147
  styles.button,
185
148
 
186
149
  {
150
+ height: dimensions.height,
151
+
187
152
  minHeight: dimensions.height,
188
153
 
189
154
  paddingHorizontal: dimensions.paddingHorizontal,
190
155
 
191
- borderRadius: theme.radius.button,
156
+ borderRadius: dimensions.borderRadius,
192
157
 
193
- backgroundColor: colors.background,
158
+ backgroundColor: buttonColors.background,
194
159
 
195
- borderColor: colors.border,
160
+ borderColor: buttonColors.border,
196
161
 
197
- borderWidth: colors.border ? 1 : 0,
162
+ borderWidth: buttonColors.border ? 1 : 0,
198
163
 
199
164
  width: fullWidth ? "100%" : undefined,
200
165
 
201
- opacity: isDisabled ? 0.55 : pressed ? activeOpacity : 1,
166
+ opacity: isDisabled ? 0.5 : pressed ? activeOpacity : 1,
202
167
  },
203
168
 
204
169
  style,
@@ -209,8 +174,6 @@ const UIButtonComponent = ({
209
174
  styles.content,
210
175
 
211
176
  {
212
- minHeight: dimensions.height - 2,
213
-
214
177
  gap: dimensions.gap,
215
178
  },
216
179
 
@@ -218,162 +181,171 @@ const UIButtonComponent = ({
218
181
  ]}
219
182
  >
220
183
  {loading ? (
221
- <ActivityIndicator size="small" color={colors.text} />
222
- ) : (
223
- leftIcon
224
- )}
184
+ <ActivityIndicator size="small" color={buttonColors.text} />
185
+ ) : leftIcon ? (
186
+ <View style={styles.icon}>{leftIcon}</View>
187
+ ) : null}
225
188
 
226
- {hasCustomChildren ? (
189
+ {hasChildren ? (
227
190
  <View style={styles.children}>{children}</View>
228
- ) : title !== undefined && title !== null ? (
229
- <UIText
230
- variant={dimensions.textVariant}
231
- color={colors.text}
232
- style={[styles.title, textStyle]}
191
+ ) : title ? (
192
+ <Text
233
193
  numberOfLines={1}
194
+ style={[
195
+ styles.text,
196
+
197
+ {
198
+ color: buttonColors.text,
199
+
200
+ fontSize: dimensions.fontSize,
201
+
202
+ lineHeight: dimensions.lineHeight,
203
+ },
204
+
205
+ textStyle,
206
+ ]}
234
207
  >
235
208
  {title}
236
- </UIText>
209
+ </Text>
237
210
  ) : null}
238
211
 
239
212
  {!loading && rightIcon ? (
240
- <View style={styles.iconRight}>{rightIcon}</View>
213
+ <View style={styles.icon}>{rightIcon}</View>
241
214
  ) : null}
242
215
  </View>
243
216
  </Pressable>
244
217
  );
245
- };
218
+ }
246
219
 
247
- function getVariantColors(variant, colors) {
248
- switch (variant) {
249
- case BUTTON_VARIANTS.outline:
250
- return {
251
- background: colors.transparent,
220
+ /**
221
+ * IMPORTANT:
222
+ *
223
+ * Variant resolution is completely independent
224
+ * from spacing/radius/typography.
225
+ *
226
+ * Therefore even if the theme is incomplete,
227
+ * variants still work.
228
+ */
229
+ function getButtonColors(variant, colors) {
230
+ const primary = colors.primary || "#FF5A1F";
252
231
 
253
- border: colors.primary,
232
+ const primarySoft = colors.primarySoft || "#FFF0EA";
254
233
 
255
- text: colors.primary,
234
+ const success = colors.success || "#16A34A";
256
235
 
257
- ripple: colors.primarySoft,
258
- };
236
+ const danger = colors.danger || "#DC2626";
259
237
 
260
- case BUTTON_VARIANTS.ghost:
261
- return {
262
- background: colors.transparent,
238
+ const text = colors.text || "#111111";
263
239
 
264
- border: null,
240
+ const onPrimary = colors.onPrimary || "#FFFFFF";
265
241
 
266
- text: colors.primary,
242
+ const border = colors.border || "#E5E5E5";
267
243
 
268
- ripple: colors.primarySoft,
244
+ switch (variant) {
245
+ case VARIANTS.outline:
246
+ return {
247
+ background: "transparent",
248
+ border: primary,
249
+ text: primary,
269
250
  };
270
251
 
271
- case BUTTON_VARIANTS.soft:
252
+ case VARIANTS.ghost:
272
253
  return {
273
- background: colors.primarySoft,
274
-
254
+ background: "transparent",
275
255
  border: null,
276
-
277
- text: colors.primary,
278
-
279
- ripple: colors.primary,
256
+ text: primary,
280
257
  };
281
258
 
282
- case BUTTON_VARIANTS.danger:
259
+ case VARIANTS.soft:
283
260
  return {
284
- background: colors.danger,
285
-
286
- border: colors.danger,
287
-
288
- text: colors.onPrimary,
289
-
290
- ripple: colors.dangerSoft || colors.danger,
261
+ background: primarySoft,
262
+ border: null,
263
+ text: primary,
291
264
  };
292
265
 
293
- case BUTTON_VARIANTS.success:
266
+ case VARIANTS.danger:
294
267
  return {
295
- background: colors.success,
296
-
297
- border: colors.success,
298
-
299
- text: colors.onPrimary,
268
+ background: danger,
269
+ border: danger,
270
+ text: onPrimary,
271
+ };
300
272
 
301
- ripple: colors.successSoft || colors.success,
273
+ case VARIANTS.success:
274
+ return {
275
+ background: success,
276
+ border: success,
277
+ text: onPrimary,
302
278
  };
303
279
 
304
- case BUTTON_VARIANTS.solid:
280
+ case VARIANTS.solid:
305
281
  default:
306
282
  return {
307
- background: colors.primary,
308
-
309
- border: colors.primary,
310
-
311
- text: colors.onPrimary,
312
-
313
- ripple: colors.primaryPressed || colors.primary,
283
+ background: primary,
284
+ border: primary,
285
+ text: onPrimary,
314
286
  };
315
287
  }
316
288
  }
317
289
 
318
- function getSizeDimensions(size, theme) {
290
+ /**
291
+ * Sizes intentionally use fixed fallback
292
+ * values so the button cannot break when
293
+ * theme.sizes is missing.
294
+ */
295
+ function getButtonDimensions(size) {
319
296
  switch (size) {
320
- case BUTTON_SIZES.sm:
297
+ case SIZES.sm:
321
298
  return {
322
- height: theme.sizes.button.sm,
323
-
324
- paddingHorizontal: theme.spacing.md,
325
-
326
- gap: theme.spacing.xs,
327
-
328
- textVariant: "label",
299
+ height: 36,
300
+ paddingHorizontal: 14,
301
+ gap: 6,
302
+ borderRadius: 10,
303
+ fontSize: 13,
304
+ lineHeight: 18,
329
305
  };
330
306
 
331
- case BUTTON_SIZES.lg:
307
+ case SIZES.lg:
332
308
  return {
333
- height: theme.sizes.button.lg,
334
-
335
- paddingHorizontal: theme.spacing.xl,
336
-
337
- gap: theme.spacing.sm,
338
-
339
- textVariant: "bodyMedium",
309
+ height: 52,
310
+ paddingHorizontal: 22,
311
+ gap: 8,
312
+ borderRadius: 14,
313
+ fontSize: 16,
314
+ lineHeight: 22,
340
315
  };
341
316
 
342
- case BUTTON_SIZES.xl:
317
+ case SIZES.xl:
343
318
  return {
344
- height: theme.sizes.button.xl,
345
-
346
- paddingHorizontal: theme.spacing.xl2,
347
-
348
- gap: theme.spacing.sm,
349
-
350
- textVariant: "bodyMedium",
319
+ height: 60,
320
+ paddingHorizontal: 26,
321
+ gap: 8,
322
+ borderRadius: 16,
323
+ fontSize: 17,
324
+ lineHeight: 24,
351
325
  };
352
326
 
353
- case BUTTON_SIZES.md:
327
+ case SIZES.md:
354
328
  default:
355
329
  return {
356
- height: theme.sizes.button.md,
357
-
358
- paddingHorizontal: theme.spacing.lg,
359
-
360
- gap: theme.spacing.sm,
361
-
362
- textVariant: "label",
330
+ height: 44,
331
+ paddingHorizontal: 18,
332
+ gap: 8,
333
+ borderRadius: 12,
334
+ fontSize: 14,
335
+ lineHeight: 20,
363
336
  };
364
337
  }
365
338
  }
366
339
 
367
340
  const styles = StyleSheet.create({
368
341
  button: {
342
+ flexShrink: 0,
369
343
  alignItems: "center",
370
344
  justifyContent: "center",
371
345
  overflow: "hidden",
372
- flexShrink: 0,
373
346
  },
374
347
 
375
348
  content: {
376
- width: "100%",
377
349
  flexDirection: "row",
378
350
  alignItems: "center",
379
351
  justifyContent: "center",
@@ -386,20 +358,24 @@ const styles = StyleSheet.create({
386
358
  justifyContent: "center",
387
359
  },
388
360
 
389
- title: {
390
- flexShrink: 1,
391
- textAlign: "center",
392
- },
393
-
394
- iconRight: {
361
+ icon: {
395
362
  alignItems: "center",
396
363
  justifyContent: "center",
397
364
  flexShrink: 0,
398
365
  },
366
+
367
+ text: {
368
+ flexShrink: 1,
369
+ fontWeight: "600",
370
+ textAlign: "center",
371
+ includeFontPadding: false,
372
+ },
399
373
  });
400
374
 
401
375
  export const UIButton = memo(UIButtonComponent);
402
376
 
403
377
  UIButton.displayName = "UIButton";
404
378
 
405
- export { BUTTON_VARIANTS as UIButtonVariants, BUTTON_SIZES as UIButtonSizes };
379
+ export const UIButtonVariants = VARIANTS;
380
+
381
+ export const UIButtonSizes = SIZES;