@praxiis/ui 0.0.1

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 (187) hide show
  1. package/dist/index.d.mts +52556 -0
  2. package/dist/index.d.ts +52556 -0
  3. package/dist/index.js +8753 -0
  4. package/dist/index.mjs +8777 -0
  5. package/package.json +70 -0
  6. package/src/__test-utils__/index.tsx +39 -0
  7. package/src/components/CalendarStrip/CalendarStrip.helpers.ts +106 -0
  8. package/src/components/CalendarStrip/CalendarStrip.tsx +83 -0
  9. package/src/components/CalendarStrip/CalendarStrip.types.ts +133 -0
  10. package/src/components/CalendarStrip/DayCard/DayCard.helpers.ts +44 -0
  11. package/src/components/CalendarStrip/DayCard/DayCard.tsx +71 -0
  12. package/src/components/CalendarStrip/DayCard/DayCard.types.ts +134 -0
  13. package/src/components/CalendarStrip/DayCard/index.ts +2 -0
  14. package/src/components/CalendarStrip/DayCard/useDayCardLogic.ts +45 -0
  15. package/src/components/CalendarStrip/index.ts +9 -0
  16. package/src/components/CalendarStrip/useCalendarStripLogic.ts +53 -0
  17. package/src/components/EmptyState/EmptyState.helpers.ts +104 -0
  18. package/src/components/EmptyState/EmptyState.tsx +205 -0
  19. package/src/components/EmptyState/EmptyState.types.ts +213 -0
  20. package/src/components/EmptyState/index.ts +44 -0
  21. package/src/components/EmptyState/useEmptyStateLogic.ts +131 -0
  22. package/src/components/Header/Header.helpers.ts +93 -0
  23. package/src/components/Header/Header.tsx +185 -0
  24. package/src/components/Header/Header.types.ts +153 -0
  25. package/src/components/Header/index.ts +44 -0
  26. package/src/components/Header/useHeaderLogic.ts +146 -0
  27. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.helpers.ts +50 -0
  28. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.tsx +78 -0
  29. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.types.ts +99 -0
  30. package/src/components/ScheduleItem/ScheduleItem/index.ts +16 -0
  31. package/src/components/ScheduleItem/ScheduleItem/useScheduleItemLogic.ts +31 -0
  32. package/src/components/ScheduleItem/index.ts +15 -0
  33. package/src/components/index.ts +40 -0
  34. package/src/core/index.ts +34 -0
  35. package/src/core/restyle/RestyleThemeProviderWrapper.tsx +31 -0
  36. package/src/core/restyle/index.ts +38 -0
  37. package/src/core/restyle/restylePresetRegistry.ts +195 -0
  38. package/src/core/restyle/restyleTheme.ts +1352 -0
  39. package/src/core/restyle/restyleTypes.ts +8 -0
  40. package/src/core/restyle/useRestyleTheme.ts +10 -0
  41. package/src/hooks/animations/index.ts +3 -0
  42. package/src/hooks/animations/useAnimatedValue.ts +10 -0
  43. package/src/hooks/animations/useEntranceAnimation.ts +106 -0
  44. package/src/hooks/animations/usePulseAnimation.ts +63 -0
  45. package/src/hooks/index.ts +30 -0
  46. package/src/hooks/useReducedMotion.ts +60 -0
  47. package/src/i18n/index.ts +2 -0
  48. package/src/i18n/labels/en.ts +120 -0
  49. package/src/i18n/labels/es.ts +120 -0
  50. package/src/i18n/labels/index.ts +6 -0
  51. package/src/i18n/labels/types.ts +165 -0
  52. package/src/index.tsx +215 -0
  53. package/src/primitives/actions/Button/Button.helpers.ts +243 -0
  54. package/src/primitives/actions/Button/Button.tsx +198 -0
  55. package/src/primitives/actions/Button/Button.types.ts +207 -0
  56. package/src/primitives/actions/Button/index.ts +41 -0
  57. package/src/primitives/actions/Button/useButtonLogic.ts +160 -0
  58. package/src/primitives/actions/IconButton/IconButton.helpers.ts +235 -0
  59. package/src/primitives/actions/IconButton/IconButton.tsx +177 -0
  60. package/src/primitives/actions/IconButton/IconButton.types.ts +273 -0
  61. package/src/primitives/actions/IconButton/index.ts +30 -0
  62. package/src/primitives/actions/IconButton/useIconButtonLogic.ts +172 -0
  63. package/src/primitives/actions/index.ts +20 -0
  64. package/src/primitives/content/Avatar/Avatar.helpers.ts +177 -0
  65. package/src/primitives/content/Avatar/Avatar.tsx +199 -0
  66. package/src/primitives/content/Avatar/Avatar.types.ts +222 -0
  67. package/src/primitives/content/Avatar/index.ts +46 -0
  68. package/src/primitives/content/Avatar/useAvatarLogic.ts +149 -0
  69. package/src/primitives/content/Badge/Badge.helpers.ts +175 -0
  70. package/src/primitives/content/Badge/Badge.tsx +174 -0
  71. package/src/primitives/content/Badge/Badge.types.ts +223 -0
  72. package/src/primitives/content/Badge/index.ts +40 -0
  73. package/src/primitives/content/Badge/useBadgeLogic.ts +128 -0
  74. package/src/primitives/content/Card/Card.helpers.ts +27 -0
  75. package/src/primitives/content/Card/Card.tsx +123 -0
  76. package/src/primitives/content/Card/Card.types.ts +95 -0
  77. package/src/primitives/content/Card/index.ts +20 -0
  78. package/src/primitives/content/Card/useCardLogic.ts +48 -0
  79. package/src/primitives/content/Chip/Chip.helpers.ts +304 -0
  80. package/src/primitives/content/Chip/Chip.tsx +205 -0
  81. package/src/primitives/content/Chip/Chip.types.ts +234 -0
  82. package/src/primitives/content/Chip/index.ts +47 -0
  83. package/src/primitives/content/Chip/useChipLogic.ts +167 -0
  84. package/src/primitives/content/Icon/Icon.helpers.ts +54 -0
  85. package/src/primitives/content/Icon/Icon.tsx +110 -0
  86. package/src/primitives/content/Icon/Icon.types.ts +95 -0
  87. package/src/primitives/content/Icon/index.ts +20 -0
  88. package/src/primitives/content/Icon/useIconLogic.ts +73 -0
  89. package/src/primitives/content/index.ts +45 -0
  90. package/src/primitives/feedback/ProgressBar/ProgressBar.helpers.ts +122 -0
  91. package/src/primitives/feedback/ProgressBar/ProgressBar.tsx +154 -0
  92. package/src/primitives/feedback/ProgressBar/ProgressBar.types.ts +178 -0
  93. package/src/primitives/feedback/ProgressBar/index.ts +17 -0
  94. package/src/primitives/feedback/ProgressBar/useProgressBarLogic.ts +120 -0
  95. package/src/primitives/feedback/Skeleton/Skeleton.helpers.ts +145 -0
  96. package/src/primitives/feedback/Skeleton/Skeleton.tsx +155 -0
  97. package/src/primitives/feedback/Skeleton/Skeleton.types.ts +223 -0
  98. package/src/primitives/feedback/Skeleton/index.ts +44 -0
  99. package/src/primitives/feedback/Skeleton/useSkeletonLogic.ts +125 -0
  100. package/src/primitives/feedback/Spinner/Spinner.helpers.ts +40 -0
  101. package/src/primitives/feedback/Spinner/Spinner.tsx +105 -0
  102. package/src/primitives/feedback/Spinner/Spinner.types.ts +114 -0
  103. package/src/primitives/feedback/Spinner/index.ts +18 -0
  104. package/src/primitives/feedback/Spinner/useSpinnerLogic.ts +84 -0
  105. package/src/primitives/feedback/Toast/Toast.helpers.ts +163 -0
  106. package/src/primitives/feedback/Toast/Toast.tsx +190 -0
  107. package/src/primitives/feedback/Toast/Toast.types.ts +270 -0
  108. package/src/primitives/feedback/Toast/ToastContext.tsx +96 -0
  109. package/src/primitives/feedback/Toast/ToastProvider.tsx +241 -0
  110. package/src/primitives/feedback/Toast/index.ts +59 -0
  111. package/src/primitives/feedback/Toast/useToastLogic.ts +112 -0
  112. package/src/primitives/feedback/index.ts +45 -0
  113. package/src/primitives/index.ts +158 -0
  114. package/src/primitives/inputs/Checkbox/Checkbox.helpers.ts +132 -0
  115. package/src/primitives/inputs/Checkbox/Checkbox.tsx +150 -0
  116. package/src/primitives/inputs/Checkbox/Checkbox.types.ts +106 -0
  117. package/src/primitives/inputs/Checkbox/index.ts +30 -0
  118. package/src/primitives/inputs/Checkbox/useCheckboxLogic.ts +121 -0
  119. package/src/primitives/inputs/RadioButton/RadioButton.helpers.ts +123 -0
  120. package/src/primitives/inputs/RadioButton/RadioButton.tsx +159 -0
  121. package/src/primitives/inputs/RadioButton/RadioButton.types.ts +106 -0
  122. package/src/primitives/inputs/RadioButton/index.ts +25 -0
  123. package/src/primitives/inputs/RadioButton/useRadioButtonLogic.ts +117 -0
  124. package/src/primitives/inputs/SegmentedControl/SegmentedControl.helpers.ts +174 -0
  125. package/src/primitives/inputs/SegmentedControl/SegmentedControl.tsx +224 -0
  126. package/src/primitives/inputs/SegmentedControl/SegmentedControl.types.ts +187 -0
  127. package/src/primitives/inputs/SegmentedControl/index.ts +39 -0
  128. package/src/primitives/inputs/SegmentedControl/useSegmentedControlLogic.ts +151 -0
  129. package/src/primitives/inputs/SelectSheet/SelectSheet.helpers.ts +147 -0
  130. package/src/primitives/inputs/SelectSheet/SelectSheet.tsx +247 -0
  131. package/src/primitives/inputs/SelectSheet/SelectSheet.types.ts +196 -0
  132. package/src/primitives/inputs/SelectSheet/SelectSheetOption.tsx +177 -0
  133. package/src/primitives/inputs/SelectSheet/index.ts +48 -0
  134. package/src/primitives/inputs/SelectSheet/useSelectSheetLogic.ts +309 -0
  135. package/src/primitives/inputs/Switch/Switch.helpers.ts +109 -0
  136. package/src/primitives/inputs/Switch/Switch.tsx +191 -0
  137. package/src/primitives/inputs/Switch/Switch.types.ts +154 -0
  138. package/src/primitives/inputs/Switch/index.ts +40 -0
  139. package/src/primitives/inputs/Switch/useSwitchLogic.ts +192 -0
  140. package/src/primitives/inputs/TextInput/TextInput.helpers.ts +206 -0
  141. package/src/primitives/inputs/TextInput/TextInput.tsx +392 -0
  142. package/src/primitives/inputs/TextInput/TextInput.types.ts +216 -0
  143. package/src/primitives/inputs/TextInput/index.ts +37 -0
  144. package/src/primitives/inputs/TextInput/useTextInputLogic.ts +195 -0
  145. package/src/primitives/inputs/index.ts +52 -0
  146. package/src/primitives/layout/AnimatedBox.tsx +44 -0
  147. package/src/primitives/layout/Box.tsx +71 -0
  148. package/src/primitives/layout/Divider/Divider.helpers.ts +115 -0
  149. package/src/primitives/layout/Divider/Divider.tsx +139 -0
  150. package/src/primitives/layout/Divider/Divider.types.ts +178 -0
  151. package/src/primitives/layout/Divider/index.ts +24 -0
  152. package/src/primitives/layout/Divider/useDividerLogic.ts +109 -0
  153. package/src/primitives/layout/FlatList.tsx +66 -0
  154. package/src/primitives/layout/Pressable.tsx +74 -0
  155. package/src/primitives/layout/ScrollView.tsx +63 -0
  156. package/src/primitives/layout/Stack.tsx +69 -0
  157. package/src/primitives/layout/index.ts +40 -0
  158. package/src/primitives/navigation/index.ts +6 -0
  159. package/src/primitives/overlays/Modal/Modal.helpers.ts +31 -0
  160. package/src/primitives/overlays/Modal/Modal.tsx +264 -0
  161. package/src/primitives/overlays/Modal/Modal.types.ts +193 -0
  162. package/src/primitives/overlays/Modal/index.ts +43 -0
  163. package/src/primitives/overlays/Modal/useModalLogic.ts +103 -0
  164. package/src/primitives/overlays/index.ts +12 -0
  165. package/src/primitives/typography/Text.tsx +51 -0
  166. package/src/primitives/typography/index.ts +1 -0
  167. package/src/provider/DesignSystemContext.ts +22 -0
  168. package/src/provider/DesignSystemProvider.tsx +121 -0
  169. package/src/provider/index.ts +7 -0
  170. package/src/providers/ThemeProvider/createTheme.ts +304 -0
  171. package/src/providers/ThemeProvider/defaultTheme.ts +70 -0
  172. package/src/providers/ThemeProvider/index.ts +34 -0
  173. package/src/providers/ThemeProvider/types.ts +249 -0
  174. package/src/providers/index.ts +29 -0
  175. package/src/tokens/colors.ts +371 -0
  176. package/src/tokens/index.ts +145 -0
  177. package/src/tokens/motion.ts +176 -0
  178. package/src/tokens/radii.ts +82 -0
  179. package/src/tokens/scales.ts +588 -0
  180. package/src/tokens/shadows.ts +190 -0
  181. package/src/tokens/spacing.ts +140 -0
  182. package/src/tokens/tokens.json +207 -0
  183. package/src/tokens/typography.ts +251 -0
  184. package/src/types.ts +50 -0
  185. package/src/utils/accessibility.ts +169 -0
  186. package/src/utils/index.ts +25 -0
  187. package/src/utils/platform.ts +72 -0
