@campxdev/react-native-blueprint 0.1.14 → 0.1.15

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.
Files changed (180) hide show
  1. package/lib/module/assets/Loading Animation.json +1 -0
  2. package/lib/module/assets/lotties/index.js +2 -1
  3. package/lib/module/assets/lotties/index.js.map +1 -1
  4. package/lib/module/components/DataDisplay/Accordion/Accordion.js +1 -0
  5. package/lib/module/components/DataDisplay/Accordion/Accordion.js.map +1 -1
  6. package/lib/module/components/DataDisplay/AccordionItem/AccordionItem.js +1 -0
  7. package/lib/module/components/DataDisplay/AccordionItem/AccordionItem.js.map +1 -1
  8. package/lib/module/components/DataDisplay/Badge/Badge.figma.js +1 -0
  9. package/lib/module/components/DataDisplay/Badge/Badge.figma.js.map +1 -1
  10. package/lib/module/components/DataDisplay/Badge/Badge.js +1 -0
  11. package/lib/module/components/DataDisplay/Badge/Badge.js.map +1 -1
  12. package/lib/module/components/DataDisplay/BannerRow/BannerRow.js +1 -0
  13. package/lib/module/components/DataDisplay/BannerRow/BannerRow.js.map +1 -1
  14. package/lib/module/components/DataDisplay/CalendarItem/CalendarItem.js +1 -0
  15. package/lib/module/components/DataDisplay/CalendarItem/CalendarItem.js.map +1 -1
  16. package/lib/module/components/DataDisplay/Card/Card.js +1 -0
  17. package/lib/module/components/DataDisplay/Card/Card.js.map +1 -1
  18. package/lib/module/components/DataDisplay/Chips/Chips.js +1 -0
  19. package/lib/module/components/DataDisplay/Chips/Chips.js.map +1 -1
  20. package/lib/module/components/DataDisplay/ChipsRow/ChipsRow.js +1 -0
  21. package/lib/module/components/DataDisplay/ChipsRow/ChipsRow.js.map +1 -1
  22. package/lib/module/components/DataDisplay/DataListItem/DataListItem.js +1 -0
  23. package/lib/module/components/DataDisplay/DataListItem/DataListItem.js.map +1 -1
  24. package/lib/module/components/DataDisplay/Datalist/Datalist.js +1 -0
  25. package/lib/module/components/DataDisplay/Datalist/Datalist.js.map +1 -1
  26. package/lib/module/components/DataDisplay/Separator/Separator.js +1 -0
  27. package/lib/module/components/DataDisplay/Separator/Separator.js.map +1 -1
  28. package/lib/module/components/DataDisplay/Skeleton/Skeleton.js +1 -0
  29. package/lib/module/components/DataDisplay/Skeleton/Skeleton.js.map +1 -1
  30. package/lib/module/components/DataDisplay/Tooltip/Tooltip.figma.js +1 -0
  31. package/lib/module/components/DataDisplay/Tooltip/Tooltip.figma.js.map +1 -1
  32. package/lib/module/components/DataDisplay/Tooltip/Tooltip.js +1 -0
  33. package/lib/module/components/DataDisplay/Tooltip/Tooltip.js.map +1 -1
  34. package/lib/module/components/Feedback/InfoBar/InfoBar.figma.js +32 -0
  35. package/lib/module/components/Feedback/InfoBar/InfoBar.figma.js.map +1 -0
  36. package/lib/module/components/Feedback/InfoBar/InfoBar.js +107 -0
  37. package/lib/module/components/Feedback/InfoBar/InfoBar.js.map +1 -0
  38. package/lib/module/components/Feedback/ProgressCircular/Progress-circular.js +1 -0
  39. package/lib/module/components/Feedback/ProgressCircular/Progress-circular.js.map +1 -1
  40. package/lib/module/components/Feedback/ProgressLinear/Progress-linear.js +1 -0
  41. package/lib/module/components/Feedback/ProgressLinear/Progress-linear.js.map +1 -1
  42. package/lib/module/components/Input/Button/Button.js +99 -43
  43. package/lib/module/components/Input/Button/Button.js.map +1 -1
  44. package/lib/module/components/Input/Checkbox/Checkbox.js +1 -0
  45. package/lib/module/components/Input/Checkbox/Checkbox.js.map +1 -1
  46. package/lib/module/components/Input/RadioGroup/Radio-Group.figma.js +3 -3
  47. package/lib/module/components/Input/RadioGroup/Radio-Group.figma.js.map +1 -1
  48. package/lib/module/components/Input/RadioGroup/Radio-Group.js +1 -0
  49. package/lib/module/components/Input/RadioGroup/Radio-Group.js.map +1 -1
  50. package/lib/module/components/Input/Select/Select.js +1 -0
  51. package/lib/module/components/Input/Select/Select.js.map +1 -1
  52. package/lib/module/components/Input/TextField/Textfield.figma.js +4 -2
  53. package/lib/module/components/Input/TextField/Textfield.figma.js.map +1 -1
  54. package/lib/module/components/Input/TextField/Textfield.js +161 -41
  55. package/lib/module/components/Input/TextField/Textfield.js.map +1 -1
  56. package/lib/module/components/Input/Toggle/Toggle.figma.js +32 -0
  57. package/lib/module/components/Input/Toggle/Toggle.figma.js.map +1 -0
  58. package/lib/module/components/Input/Toggle/Toggle.js +52 -23
  59. package/lib/module/components/Input/Toggle/Toggle.js.map +1 -1
  60. package/lib/module/components/Input/ToggleGroup/Toggle-Group.js +1 -0
  61. package/lib/module/components/Input/ToggleGroup/Toggle-Group.js.map +1 -1
  62. package/lib/module/components/Input/switch/Switch.figma.js +1 -0
  63. package/lib/module/components/Input/switch/Switch.figma.js.map +1 -1
  64. package/lib/module/components/Input/switch/Switch.js +1 -0
  65. package/lib/module/components/Input/switch/Switch.js.map +1 -1
  66. package/lib/module/components/Layout/Tabs/Tabs.figma.js +1 -0
  67. package/lib/module/components/Layout/Tabs/Tabs.figma.js.map +1 -1
  68. package/lib/module/components/Layout/Tabs/Tabs.js +1 -0
  69. package/lib/module/components/Layout/Tabs/Tabs.js.map +1 -1
  70. package/lib/module/components/Navigation/Appbar/AppBar.figma.js +1 -0
  71. package/lib/module/components/Navigation/Appbar/AppBar.figma.js.map +1 -1
  72. package/lib/module/components/Navigation/FloatingAction/Floating-Action.js +1 -0
  73. package/lib/module/components/Navigation/FloatingAction/Floating-Action.js.map +1 -1
  74. package/lib/module/components/Navigation/Popover/Popover.figma.js +1 -0
  75. package/lib/module/components/Navigation/Popover/Popover.figma.js.map +1 -1
  76. package/lib/module/components/Navigation/Popover/Popover.js +1 -0
  77. package/lib/module/components/Navigation/Popover/Popover.js.map +1 -1
  78. package/lib/module/components/ui/Custom-Card.js +1 -0
  79. package/lib/module/components/ui/Custom-Card.js.map +1 -1
  80. package/lib/module/components/ui/Dropdown-Menu.js +1 -0
  81. package/lib/module/components/ui/Dropdown-Menu.js.map +1 -1
  82. package/lib/module/components/ui/Greeting-Card.js +1 -0
  83. package/lib/module/components/ui/Greeting-Card.js.map +1 -1
  84. package/lib/module/components/ui/Hover-Card.js +1 -0
  85. package/lib/module/components/ui/Hover-Card.js.map +1 -1
  86. package/lib/module/components/ui/Icon.js +1 -0
  87. package/lib/module/components/ui/Icon.js.map +1 -1
  88. package/lib/module/components/ui/Input.js +1 -0
  89. package/lib/module/components/ui/Input.js.map +1 -1
  90. package/lib/module/components/ui/Menubar.js +1 -0
  91. package/lib/module/components/ui/Menubar.js.map +1 -1
  92. package/lib/module/components/ui/Slider.js +1 -0
  93. package/lib/module/components/ui/Slider.js.map +1 -1
  94. package/lib/module/components/ui/Table.js +1 -0
  95. package/lib/module/components/ui/Table.js.map +1 -1
  96. package/lib/module/components/ui/Toast.js +1 -0
  97. package/lib/module/components/ui/Toast.js.map +1 -1
  98. package/lib/module/components/ui/index.js +2 -0
  99. package/lib/module/components/ui/index.js.map +1 -1
  100. package/lib/module/patterns/pattern-components/AlertDialogPattern/AlertDialogPattern.js +1 -0
  101. package/lib/module/patterns/pattern-components/AlertDialogPattern/AlertDialogPattern.js.map +1 -1
  102. package/lib/module/patterns/pattern-components/AlertPattern/AlertPattern.js +1 -0
  103. package/lib/module/patterns/pattern-components/AlertPattern/AlertPattern.js.map +1 -1
  104. package/lib/module/patterns/pattern-components/CalendarPattern/CalendarPattern.figma.js +1 -0
  105. package/lib/module/patterns/pattern-components/CalendarPattern/CalendarPattern.figma.js.map +1 -1
  106. package/lib/module/patterns/pattern-components/CalendarPattern/CalendarPattern.js +1 -0
  107. package/lib/module/patterns/pattern-components/CalendarPattern/CalendarPattern.js.map +1 -1
  108. package/lib/module/patterns/pattern-components/EmptyState/EmptyState.js +1 -0
  109. package/lib/module/patterns/pattern-components/EmptyState/EmptyState.js.map +1 -1
  110. package/lib/module/patterns/pattern-components/EntityPatternGuided/EntityPatternGuided.js +1 -0
  111. package/lib/module/patterns/pattern-components/EntityPatternGuided/EntityPatternGuided.js.map +1 -1
  112. package/lib/module/patterns/pattern-components/EntityPatternOverview/EntityPatternOverview.js +1 -0
  113. package/lib/module/patterns/pattern-components/EntityPatternOverview/EntityPatternOverview.js.map +1 -1
  114. package/lib/module/patterns/pattern-components/EntityPatternStructured/EntityPatternStructured.js +1 -0
  115. package/lib/module/patterns/pattern-components/EntityPatternStructured/EntityPatternStructured.js.map +1 -1
  116. package/lib/module/patterns/pattern-components/FormPattern/FormPattern.js +1 -0
  117. package/lib/module/patterns/pattern-components/FormPattern/FormPattern.js.map +1 -1
  118. package/lib/module/patterns/pattern-components/index.js +1 -0
  119. package/lib/module/patterns/pattern-components/index.js.map +1 -1
  120. package/package.json +2 -2
  121. package/src/assets/Loading Animation.json +1 -0
  122. package/src/assets/lotties/index.ts +1 -0
  123. package/src/components/DataDisplay/Accordion/Accordion.tsx +1 -0
  124. package/src/components/DataDisplay/AccordionItem/AccordionItem.tsx +1 -0
  125. package/src/components/DataDisplay/Badge/Badge.figma.tsx +1 -0
  126. package/src/components/DataDisplay/Badge/Badge.tsx +1 -0
  127. package/src/components/DataDisplay/BannerRow/BannerRow.tsx +1 -0
  128. package/src/components/DataDisplay/CalendarItem/CalendarItem.tsx +1 -0
  129. package/src/components/DataDisplay/Card/Card.tsx +1 -0
  130. package/src/components/DataDisplay/Chips/Chips.tsx +1 -0
  131. package/src/components/DataDisplay/ChipsRow/ChipsRow.tsx +1 -0
  132. package/src/components/DataDisplay/DataListItem/DataListItem.tsx +1 -0
  133. package/src/components/DataDisplay/Datalist/Datalist.tsx +1 -0
  134. package/src/components/DataDisplay/Separator/Separator.tsx +1 -0
  135. package/src/components/DataDisplay/Skeleton/Skeleton.tsx +1 -0
  136. package/src/components/DataDisplay/Tooltip/Tooltip.figma.tsx +1 -0
  137. package/src/components/DataDisplay/Tooltip/Tooltip.tsx +1 -0
  138. package/src/components/Feedback/InfoBar/InfoBar.figma.tsx +31 -0
  139. package/src/components/Feedback/InfoBar/InfoBar.tsx +127 -0
  140. package/src/components/Feedback/ProgressCircular/Progress-circular.tsx +1 -0
  141. package/src/components/Feedback/ProgressLinear/Progress-linear.tsx +1 -0
  142. package/src/components/Input/Button/Button.tsx +110 -43
  143. package/src/components/Input/Checkbox/Checkbox.tsx +1 -0
  144. package/src/components/Input/RadioGroup/Radio-Group.figma.tsx +3 -3
  145. package/src/components/Input/RadioGroup/Radio-Group.tsx +1 -0
  146. package/src/components/Input/Select/Select.tsx +1 -0
  147. package/src/components/Input/TextField/Textfield.figma.tsx +2 -0
  148. package/src/components/Input/TextField/Textfield.tsx +195 -48
  149. package/src/components/Input/Toggle/Toggle.figma.tsx +37 -0
  150. package/src/components/Input/Toggle/Toggle.tsx +49 -22
  151. package/src/components/Input/ToggleGroup/Toggle-Group.tsx +1 -0
  152. package/src/components/Input/switch/Switch.figma.tsx +1 -0
  153. package/src/components/Input/switch/Switch.tsx +1 -0
  154. package/src/components/Layout/Tabs/Tabs.figma.tsx +1 -0
  155. package/src/components/Layout/Tabs/Tabs.tsx +1 -0
  156. package/src/components/Navigation/Appbar/AppBar.figma.tsx +1 -0
  157. package/src/components/Navigation/FloatingAction/Floating-Action.tsx +1 -0
  158. package/src/components/Navigation/Popover/Popover.figma.tsx +1 -0
  159. package/src/components/Navigation/Popover/Popover.tsx +1 -0
  160. package/src/components/ui/Custom-Card.tsx +1 -0
  161. package/src/components/ui/Dropdown-Menu.tsx +1 -0
  162. package/src/components/ui/Greeting-Card.tsx +1 -0
  163. package/src/components/ui/Hover-Card.tsx +1 -0
  164. package/src/components/ui/Icon.tsx +1 -0
  165. package/src/components/ui/Input.tsx +1 -0
  166. package/src/components/ui/Menubar.tsx +1 -0
  167. package/src/components/ui/Slider.tsx +1 -0
  168. package/src/components/ui/Table.tsx +1 -0
  169. package/src/components/ui/Toast.tsx +1 -0
  170. package/src/components/ui/index.ts +2 -0
  171. package/src/patterns/pattern-components/AlertDialogPattern/AlertDialogPattern.tsx +1 -0
  172. package/src/patterns/pattern-components/AlertPattern/AlertPattern.tsx +1 -0
  173. package/src/patterns/pattern-components/CalendarPattern/CalendarPattern.figma.tsx +1 -0
  174. package/src/patterns/pattern-components/CalendarPattern/CalendarPattern.tsx +1 -0
  175. package/src/patterns/pattern-components/EmptyState/EmptyState.tsx +1 -0
  176. package/src/patterns/pattern-components/EntityPatternGuided/EntityPatternGuided.tsx +1 -0
  177. package/src/patterns/pattern-components/EntityPatternOverview/EntityPatternOverview.tsx +1 -0
  178. package/src/patterns/pattern-components/EntityPatternStructured/EntityPatternStructured.tsx +1 -0
  179. package/src/patterns/pattern-components/FormPattern/FormPattern.tsx +1 -0
  180. package/src/patterns/pattern-components/index.ts +1 -0
@@ -1,16 +1,20 @@
1
+ // @ts-nocheck
1
2
  import * as React from 'react';
2
3
  import {
3
4
  Pressable,
4
5
  View,
5
6
  Text as RNText,
7
+ type GestureResponderEvent,
6
8
  type StyleProp,
7
9
  type ViewStyle,
8
10
  type PressableProps,
9
11
  } from 'react-native';
10
12
  import { cssInterop } from 'nativewind';
13
+ import { SquircleView } from 'react-native-figma-squircle';
11
14
  import { cva, type VariantProps } from 'class-variance-authority';
12
15
 
13
16
  import { cn } from '../../../lib/utils';
17
+ import { useThemeColors } from '../../../lib/theme';
14
18
 
15
19
  // NativeWind interop (className -> style)
16
20
  cssInterop(Pressable, { className: 'style' });
@@ -30,19 +34,66 @@ export const ButtonVariants = {
30
34
  * ✅ No inline styles (satisfies react-native/no-inline-styles)
31
35
  */
32
36
  function DummyIcon({ testID }: { testID?: string }) {
33
- return <View testID={testID} className="w-4 h-4 rounded bg-black/60" />;
37
+ return <View testID={testID} className="w-4 h-4 rounded bg-text-inverse" />;
34
38
  }
35
39
 
36
- const buttonVariants = cva('flex-row items-center justify-center rounded-md', {
40
+ /**
41
+ * Returns squircle fill & stroke colors per variant from THEME tokens
42
+ */
43
+ function useVariantSquircleParams(variant: string) {
44
+ const colors = useThemeColors();
45
+
46
+ const map: Record<
47
+ string,
48
+ { fillColor: string; strokeColor: string; strokeWidth: number }
49
+ > = {
50
+ default: {
51
+ fillColor: colors.brandPrimary,
52
+ strokeColor: 'transparent',
53
+ strokeWidth: 0,
54
+ },
55
+ secondary: {
56
+ fillColor: colors.brandSecondary,
57
+ strokeColor: colors.brandPrimary,
58
+ strokeWidth: 1,
59
+ },
60
+ outline: {
61
+ fillColor: 'transparent',
62
+ strokeColor: colors.border,
63
+ strokeWidth: 1,
64
+ },
65
+ ghost: {
66
+ fillColor: 'transparent',
67
+ strokeColor: 'transparent',
68
+ strokeWidth: 0,
69
+ },
70
+ destructive: {
71
+ fillColor: colors.destructive,
72
+ strokeColor: 'transparent',
73
+ strokeWidth: 0,
74
+ },
75
+ link: {
76
+ fillColor: 'transparent',
77
+ strokeColor: 'transparent',
78
+ strokeWidth: 0,
79
+ },
80
+ };
81
+
82
+ return map[variant] ?? map.default;
83
+ }
84
+
85
+ const CORNER_RADIUS = 8; // radius-lg
86
+ const CORNER_SMOOTHING = 1; // maximum smoothing
87
+
88
+ const buttonVariants = cva('flex-row items-center justify-center', {
37
89
  variants: {
38
90
  variant: {
39
- default: 'bg-brand-primary active:opacity-90',
40
- secondary:
41
- 'bg-brand-secondary border border-brand-primary active:opacity-90',
42
- outline: 'bg-transparent border border-border-default active:opacity-90',
43
- ghost: 'bg-transparent active:opacity-90',
44
- destructive: 'bg-highlight-alert-red active:opacity-90',
45
- link: 'bg-transparent',
91
+ default: 'gap-2 active:opacity-90',
92
+ secondary: 'active:opacity-90',
93
+ outline: 'active:opacity-90',
94
+ ghost: 'active:opacity-90',
95
+ destructive: 'active:opacity-90',
96
+ link: '',
46
97
  },
47
98
  size: {
48
99
  // ✅ Figma-like sizing
@@ -77,7 +128,7 @@ const buttonVariants = cva('flex-row items-center justify-center rounded-md', {
77
128
  const buttonTextVariants = cva('font-semibold', {
78
129
  variants: {
79
130
  variant: {
80
- default: 'text-primary-foreground',
131
+ default: 'text-text-inverse',
81
132
  secondary: 'text-foreground',
82
133
  outline: 'text-foreground',
83
134
  ghost: 'text-foreground',
@@ -177,44 +228,60 @@ export function Button(props: ButtonProps) {
177
228
  // spacing only when icons exist
178
229
  const hasIcons = Boolean(showLeftIcon || showRightIcon);
179
230
 
180
- // For link variant: we keep Pressable minimal
181
- // - still show label unless icon-only (rare)
182
- // - can show icons with gap
231
+ const squircleParams = useVariantSquircleParams(v);
232
+
233
+ const handlePress = React.useCallback(
234
+ (e: GestureResponderEvent) => {
235
+ if (d) return;
236
+ onPress?.(e);
237
+ },
238
+ [d, onPress]
239
+ );
240
+
183
241
  return (
184
- <Pressable
185
- testID={testID ?? 'button'}
186
- accessibilityRole="button"
187
- accessibilityState={{ disabled: d }}
188
- disabled={d}
189
- onPress={d ? undefined : onPress}
190
- className={cn(
191
- buttonVariants({
192
- variant: v,
193
- size: s,
194
- disabled: d,
195
- hasIcons,
196
- })
197
- )}
198
- style={style}
199
- {...rest}
200
- >
201
- {showLeftIcon ? <DummyIcon testID="button-left-icon" /> : null}
202
-
203
- {!isIconOnly ? (
204
- <RNText
205
- testID="button-label"
242
+ <View style={style}>
243
+ <SquircleView
244
+ squircleParams={{
245
+ cornerRadius: CORNER_RADIUS,
246
+ cornerSmoothing: CORNER_SMOOTHING,
247
+ ...squircleParams,
248
+ }}
249
+ >
250
+ <Pressable
251
+ testID={testID ?? 'button'}
252
+ accessibilityRole="button"
253
+ accessibilityState={{ disabled: d }}
254
+ disabled={d}
255
+ onPress={handlePress}
206
256
  className={cn(
207
- buttonTextVariants({ variant: v, size: s, disabled: d })
257
+ buttonVariants({
258
+ variant: v,
259
+ size: s,
260
+ disabled: d,
261
+ hasIcons,
262
+ })
208
263
  )}
209
- numberOfLines={1}
210
- ellipsizeMode="tail"
264
+ {...rest}
211
265
  >
212
- {children}
213
- </RNText>
214
- ) : null}
266
+ {showLeftIcon ? <DummyIcon testID="button-left-icon" /> : null}
267
+
268
+ {!isIconOnly ? (
269
+ <RNText
270
+ testID="button-label"
271
+ className={cn(
272
+ buttonTextVariants({ variant: v, size: s, disabled: d })
273
+ )}
274
+ numberOfLines={1}
275
+ ellipsizeMode="tail"
276
+ >
277
+ {children}
278
+ </RNText>
279
+ ) : null}
215
280
 
216
- {showRightIcon ? <DummyIcon testID="button-right-icon" /> : null}
217
- </Pressable>
281
+ {showRightIcon ? <DummyIcon testID="button-right-icon" /> : null}
282
+ </Pressable>
283
+ </SquircleView>
284
+ </View>
218
285
  );
219
286
  }
220
287
 
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import * as React from 'react';
2
3
  import { Pressable, View, type StyleProp, type ViewStyle } from 'react-native';
3
4
  import { cssInterop } from 'nativewind';
@@ -17,14 +17,14 @@ figma.connect(RadioGroup, FIGMA_URL, {
17
17
  }),
18
18
 
19
19
  state: figma.enum('state', {
20
- unchecked: 'Unchecked',
21
- checked: 'Checked',
20
+ Unchecked: 'unchecked',
21
+ Checked: 'checked',
22
22
  }),
23
23
  },
24
24
 
25
25
  example: ({ size, disabled, state }) => (
26
26
  <RadioGroup
27
- value={state === 'Checked' ? 'one' : ''}
27
+ value={state === 'checked' ? 'one' : ''}
28
28
  size={size}
29
29
  disabled={disabled}
30
30
  onValueChange={() => {}}
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import * as React from 'react';
2
3
  import {
3
4
  Pressable,
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import * as React from 'react';
2
3
  import {
3
4
  View as RNView,
@@ -13,6 +13,7 @@ figma.connect(TextField, FIGMA_URL, {
13
13
  success: 'success',
14
14
  error: 'error',
15
15
  disabled: 'disabled',
16
+ loading: 'loading',
16
17
  }),
17
18
 
18
19
  // Content
@@ -27,6 +28,7 @@ figma.connect(TextField, FIGMA_URL, {
27
28
  search: 'search',
28
29
  ai: 'ai',
29
30
  composer: 'composer',
31
+ code: 'code',
30
32
  }),
31
33
 
32
34
  // Visibility
@@ -1,4 +1,5 @@
1
1
  /// <reference types="nativewind/types" />
2
+ // @ts-nocheck
2
3
  import * as React from 'react';
3
4
  import * as Slot from '@rn-primitives/slot';
4
5
  import type { SlottableViewProps, ViewRef } from '@rn-primitives/types';
@@ -6,9 +7,13 @@ import { cva, type VariantProps } from 'class-variance-authority';
6
7
  import { cssInterop } from 'nativewind';
7
8
  import { View, TextInput } from 'react-native';
8
9
  import type { LucideIcon } from 'lucide-react-native';
10
+ import LottieView from 'lottie-react-native';
11
+ import { SquircleView } from 'react-native-figma-squircle';
9
12
 
10
13
  import { cn } from '../../../lib/utils';
11
14
  import { TextClassContext, Text } from '../Text/Text';
15
+ import { Lotties } from '../../../assets/lotties';
16
+ import { useThemeColors } from '../../../lib/theme';
12
17
 
13
18
  // NativeWind interop (className -> style)
14
19
  cssInterop(View, { className: 'style' });
@@ -23,17 +28,62 @@ const StyledTextInput = TextInput as any;
23
28
  * Variants
24
29
  * -------------------------------------------------------------------------- */
25
30
 
31
+ const CORNER_RADIUS = 8;
32
+ const CORNER_SMOOTHING = 1;
33
+
34
+ function useStateSquircleParams(state: string) {
35
+ const colors = useThemeColors();
36
+
37
+ const map: Record<
38
+ string,
39
+ { fillColor: string; strokeColor: string; strokeWidth: number }
40
+ > = {
41
+ default: {
42
+ fillColor: colors.surfaceDefault,
43
+ strokeColor: colors.border,
44
+ strokeWidth: 1,
45
+ },
46
+ focused: {
47
+ fillColor: colors.surfaceDefault,
48
+ strokeColor: colors.brandPrimary,
49
+ strokeWidth: 1,
50
+ },
51
+ success: {
52
+ fillColor: colors.surfaceDefault,
53
+ strokeColor: colors.success,
54
+ strokeWidth: 1,
55
+ },
56
+ error: {
57
+ fillColor: colors.surfaceDefault,
58
+ strokeColor: colors.destructive,
59
+ strokeWidth: 1,
60
+ },
61
+ disabled: {
62
+ fillColor: colors.surfaceSubtle,
63
+ strokeColor: colors.border,
64
+ strokeWidth: 1,
65
+ },
66
+ loading: {
67
+ fillColor: colors.surfaceDefault,
68
+ strokeColor: colors.border,
69
+ strokeWidth: 1,
70
+ },
71
+ };
72
+
73
+ return map[state] ?? map.default;
74
+ }
75
+
26
76
  const textFieldVariants = cva(
27
- 'flex-row items-center border bg-surface-default px-3 py-4',
77
+ 'flex-row items-center justify-between self-stretch bg-transparent min-h-[54px] px-3',
28
78
  {
29
79
  variants: {
30
80
  state: {
31
- default: 'border-border-default-2 rounded-lg',
32
- focused: 'border-brand-primary ring-1 ring-brand-primary rounded-lg',
33
- success: 'border-highlight-success-green rounded-lg',
34
- error: 'border-highlight-alert-red rounded-lg',
35
- disabled:
36
- 'border-border-default-2 bg-surface-subtle rounded-lg opacity-50',
81
+ default: '',
82
+ focused: '',
83
+ success: '',
84
+ error: '',
85
+ disabled: 'opacity-60',
86
+ loading: '',
37
87
  },
38
88
  },
39
89
  defaultVariants: {
@@ -42,7 +92,7 @@ const textFieldVariants = cva(
42
92
  }
43
93
  );
44
94
 
45
- const fieldTextVariants = cva('text-sm', {
95
+ const fieldTextVariants = cva('text-xs font-medium', {
46
96
  variants: {
47
97
  state: {
48
98
  default: 'text-text-primary',
@@ -50,6 +100,7 @@ const fieldTextVariants = cva('text-sm', {
50
100
  success: 'text-highlight-success-green',
51
101
  error: 'text-highlight-alert-red',
52
102
  disabled: 'text-text-muted',
103
+ loading: 'text-text-secondary',
53
104
  },
54
105
  },
55
106
  defaultVariants: {
@@ -57,7 +108,7 @@ const fieldTextVariants = cva('text-sm', {
57
108
  },
58
109
  });
59
110
 
60
- const nativeTextFieldVariants = cva('flex-1 text-base', {
111
+ const nativeTextFieldVariants = cva('flex-1 text-sm font-medium', {
61
112
  variants: {
62
113
  state: {
63
114
  default: 'text-text-primary',
@@ -65,6 +116,7 @@ const nativeTextFieldVariants = cva('flex-1 text-base', {
65
116
  success: 'text-text-primary',
66
117
  error: 'text-text-primary',
67
118
  disabled: 'text-text-muted',
119
+ loading: 'text-text-secondary',
68
120
  },
69
121
  content: {
70
122
  placeholder: 'placeholder:text-text-muted',
@@ -85,6 +137,7 @@ const iconVariants = cva('', {
85
137
  success: 'text-highlight-success-green',
86
138
  error: 'text-highlight-alert-red',
87
139
  disabled: 'text-text-muted',
140
+ loading: 'text-text-primary',
88
141
  },
89
142
  },
90
143
  defaultVariants: {
@@ -107,10 +160,10 @@ type TextFieldProps = Omit<SlottableViewProps, 'className'> &
107
160
  asChild?: boolean;
108
161
 
109
162
  leftIcon?: LucideIcon;
110
- rightIcon?: LucideIcon;
163
+ rightIcon?: LucideIcon | React.ReactNode;
111
164
 
112
165
  // Type (custom, not from CVA)
113
- type?: 'text' | 'search' | 'ai' | 'composer';
166
+ type?: 'text' | 'search' | 'ai' | 'composer' | 'code';
114
167
 
115
168
  // Visibility
116
169
  showLabel?: boolean;
@@ -160,6 +213,7 @@ const TextField = React.forwardRef<ViewRef, TextFieldProps>(
160
213
  ) => {
161
214
  const Component = asChild ? Slot.View : View;
162
215
  const isDisabled = state === 'disabled';
216
+ const squircleParams = useStateSquircleParams(state ?? 'default');
163
217
 
164
218
  const valueLike =
165
219
  inputProps?.value ??
@@ -180,7 +234,9 @@ const TextField = React.forwardRef<ViewRef, TextFieldProps>(
180
234
  ? errorMessage || 'This field has an error'
181
235
  : state === 'success'
182
236
  ? successMessage || 'This field is valid'
183
- : helpText || 'Helper text';
237
+ : state === 'loading'
238
+ ? 'Loading...'
239
+ : helpText || 'Helper text';
184
240
 
185
241
  const getPlaceholder = () => {
186
242
  if (resolvedContent !== 'placeholder') {
@@ -194,12 +250,77 @@ const TextField = React.forwardRef<ViewRef, TextFieldProps>(
194
250
  return 'Ask AI';
195
251
  case 'composer':
196
252
  return 'Type here';
253
+ case 'code':
254
+ return 'Paste your code here';
197
255
  case 'text':
198
256
  default:
199
257
  return inputProps?.placeholder ?? 'Placeholder text';
200
258
  }
201
259
  };
202
260
 
261
+ // Handle code type (OTP-style boxes)
262
+ if (type === 'code') {
263
+ const codeLength = 6;
264
+ const codeValue = (inputProps?.value as string) || '';
265
+ const codeBoxes = Array.from({ length: codeLength }).map(
266
+ (_, i) => codeValue[i] || ''
267
+ );
268
+
269
+ return (
270
+ <TextClassContext.Provider value={fieldTextVariants({ state })}>
271
+ <StyledView className="gap-2">
272
+ {showLabel && (
273
+ <StyledView>
274
+ <Text>{label || 'Label'}</Text>
275
+ </StyledView>
276
+ )}
277
+
278
+ <StyledView className="flex-row gap-2 justify-between">
279
+ {codeBoxes.map((char, index) => (
280
+ <StyledView
281
+ key={index}
282
+ className={cn(
283
+ 'flex-1 items-center justify-center border rounded-md px-3 py-4',
284
+ index === codeValue.length
285
+ ? 'border-brand-primary ring-1 ring-brand-primary'
286
+ : state === 'error'
287
+ ? 'border-highlight-alert-red'
288
+ : 'border-border-default',
289
+ 'bg-surface-default'
290
+ )}
291
+ >
292
+ <Text
293
+ className={cn(
294
+ 'text-sm font-medium',
295
+ state === 'error'
296
+ ? 'text-highlight-alert-red'
297
+ : 'text-text-primary'
298
+ )}
299
+ >
300
+ {char || 'X'}
301
+ </Text>
302
+ </StyledView>
303
+ ))}
304
+ </StyledView>
305
+
306
+ {/* Hidden input for actual text input */}
307
+ <StyledTextInput
308
+ {...inputProps}
309
+ maxLength={codeLength}
310
+ editable={!isDisabled}
311
+ style={{ position: 'absolute', opacity: 0, width: 0, height: 0 }}
312
+ />
313
+
314
+ {showHelpText && (
315
+ <StyledView>
316
+ <Text>{message}</Text>
317
+ </StyledView>
318
+ )}
319
+ </StyledView>
320
+ </TextClassContext.Provider>
321
+ );
322
+ }
323
+
203
324
  return (
204
325
  <TextClassContext.Provider value={fieldTextVariants({ state })}>
205
326
  <StyledView className="gap-1">
@@ -209,42 +330,68 @@ const TextField = React.forwardRef<ViewRef, TextFieldProps>(
209
330
  </StyledView>
210
331
  )}
211
332
 
212
- <Component
213
- ref={ref as any}
214
- {...containerProps}
215
- className={cn(textFieldVariants({ state }))}
216
- >
217
- {showLeftIcon && LeftIcon && (
218
- <LeftIcon
219
- size={16}
220
- className={cn('mr-2 mt-1', iconVariants({ state }))}
221
- />
222
- )}
223
-
224
- <StyledTextInput
225
- {...inputProps}
226
- multiline
227
- editable={!isDisabled}
228
- placeholder={getPlaceholder()}
229
- className={cn(
230
- nativeTextFieldVariants({ state, content: resolvedContent }),
231
- isDisabled && 'opacity-100'
232
- )}
233
- style={[
234
- {
235
- textAlignVertical: 'top',
236
- },
237
- inputProps?.style,
238
- ]}
239
- />
240
-
241
- {showRightIcon && RightIcon && (
242
- <RightIcon
243
- size={16}
244
- className={cn('ml-2 mt-1', iconVariants({ state }))}
245
- />
246
- )}
247
- </Component>
333
+ <View style={{ overflow: 'hidden', borderRadius: CORNER_RADIUS }}>
334
+ <SquircleView
335
+ squircleParams={{
336
+ cornerRadius: CORNER_RADIUS,
337
+ cornerSmoothing: CORNER_SMOOTHING,
338
+ ...squircleParams,
339
+ }}
340
+ >
341
+ <Component
342
+ ref={ref as any}
343
+ {...containerProps}
344
+ className={cn(textFieldVariants({ state }))}
345
+ >
346
+ {showLeftIcon && LeftIcon && (
347
+ <LeftIcon
348
+ size={16}
349
+ className={cn('mr-2 mt-1', iconVariants({ state }))}
350
+ />
351
+ )}
352
+
353
+ <StyledTextInput
354
+ {...inputProps}
355
+ multiline
356
+ editable={!isDisabled}
357
+ placeholder={getPlaceholder()}
358
+ className={cn(
359
+ nativeTextFieldVariants({
360
+ state,
361
+ content: resolvedContent,
362
+ }),
363
+ isDisabled && 'opacity-100'
364
+ )}
365
+ style={[
366
+ {
367
+ textAlignVertical: 'top',
368
+ },
369
+ inputProps?.style,
370
+ ]}
371
+ />
372
+
373
+ {state === 'loading' ? (
374
+ <LottieView
375
+ source={Lotties.loading}
376
+ autoPlay
377
+ loop
378
+ style={{ width: 48, height: 48, marginLeft: 8 }}
379
+ />
380
+ ) : showRightIcon && RightIcon ? (
381
+ typeof RightIcon === 'function' ? (
382
+ <RightIcon
383
+ size={16}
384
+ className={cn('ml-2 mt-1', iconVariants({ state }))}
385
+ />
386
+ ) : (
387
+ <StyledView className={cn('ml-2 mt-1')}>
388
+ {RightIcon}
389
+ </StyledView>
390
+ )
391
+ ) : null}
392
+ </Component>
393
+ </SquircleView>
394
+ </View>
248
395
 
249
396
  {showHelpText && (
250
397
  <StyledView>
@@ -0,0 +1,37 @@
1
+ import figma from '@figma/code-connect';
2
+ import { ToggleItem } from './Toggle';
3
+
4
+ const FIGMA_URL =
5
+ 'https://www.figma.com/design/66WaqopqU3WXgwVtyQuTUf/React-Native-Blueprint-Library?node-id=408-2632';
6
+
7
+ figma.connect(ToggleItem, FIGMA_URL, {
8
+ props: {
9
+ // State (whether toggle is on/off)
10
+ state: figma.boolean('state'),
11
+
12
+ // Tone (color variant)
13
+ tone: figma.enum('tone', {
14
+ default: 'default',
15
+ success: 'success',
16
+ destructive: 'destructive',
17
+ warning: 'warning',
18
+ }),
19
+
20
+ // Text content
21
+ text: figma.string('Text'),
22
+
23
+ // Icon visibility
24
+ showLeftIcon: figma.boolean('Show leftIcon'),
25
+ showRightIcon: figma.boolean('Show rightIcon'),
26
+ },
27
+
28
+ example: (props) => (
29
+ <ToggleItem
30
+ state={props.state}
31
+ tone={props.tone}
32
+ text={props.text}
33
+ showLeftIcon={props.showLeftIcon}
34
+ showRightIcon={props.showRightIcon}
35
+ />
36
+ ),
37
+ });