@@ -0,0 +1,206 @@
1
+ /**
2
+ * TextInput Component Helpers
3
+ *
4
+ * Pure functions for computing TextInput sizes, styles, and mappings.
5
+ *
6
+ * ## Size Configuration
7
+ * | Size | Height | Font | Icon | Padding |
8
+ * |------|--------|------|------|---------|
9
+ * | sm | 40px | 14px | 18px | 12px |
10
+ * | md | 48px | 16px | 20px | 16px |
11
+ * | lg | 56px | 17px | 24px | 16px |
12
+ *
13
+ * ## Text Variant Mapping
14
+ * | Input Size | Text Variant | Label Variant |
15
+ * |------------|--------------|---------------|
16
+ * | sm | bodySmall | labelSmall |
17
+ * | md | bodyMedium | labelSmall |
18
+ * | lg | bodyLarge | labelMedium |
19
+ */
20
+
21
+ import { IconSize } from "../../content/Icon";
22
+ import { BaseThemeColor, RestyleColor } from "../../../types";
23
+ import {
24
+ ResolveTextInputStyleParams,
25
+ TextInputSize,
26
+ TextInputSizeConfig,
27
+ TextInputStyleResult,
28
+ TextInputTextVariant,
29
+ } from "./TextInput.types";
30
+
31
+ /**
32
+ * TextInput size configuration mapping.
33
+ *
34
+ * Height follows a consistent scale:
35
+ * - sm: 40px - Compact forms
36
+ * - md: 48px - Default mobile size (meets 44px touch target)
37
+ * - lg: 56px - Tablets, emphasized inputs
38
+ */
39
+ const TEXT_INPUT_SIZE_CONFIG: Record<TextInputSize, TextInputSizeConfig> = {
40
+ sm: {
41
+ height: 40,
42
+ paddingHorizontal: 12,
43
+ fontSize: 14,
44
+ iconSize: 18,
45
+ },
46
+ md: {
47
+ height: 48,
48
+ paddingHorizontal: 16,
49
+ fontSize: 16,
50
+ iconSize: 20,
51
+ },
52
+ lg: {
53
+ height: 56,
54
+ paddingHorizontal: 16,
55
+ fontSize: 17,
56
+ iconSize: 24,
57
+ },
58
+ } as const;
59
+
60
+ /**
61
+ * Text variant mapping for input text.
62
+ *
63
+ * - sm input → bodySmall text
64
+ * - md input → bodyMedium text
65
+ * - lg input → bodyLarge text
66
+ */
67
+ const TEXT_INPUT_TEXT_VARIANTS: Record<TextInputSize, TextInputTextVariant> = {
68
+ sm: "bodySmall",
69
+ md: "bodyMedium",
70
+ lg: "bodyLarge",
71
+ } as const;
72
+
73
+ /**
74
+ * Text variant mapping for labels.
75
+ *
76
+ * - sm input → labelSmall label
77
+ * - md input → labelSmall label
78
+ * - lg input → labelMedium label
79
+ */
80
+ const TEXT_INPUT_LABEL_VARIANTS: Record<TextInputSize, TextInputTextVariant> = {
81
+ sm: "labelSmall",
82
+ md: "labelSmall",
83
+ lg: "labelMedium",
84
+ } as const;
85
+
86
+ /**
87
+ * Icon size mapping - derives icon size from input size.
88
+ * Icons should match text size for visual balance.
89
+ *
90
+ * - sm input → sm icon (16px)
91
+ * - md input → md icon (20px)
92
+ * - lg input → lg icon (24px)
93
+ */
94
+ const SIZE_TO_ICON_SIZE: Record<TextInputSize, IconSize> = {
95
+ sm: "sm",
96
+ md: "md",
97
+ lg: "lg",
98
+ } as const;
99
+
100
+ /**
101
+ * Get size configuration based on input size.
102
+ *
103
+ * @param size - The input size variant
104
+ * @returns Size configuration object
105
+ */
106
+ export function getTextInputSizeConfig(size: TextInputSize): TextInputSizeConfig {
107
+ return TEXT_INPUT_SIZE_CONFIG[size] || TEXT_INPUT_SIZE_CONFIG.md;
108
+ }
109
+
110
+ /**
111
+ * Get text variant for input text based on size.
112
+ *
113
+ * @param size - The input size variant
114
+ * @returns Text variant key
115
+ */
116
+ export function getTextInputTextVariant(size: TextInputSize): TextInputTextVariant {
117
+ return TEXT_INPUT_TEXT_VARIANTS[size] || TEXT_INPUT_TEXT_VARIANTS.md;
118
+ }
119
+
120
+ /**
121
+ * Get text variant for label based on size.
122
+ *
123
+ * @param size - The input size variant
124
+ * @returns Text variant key for label
125
+ */
126
+ export function getTextInputLabelVariant(size: TextInputSize): TextInputTextVariant {
127
+ return TEXT_INPUT_LABEL_VARIANTS[size] || TEXT_INPUT_LABEL_VARIANTS.md;
128
+ }
129
+
130
+ /**
131
+ * Get icon size based on input size.
132
+ *
133
+ * @param size - The input size variant
134
+ * @returns Icon size key
135
+ */
136
+ export function getTextInputIconSize(size: TextInputSize): IconSize {
137
+ return SIZE_TO_ICON_SIZE[size];
138
+ }
139
+
140
+ /**
141
+ * Get icon color based on error and focus states.
142
+ *
143
+ * @param hasError - Whether the input has an error
144
+ * @param isFocused - Whether the input is focused
145
+ * @param color - The primary color
146
+ * @returns Icon color key
147
+ */
148
+ export function getTextInputIconColor(
149
+ hasError: boolean,
150
+ isFocused: boolean,
151
+ color: RestyleColor
152
+ ): RestyleColor {
153
+ if (hasError) {
154
+ return "feedbackError";
155
+ }
156
+ if (isFocused) {
157
+ return color;
158
+ }
159
+ return "textTertiary";
160
+ }
161
+
162
+ /**
163
+ * Resolve dynamic style properties for the input container.
164
+ *
165
+ * @param params - Style resolution parameters
166
+ * @returns Computed styles for the input container
167
+ */
168
+ export function resolveTextInputStyle(
169
+ params: ResolveTextInputStyleParams
170
+ ): TextInputStyleResult {
171
+ const { variant, disabled, error, isFocused, color, theme } = params;
172
+
173
+ const hasError = !!error;
174
+
175
+ // Background color
176
+ let backgroundColor: string;
177
+ if (disabled) {
178
+ backgroundColor = theme.colors.interactiveDisabled;
179
+ } else if (variant === "filled") {
180
+ backgroundColor = theme.colors.backgroundSecondary;
181
+ } else {
182
+ backgroundColor = theme.colors.surfacePrimary;
183
+ }
184
+
185
+ // Border color
186
+ let borderColor: string;
187
+ if (disabled) {
188
+ borderColor = theme.colors.borderDefault;
189
+ } else if (hasError) {
190
+ borderColor = theme.colors.feedbackError;
191
+ } else if (isFocused) {
192
+ borderColor = theme.colors[color as BaseThemeColor];
193
+ } else {
194
+ borderColor = theme.colors.borderDefault;
195
+ }
196
+
197
+ // Border width
198
+ const borderWidth = isFocused ? 2 : 1;
199
+
200
+ return {
201
+ backgroundColor,
202
+ borderColor,
203
+ borderWidth,
204
+ opacity: 1,
205
+ };
206
+ }
@@ -0,0 +1,392 @@
1
+ /**
2
+ * TextInput Component
3
+ *
4
+ * @description Text entry field with labels and validation - Molecule
5
+ *
6
+ * TextInput combines multiple atoms (Icon, Text, Box) into a
7
+ * complete form field with label, input, helper text, and error states.
8
+ *
9
+ * ## Size Scale
10
+ * | Size | Height | Font | Icon | Padding |
11
+ * |------|--------|------|------|---------|
12
+ * | sm | 40px | 14px | 18px | 12px |
13
+ * | md | 48px | 16px | 20px | 16px |
14
+ * | lg | 56px | 17px | 24px | 16px |
15
+ *
16
+ * ## Variants
17
+ * - `outlined`: Border with transparent/white background (default)
18
+ * - `filled`: Filled background with subtle border
19
+ * - `textarea`: Multi-line with configurable height
20
+ *
21
+ * ## Visual States
22
+ * - Default: Gray border
23
+ * - Focused: Colored border (2px), colored label
24
+ * - Error: Red border, red label, error message
25
+ * - Disabled: Reduced opacity (0.5)
26
+ *
27
+ * ## Features
28
+ * - Responsive size prop (phone/tablet breakpoints)
29
+ * - Optional left and right icons
30
+ * - Label with required indicator
31
+ * - Helper text and error states
32
+ * - Character count display
33
+ * - Full accessibility support
34
+ * - Textarea with cursor at top
35
+ *
36
+ * @performance
37
+ * - Wrapped with React.memo() to prevent unnecessary re-renders
38
+ * - Uses useMemo() for expensive calculations (style resolution, state-based colors)
39
+ * - Border width and color calculations cached per state change
40
+ * - Icon sizing optimized with lookup tables for instant resolution
41
+ *
42
+ * @see TextInput.types.ts - Type definitions
43
+ * @see TextInput.helpers.ts - Size and style functions
44
+ * @see TextInput.a11y.ts - Accessibility prop generation
45
+ *
46
+ * @example
47
+ * // Basic usage
48
+ * <TextInput label="Email" value={email} onChangeText={setEmail} />
49
+ *
50
+ * @example
51
+ * // With icons and validation
52
+ * <TextInput
53
+ * label="Email"
54
+ * value={email}
55
+ * onChangeText={setEmail}
56
+ * leftIconName="email"
57
+ * rightIconName={isValid ? "check-circle" : undefined}
58
+ * error={emailError}
59
+ * helperText="Enter your email address"
60
+ * required
61
+ * />
62
+ *
63
+ * @example
64
+ * // Textarea variant
65
+ * <TextInput
66
+ * label="Description"
67
+ * variant="textarea"
68
+ * textareaHeight={120}
69
+ * value={description}
70
+ * onChangeText={setDescription}
71
+ * />
72
+ */
73
+
74
+ import {
75
+ backgroundColor,
76
+ border,
77
+ createRestyleComponent,
78
+ layout,
79
+ spacing,
80
+ spacingShorthand,
81
+ useResponsiveProp,
82
+ } from "@shopify/restyle";
83
+ import React, { forwardRef, memo, useCallback, useMemo, useState } from "react";
84
+ import {
85
+ NativeSyntheticEvent,
86
+ TextInput as RNTextInput,
87
+ TargetedEvent,
88
+ StyleSheet,
89
+ } from "react-native";
90
+ import { RestyleTheme, useRestyleTheme } from "../../../core/restyle";
91
+ import { BaseThemeColor } from "../../../types";
92
+ import { Icon } from "../../content/Icon";
93
+ import { Box } from "../../layout";
94
+ import { Text } from "../../typography";
95
+ import {
96
+ getTextInputLabelVariant,
97
+ getTextInputSizeConfig,
98
+ } from "./TextInput.helpers";
99
+ import { BaseTextInputContainerProps, TextInputProps, TextInputSize } from "./TextInput.types";
100
+ import { useTextInputLogic } from "./useTextInputLogic";
101
+
102
+ /**
103
+ * Base container for the input field with Restyle support.
104
+ * @internal
105
+ */
106
+ const BaseTextInputContainer = createRestyleComponent<
107
+ BaseTextInputContainerProps,
108
+ RestyleTheme
109
+ >([backgroundColor, border, layout, spacing, spacingShorthand], Box);
110
+
111
+ const TextInputComponent = forwardRef<RNTextInput, TextInputProps>(
112
+ function TextInputComponent({
113
+ label,
114
+ helperText,
115
+ error,
116
+ size = "md",
117
+ variant = "outlined",
118
+ leftIconName,
119
+ rightIconName,
120
+ disabled = false,
121
+ required = false,
122
+ showCharCount = false,
123
+ maxLength,
124
+ value = "",
125
+ color = "accentPrimary",
126
+ onFocus,
127
+ onBlur,
128
+ containerStyle,
129
+ inputContainerStyle,
130
+ testID,
131
+ accessibilityLabel,
132
+ accessibilityHint,
133
+ textareaHeight = 120,
134
+ // Extract BoxProps for outer container
135
+ margin,
136
+ marginTop,
137
+ marginBottom,
138
+ marginLeft,
139
+ marginRight,
140
+ marginHorizontal,
141
+ marginVertical,
142
+ padding,
143
+ paddingTop,
144
+ paddingBottom,
145
+ paddingLeft,
146
+ paddingRight,
147
+ paddingHorizontal,
148
+ paddingVertical,
149
+ flex,
150
+ flexGrow,
151
+ flexShrink,
152
+ width,
153
+ minWidth,
154
+ maxWidth,
155
+ ...rest
156
+ }: TextInputProps, ref) {
157
+ const theme = useRestyleTheme();
158
+ const [isFocused, setIsFocused] = useState(false);
159
+
160
+ const {
161
+ isDisabled,
162
+ hasError,
163
+ opacity,
164
+ finalBackgroundColor,
165
+ finalBorderColor,
166
+ finalTextColor,
167
+ finalPlaceholderColor,
168
+ finalLabelColor,
169
+ borderWidth,
170
+ leftIconSize,
171
+ rightIconSize,
172
+ iconColor,
173
+ a11yProps,
174
+ } = useTextInputLogic({
175
+ variant,
176
+ size,
177
+ color,
178
+ disabled,
179
+ error,
180
+ isFocused,
181
+ leftIconName,
182
+ rightIconName,
183
+ label,
184
+ accessibilityLabel,
185
+ accessibilityHint,
186
+ required,
187
+ });
188
+
189
+ // Resolve responsive size value
190
+ const resolvedSize = (useResponsiveProp(size) ?? "md") as TextInputSize;
191
+
192
+ // Get size configuration for dimensions
193
+ const sizeConfig = getTextInputSizeConfig(resolvedSize);
194
+ const labelVariant = getTextInputLabelVariant(resolvedSize);
195
+
196
+ const handleFocus = useCallback(
197
+ (e: NativeSyntheticEvent<TargetedEvent>) => {
198
+ setIsFocused(true);
199
+ onFocus?.(e);
200
+ },
201
+ [onFocus],
202
+ );
203
+
204
+ const handleBlur = useCallback(
205
+ (e: NativeSyntheticEvent<TargetedEvent>) => {
206
+ setIsFocused(false);
207
+ onBlur?.(e);
208
+ },
209
+ [onBlur],
210
+ );
211
+
212
+ const charCount = typeof value === "string" ? value.length : 0;
213
+ const isTextarea = variant === "textarea";
214
+
215
+ const backgroundColorStyle = finalBackgroundColor === "transparent"
216
+ ? "transparent"
217
+ : theme.colors[finalBackgroundColor as BaseThemeColor];
218
+
219
+ const borderColorStyle = theme.colors[finalBorderColor as BaseThemeColor];
220
+
221
+ const textColorStyle = theme.colors[finalTextColor as BaseThemeColor];
222
+
223
+ const placeholderColorStyle = theme.colors[finalPlaceholderColor as BaseThemeColor];
224
+
225
+ // Calculate container height for textarea
226
+ const containerHeight = isTextarea ? textareaHeight : sizeConfig.height;
227
+
228
+ // Calculate padding for textarea (needs vertical padding)
229
+ const containerPadding = useMemo(() => {
230
+ if (isTextarea) {
231
+ return {
232
+ paddingHorizontal: sizeConfig.paddingHorizontal,
233
+ paddingVertical: theme.spacing.sm,
234
+ };
235
+ }
236
+ return {
237
+ paddingHorizontal: sizeConfig.paddingHorizontal,
238
+ paddingVertical: 0,
239
+ };
240
+ }, [isTextarea, sizeConfig.paddingHorizontal, theme.spacing.sm]);
241
+
242
+ return (
243
+ <Box
244
+ style={containerStyle}
245
+ testID={testID}
246
+ opacity={opacity}
247
+ margin={margin}
248
+ marginTop={marginTop}
249
+ marginBottom={marginBottom}
250
+ marginLeft={marginLeft}
251
+ marginRight={marginRight}
252
+ marginHorizontal={marginHorizontal}
253
+ marginVertical={marginVertical}
254
+ padding={padding}
255
+ paddingTop={paddingTop}
256
+ paddingBottom={paddingBottom}
257
+ paddingLeft={paddingLeft}
258
+ paddingRight={paddingRight}
259
+ paddingHorizontal={paddingHorizontal}
260
+ paddingVertical={paddingVertical}
261
+ flex={flex}
262
+ flexGrow={flexGrow}
263
+ flexShrink={flexShrink}
264
+ width={width}
265
+ minWidth={minWidth}
266
+ maxWidth={maxWidth}
267
+ >
268
+ {label && (
269
+ <Box marginBottom="xs" flexDirection="row" alignItems="center">
270
+ <Text variant={labelVariant} color={finalLabelColor as BaseThemeColor}>
271
+ {label}
272
+ {required && (
273
+ <Text variant={labelVariant} color="feedbackError">
274
+ {" *"}
275
+ </Text>
276
+ )}
277
+ </Text>
278
+ </Box>
279
+ )}
280
+
281
+ <BaseTextInputContainer
282
+ flexDirection={isTextarea ? "column" : "row"}
283
+ alignItems={isTextarea ? "flex-start" : "center"}
284
+ borderRadius="md"
285
+ style={[
286
+ {
287
+ height: containerHeight,
288
+ paddingHorizontal: containerPadding.paddingHorizontal,
289
+ paddingTop: containerPadding.paddingVertical,
290
+ paddingBottom: containerPadding.paddingVertical,
291
+ backgroundColor: backgroundColorStyle,
292
+ borderColor: borderColorStyle,
293
+ borderWidth: borderWidth,
294
+ },
295
+ inputContainerStyle,
296
+ ]}
297
+ >
298
+ {!isTextarea && leftIconName && leftIconSize && (
299
+ <Box marginRight="sm" alignItems="center" justifyContent="center">
300
+ <Icon
301
+ name={leftIconName}
302
+ size={leftIconSize}
303
+ color={iconColor}
304
+ accessibilityLabel={leftIconName}
305
+ />
306
+ </Box>
307
+ )}
308
+
309
+ <RNTextInput
310
+ ref={ref}
311
+ style={[
312
+ styles.input,
313
+ {
314
+ fontSize: sizeConfig.fontSize,
315
+ color: textColorStyle,
316
+ ...(isTextarea && {
317
+ textAlignVertical: "top", // Android: cursor starts at top
318
+ paddingTop: 0,
319
+ paddingBottom: 0,
320
+ flex: 1,
321
+ width: "100%",
322
+ minHeight:
323
+ containerHeight -
324
+ containerPadding.paddingVertical * 2 -
325
+ borderWidth * 2,
326
+ }),
327
+ },
328
+ ]}
329
+ placeholderTextColor={placeholderColorStyle}
330
+ editable={!isDisabled}
331
+ value={value}
332
+ maxLength={maxLength}
333
+ multiline={isTextarea}
334
+ onFocus={handleFocus}
335
+ onBlur={handleBlur}
336
+ accessibilityLabel={a11yProps.accessibilityLabel}
337
+ accessibilityState={a11yProps.accessibilityState}
338
+ accessibilityHint={a11yProps.accessibilityHint}
339
+ {...rest}
340
+ />
341
+
342
+ {!isTextarea && rightIconName && rightIconSize && (
343
+ <Box marginLeft="sm" alignItems="center" justifyContent="center">
344
+ <Icon
345
+ name={rightIconName}
346
+ size={rightIconSize}
347
+ color={iconColor}
348
+ accessibilityLabel={rightIconName}
349
+ />
350
+ </Box>
351
+ )}
352
+ </BaseTextInputContainer>
353
+
354
+ {(helperText || error || showCharCount) && (
355
+ <Box
356
+ marginTop="xs"
357
+ flexDirection="row"
358
+ alignItems="center"
359
+ justifyContent="space-between"
360
+ >
361
+ <Box flex={1}>
362
+ {(error || helperText) && (
363
+ <Text
364
+ variant="caption"
365
+ color={hasError ? "feedbackError" : "textSecondary"}
366
+ >
367
+ {error || helperText}
368
+ </Text>
369
+ )}
370
+ </Box>
371
+
372
+ {showCharCount && maxLength && (
373
+ <Text variant="caption" color="textTertiary">
374
+ {charCount}/{maxLength}
375
+ </Text>
376
+ )}
377
+ </Box>
378
+ )}
379
+ </Box>
380
+ );
381
+ });
382
+
383
+ const styles = StyleSheet.create({
384
+ input: {
385
+ flex: 1,
386
+ padding: 0,
387
+ margin: 0,
388
+ },
389
+ });
390
+
391
+ export const TextInput = memo(TextInputComponent);
392
+ TextInput.displayName = "TextInput";