@webority-technologies/mobile 0.0.14 → 0.0.20

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 (202) hide show
  1. package/lib/commonjs/components/Accordion/Accordion.js +60 -19
  2. package/lib/commonjs/components/AppBar/AppBar.js +29 -20
  3. package/lib/commonjs/components/Avatar/Avatar.js +38 -8
  4. package/lib/commonjs/components/Badge/Badge.js +66 -4
  5. package/lib/commonjs/components/Banner/Banner.js +146 -66
  6. package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +37 -15
  7. package/lib/commonjs/components/BottomSheet/BottomSheet.js +85 -50
  8. package/lib/commonjs/components/Button/Button.js +12 -5
  9. package/lib/commonjs/components/Card/Card.js +106 -16
  10. package/lib/commonjs/components/Carousel/Carousel.js +66 -12
  11. package/lib/commonjs/components/Checkbox/Checkbox.js +11 -7
  12. package/lib/commonjs/components/Chip/Chip.js +44 -12
  13. package/lib/commonjs/components/DatePicker/DatePicker.js +185 -76
  14. package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +133 -59
  15. package/lib/commonjs/components/Dialog/Dialog.js +16 -10
  16. package/lib/commonjs/components/Drawer/Drawer.js +13 -10
  17. package/lib/commonjs/components/FieldBase/FieldBase.js +306 -0
  18. package/lib/commonjs/components/FieldBase/index.js +32 -0
  19. package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +69 -44
  20. package/lib/commonjs/components/ForceUpdateDialog/ForceUpdateDialog.js +8 -2
  21. package/lib/commonjs/components/FormField/FormField.js +3 -2
  22. package/lib/commonjs/components/ImageGallery/ImageGallery.js +132 -44
  23. package/lib/commonjs/components/Input/Input.js +144 -181
  24. package/lib/commonjs/components/ListItem/ListItem.js +90 -11
  25. package/lib/commonjs/components/Modal/Modal.js +55 -27
  26. package/lib/commonjs/components/NumberInput/NumberInput.js +60 -106
  27. package/lib/commonjs/components/OTPInput/OTPInput.js +65 -58
  28. package/lib/commonjs/components/PickerTrigger/PickerTrigger.js +185 -0
  29. package/lib/commonjs/components/{AppIcon → PickerTrigger}/index.js +4 -4
  30. package/lib/commonjs/components/ProgressBar/ProgressBar.js +19 -11
  31. package/lib/commonjs/components/Radio/Radio.js +11 -6
  32. package/lib/commonjs/components/Rating/Rating.js +85 -19
  33. package/lib/commonjs/components/SearchBar/SearchBar.js +84 -107
  34. package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +22 -11
  35. package/lib/commonjs/components/Select/Select.js +62 -91
  36. package/lib/commonjs/components/Skeleton/Skeleton.js +131 -174
  37. package/lib/commonjs/components/Skeleton/SkeletonClock.js +117 -0
  38. package/lib/commonjs/components/Skeleton/SkeletonContent.js +164 -81
  39. package/lib/commonjs/components/Skeleton/SkeletonProvider.js +72 -10
  40. package/lib/commonjs/components/Skeleton/index.js +17 -16
  41. package/lib/commonjs/components/Slider/Slider.js +44 -25
  42. package/lib/commonjs/components/Stepper/Stepper.js +199 -29
  43. package/lib/commonjs/components/Swipeable/Swipeable.js +36 -19
  44. package/lib/commonjs/components/Switch/Switch.js +9 -2
  45. package/lib/commonjs/components/Tabs/Tabs.js +84 -21
  46. package/lib/commonjs/components/TimePicker/TimePicker.js +123 -45
  47. package/lib/commonjs/components/Toast/Toast.js +27 -16
  48. package/lib/commonjs/components/Tooltip/Tooltip.js +56 -32
  49. package/lib/commonjs/components/index.js +37 -37
  50. package/lib/commonjs/theme/tokens.js +55 -7
  51. package/lib/module/components/Accordion/Accordion.js +61 -20
  52. package/lib/module/components/AppBar/AppBar.js +29 -20
  53. package/lib/module/components/Avatar/Avatar.js +39 -9
  54. package/lib/module/components/Badge/Badge.js +67 -5
  55. package/lib/module/components/Banner/Banner.js +147 -67
  56. package/lib/module/components/BottomNavigation/BottomNavigation.js +37 -15
  57. package/lib/module/components/BottomSheet/BottomSheet.js +87 -52
  58. package/lib/module/components/Button/Button.js +12 -5
  59. package/lib/module/components/Card/Card.js +107 -17
  60. package/lib/module/components/Carousel/Carousel.js +67 -13
  61. package/lib/module/components/Checkbox/Checkbox.js +11 -7
  62. package/lib/module/components/Chip/Chip.js +45 -13
  63. package/lib/module/components/DatePicker/DatePicker.js +185 -76
  64. package/lib/module/components/DateRangePicker/DateRangePicker.js +134 -60
  65. package/lib/module/components/Dialog/Dialog.js +16 -10
  66. package/lib/module/components/Drawer/Drawer.js +13 -10
  67. package/lib/module/components/FieldBase/FieldBase.js +297 -0
  68. package/lib/module/components/FieldBase/index.js +4 -0
  69. package/lib/module/components/FloatingActionButton/FloatingActionButton.js +69 -44
  70. package/lib/module/components/ForceUpdateDialog/ForceUpdateDialog.js +8 -2
  71. package/lib/module/components/FormField/FormField.js +3 -2
  72. package/lib/module/components/ImageGallery/ImageGallery.js +128 -40
  73. package/lib/module/components/Input/Input.js +144 -179
  74. package/lib/module/components/ListItem/ListItem.js +91 -12
  75. package/lib/module/components/Modal/Modal.js +55 -27
  76. package/lib/module/components/NumberInput/NumberInput.js +60 -106
  77. package/lib/module/components/OTPInput/OTPInput.js +65 -58
  78. package/lib/module/components/PickerTrigger/PickerTrigger.js +181 -0
  79. package/lib/module/components/PickerTrigger/index.js +4 -0
  80. package/lib/module/components/ProgressBar/ProgressBar.js +19 -11
  81. package/lib/module/components/Radio/Radio.js +11 -6
  82. package/lib/module/components/Rating/Rating.js +86 -20
  83. package/lib/module/components/SearchBar/SearchBar.js +84 -107
  84. package/lib/module/components/SegmentedControl/SegmentedControl.js +22 -11
  85. package/lib/module/components/Select/Select.js +62 -91
  86. package/lib/module/components/Skeleton/Skeleton.js +135 -175
  87. package/lib/module/components/Skeleton/SkeletonClock.js +110 -0
  88. package/lib/module/components/Skeleton/SkeletonContent.js +167 -84
  89. package/lib/module/components/Skeleton/SkeletonProvider.js +71 -10
  90. package/lib/module/components/Skeleton/index.js +3 -2
  91. package/lib/module/components/Slider/Slider.js +44 -25
  92. package/lib/module/components/Stepper/Stepper.js +201 -31
  93. package/lib/module/components/Swipeable/Swipeable.js +36 -19
  94. package/lib/module/components/Switch/Switch.js +9 -2
  95. package/lib/module/components/Tabs/Tabs.js +84 -21
  96. package/lib/module/components/TimePicker/TimePicker.js +123 -45
  97. package/lib/module/components/Toast/Toast.js +27 -16
  98. package/lib/module/components/Tooltip/Tooltip.js +56 -32
  99. package/lib/module/components/index.js +2 -2
  100. package/lib/module/theme/tokens.js +55 -7
  101. package/lib/typescript/commonjs/components/Accordion/Accordion.d.ts +10 -5
  102. package/lib/typescript/commonjs/components/AppBar/AppBar.d.ts +8 -0
  103. package/lib/typescript/commonjs/components/Avatar/Avatar.d.ts +12 -6
  104. package/lib/typescript/commonjs/components/Badge/Badge.d.ts +7 -6
  105. package/lib/typescript/commonjs/components/Banner/Banner.d.ts +17 -6
  106. package/lib/typescript/commonjs/components/BottomSheet/BottomSheet.d.ts +7 -0
  107. package/lib/typescript/commonjs/components/Card/Card.d.ts +17 -6
  108. package/lib/typescript/commonjs/components/Carousel/Carousel.d.ts +7 -6
  109. package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +9 -1
  110. package/lib/typescript/commonjs/components/Chip/Chip.d.ts +13 -6
  111. package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +38 -3
  112. package/lib/typescript/commonjs/components/DateRangePicker/DateRangePicker.d.ts +36 -3
  113. package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +13 -1
  114. package/lib/typescript/commonjs/components/FieldBase/FieldBase.d.ts +141 -0
  115. package/lib/typescript/commonjs/components/FieldBase/index.d.ts +3 -0
  116. package/lib/typescript/commonjs/components/FloatingActionButton/FloatingActionButton.d.ts +8 -6
  117. package/lib/typescript/commonjs/components/FloatingActionButton/index.d.ts +1 -1
  118. package/lib/typescript/commonjs/components/ForceUpdateDialog/ForceUpdateDialog.d.ts +7 -0
  119. package/lib/typescript/commonjs/components/FormField/FormField.d.ts +7 -0
  120. package/lib/typescript/commonjs/components/ImageGallery/ImageGallery.d.ts +6 -4
  121. package/lib/typescript/commonjs/components/Input/Input.d.ts +6 -0
  122. package/lib/typescript/commonjs/components/ListItem/ListItem.d.ts +13 -6
  123. package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +3 -0
  124. package/lib/typescript/commonjs/components/PickerTrigger/PickerTrigger.d.ts +57 -0
  125. package/lib/typescript/commonjs/components/PickerTrigger/index.d.ts +3 -0
  126. package/lib/typescript/commonjs/components/ProgressBar/ProgressBar.d.ts +2 -0
  127. package/lib/typescript/commonjs/components/Radio/Radio.d.ts +3 -0
  128. package/lib/typescript/commonjs/components/Rating/Rating.d.ts +9 -6
  129. package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +3 -0
  130. package/lib/typescript/commonjs/components/Skeleton/Skeleton.d.ts +49 -20
  131. package/lib/typescript/commonjs/components/Skeleton/SkeletonClock.d.ts +60 -0
  132. package/lib/typescript/commonjs/components/Skeleton/SkeletonContent.d.ts +80 -19
  133. package/lib/typescript/commonjs/components/Skeleton/SkeletonProvider.d.ts +39 -5
  134. package/lib/typescript/commonjs/components/Skeleton/index.d.ts +6 -4
  135. package/lib/typescript/commonjs/components/Slider/Slider.d.ts +12 -1
  136. package/lib/typescript/commonjs/components/Stepper/Stepper.d.ts +18 -6
  137. package/lib/typescript/commonjs/components/Swipeable/Swipeable.d.ts +2 -0
  138. package/lib/typescript/commonjs/components/Switch/Switch.d.ts +1 -0
  139. package/lib/typescript/commonjs/components/Tabs/Tabs.d.ts +26 -2
  140. package/lib/typescript/commonjs/components/TimePicker/TimePicker.d.ts +36 -3
  141. package/lib/typescript/commonjs/components/Toast/Toast.d.ts +8 -0
  142. package/lib/typescript/commonjs/components/Tooltip/Tooltip.d.ts +7 -1
  143. package/lib/typescript/commonjs/components/index.d.ts +5 -5
  144. package/lib/typescript/commonjs/index.d.ts +1 -1
  145. package/lib/typescript/commonjs/theme/index.d.ts +1 -1
  146. package/lib/typescript/commonjs/theme/types.d.ts +553 -11
  147. package/lib/typescript/module/components/Accordion/Accordion.d.ts +10 -5
  148. package/lib/typescript/module/components/AppBar/AppBar.d.ts +8 -0
  149. package/lib/typescript/module/components/Avatar/Avatar.d.ts +12 -6
  150. package/lib/typescript/module/components/Badge/Badge.d.ts +7 -6
  151. package/lib/typescript/module/components/Banner/Banner.d.ts +17 -6
  152. package/lib/typescript/module/components/BottomSheet/BottomSheet.d.ts +7 -0
  153. package/lib/typescript/module/components/Card/Card.d.ts +17 -6
  154. package/lib/typescript/module/components/Carousel/Carousel.d.ts +7 -6
  155. package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +9 -1
  156. package/lib/typescript/module/components/Chip/Chip.d.ts +13 -6
  157. package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +38 -3
  158. package/lib/typescript/module/components/DateRangePicker/DateRangePicker.d.ts +36 -3
  159. package/lib/typescript/module/components/Dialog/Dialog.d.ts +13 -1
  160. package/lib/typescript/module/components/FieldBase/FieldBase.d.ts +141 -0
  161. package/lib/typescript/module/components/FieldBase/index.d.ts +3 -0
  162. package/lib/typescript/module/components/FloatingActionButton/FloatingActionButton.d.ts +8 -6
  163. package/lib/typescript/module/components/FloatingActionButton/index.d.ts +1 -1
  164. package/lib/typescript/module/components/ForceUpdateDialog/ForceUpdateDialog.d.ts +7 -0
  165. package/lib/typescript/module/components/FormField/FormField.d.ts +7 -0
  166. package/lib/typescript/module/components/ImageGallery/ImageGallery.d.ts +6 -4
  167. package/lib/typescript/module/components/Input/Input.d.ts +6 -0
  168. package/lib/typescript/module/components/ListItem/ListItem.d.ts +13 -6
  169. package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +3 -0
  170. package/lib/typescript/module/components/PickerTrigger/PickerTrigger.d.ts +57 -0
  171. package/lib/typescript/module/components/PickerTrigger/index.d.ts +3 -0
  172. package/lib/typescript/module/components/ProgressBar/ProgressBar.d.ts +2 -0
  173. package/lib/typescript/module/components/Radio/Radio.d.ts +3 -0
  174. package/lib/typescript/module/components/Rating/Rating.d.ts +9 -6
  175. package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +3 -0
  176. package/lib/typescript/module/components/Skeleton/Skeleton.d.ts +49 -20
  177. package/lib/typescript/module/components/Skeleton/SkeletonClock.d.ts +60 -0
  178. package/lib/typescript/module/components/Skeleton/SkeletonContent.d.ts +80 -19
  179. package/lib/typescript/module/components/Skeleton/SkeletonProvider.d.ts +39 -5
  180. package/lib/typescript/module/components/Skeleton/index.d.ts +6 -4
  181. package/lib/typescript/module/components/Slider/Slider.d.ts +12 -1
  182. package/lib/typescript/module/components/Stepper/Stepper.d.ts +18 -6
  183. package/lib/typescript/module/components/Swipeable/Swipeable.d.ts +2 -0
  184. package/lib/typescript/module/components/Switch/Switch.d.ts +1 -0
  185. package/lib/typescript/module/components/Tabs/Tabs.d.ts +26 -2
  186. package/lib/typescript/module/components/TimePicker/TimePicker.d.ts +36 -3
  187. package/lib/typescript/module/components/Toast/Toast.d.ts +8 -0
  188. package/lib/typescript/module/components/Tooltip/Tooltip.d.ts +7 -1
  189. package/lib/typescript/module/components/index.d.ts +5 -5
  190. package/lib/typescript/module/index.d.ts +1 -1
  191. package/lib/typescript/module/theme/index.d.ts +1 -1
  192. package/lib/typescript/module/theme/types.d.ts +553 -11
  193. package/package.json +2 -6
  194. package/lib/commonjs/components/AppIcon/AppIcon.js +0 -120
  195. package/lib/commonjs/types/vector-icons.d.js +0 -2
  196. package/lib/module/components/AppIcon/AppIcon.js +0 -111
  197. package/lib/module/components/AppIcon/index.js +0 -4
  198. package/lib/module/types/vector-icons.d.js +0 -2
  199. package/lib/typescript/commonjs/components/AppIcon/AppIcon.d.ts +0 -20
  200. package/lib/typescript/commonjs/components/AppIcon/index.d.ts +0 -3
  201. package/lib/typescript/module/components/AppIcon/AppIcon.d.ts +0 -20
  202. package/lib/typescript/module/components/AppIcon/index.d.ts +0 -3
@@ -25,12 +25,28 @@ const Tabs = /*#__PURE__*/forwardRef((props, ref) => {
25
25
  scrollable = false,
26
26
  align = 'left',
27
27
  style,
28
+ containerStyle,
28
29
  tabStyle,
30
+ tabLabelStyle,
31
+ tabIconStyle,
32
+ tabBadgeStyle,
33
+ dividerStyle,
34
+ indicatorStyle,
35
+ progress,
29
36
  accessibilityLabel,
30
37
  testID
31
38
  } = props;
32
39
  const theme = useTheme();
33
40
  const styles = useMemo(() => buildStyles(theme), [theme]);
41
+ const tabsTokens = theme.components.tabs;
42
+ const tabPaddingHorizontal = tabsTokens?.tabPaddingHorizontal ?? theme.spacing.lg;
43
+ const tabPaddingVertical = tabsTokens?.tabPaddingVertical ?? theme.spacing.md;
44
+ const tabIconSpacing = tabsTokens?.tabIconSpacing ?? 6;
45
+ const underlineHeight = tabsTokens?.underlineHeight ?? 2;
46
+ const pillInset = tabsTokens?.pillInset ?? 4;
47
+ const disabledOpacity = tabsTokens?.disabledOpacity ?? 0.45;
48
+ const badgeGap = tabsTokens?.badgeGap ?? 6;
49
+ const hapticOnPress = tabsTokens?.hapticOnPress ?? false;
34
50
 
35
51
  // Per-tab measured layouts (key → {x, width}).
36
52
  const [layouts, setLayouts] = useState({});
@@ -38,9 +54,33 @@ const Tabs = /*#__PURE__*/forwardRef((props, ref) => {
38
54
  const indicatorWidth = useRef(createAnimatedValue(0)).current;
39
55
  const activeLayout = layouts[activeKey];
40
56
 
57
+ // Progress-driven mode: when the caller supplies a fractional-index signal
58
+ // and every tab has reported its layout, the indicator's translateX/width
59
+ // are interpolated from that signal — the press-spring is suppressed so the
60
+ // two drivers don't fight for the same view.
61
+ const allMeasured = tabs.every(t => layouts[t.key] !== undefined);
62
+ const useProgress = progress !== undefined && allMeasured && tabs.length > 1;
63
+ const progressTranslateX = useMemo(() => {
64
+ if (!useProgress || !progress) return null;
65
+ return progress.interpolate({
66
+ inputRange: tabs.map((_, i) => i),
67
+ outputRange: tabs.map(t => layouts[t.key].x),
68
+ extrapolate: 'clamp'
69
+ });
70
+ }, [useProgress, progress, tabs, layouts]);
71
+ const progressWidth = useMemo(() => {
72
+ if (!useProgress || !progress) return null;
73
+ return progress.interpolate({
74
+ inputRange: tabs.map((_, i) => i),
75
+ outputRange: tabs.map(t => layouts[t.key].width),
76
+ extrapolate: 'clamp'
77
+ });
78
+ }, [useProgress, progress, tabs, layouts]);
79
+
41
80
  // Animate indicator whenever activeKey or its layout changes.
42
81
  useEffect(() => {
43
82
  if (!activeLayout) return;
83
+ if (useProgress) return;
44
84
  const spring = theme.motion.spring.snappy;
45
85
  Animated.parallel([
46
86
  // Both must use the JS driver: width can't run on native, and mixing
@@ -59,7 +99,7 @@ const Tabs = /*#__PURE__*/forwardRef((props, ref) => {
59
99
  mass: spring.mass,
60
100
  useNativeDriver: false
61
101
  })]).start();
62
- }, [activeLayout, activeKey, indicatorTranslateX, indicatorWidth, theme.motion.spring.snappy]);
102
+ }, [activeLayout, activeKey, indicatorTranslateX, indicatorWidth, theme.motion.spring.snappy, useProgress]);
63
103
  const handleLayout = useCallback(key => e => {
64
104
  const {
65
105
  x,
@@ -80,22 +120,27 @@ const Tabs = /*#__PURE__*/forwardRef((props, ref) => {
80
120
  const handlePress = useCallback(tab => {
81
121
  if (tab.disabled) return;
82
122
  if (tab.key === activeKey) return;
83
- triggerHaptic('selection');
123
+ if (hapticOnPress) triggerHaptic('selection');
84
124
  onChange(tab.key);
85
- }, [activeKey, onChange]);
125
+ }, [activeKey, onChange, hapticOnPress]);
86
126
  const indicatorIsPill = variant === 'pills';
87
127
 
88
128
  // Indicator visual.
89
129
  const indicator = /*#__PURE__*/_jsx(Animated.View, {
90
130
  pointerEvents: "none",
91
- style: [indicatorIsPill ? styles.indicatorPill : styles.indicatorUnderline, {
92
- width: indicatorWidth,
131
+ style: [indicatorIsPill ? styles.indicatorPill : styles.indicatorUnderline, indicatorIsPill ? {
132
+ top: pillInset,
133
+ bottom: pillInset
134
+ } : {
135
+ height: underlineHeight
136
+ }, {
137
+ width: progressWidth ?? indicatorWidth,
93
138
  transform: [{
94
- translateX: indicatorTranslateX
139
+ translateX: progressTranslateX ?? indicatorTranslateX
95
140
  }],
96
141
  backgroundColor: indicatorIsPill ? theme.colors.primaryMuted : theme.colors.primary,
97
142
  borderRadius: indicatorIsPill ? theme.radius.full : 0
98
- }]
143
+ }, indicatorStyle]
99
144
  });
100
145
  const renderTabs = () => tabs.map(tab => {
101
146
  const isActive = tab.key === activeKey;
@@ -117,23 +162,41 @@ const Tabs = /*#__PURE__*/forwardRef((props, ref) => {
117
162
  style: ({
118
163
  pressed
119
164
  }) => [styles.tab, scrollable ? null : styles.tabFlex, {
120
- paddingHorizontal: theme.spacing.lg,
121
- paddingVertical: theme.spacing.md,
122
- opacity: isDisabled ? 0.45 : 1,
165
+ paddingHorizontal: tabPaddingHorizontal,
166
+ paddingVertical: tabPaddingVertical,
167
+ opacity: isDisabled ? disabledOpacity : 1,
123
168
  backgroundColor: pressed ? theme.colors.surface.pressed : 'transparent'
124
169
  }, tabStyle],
125
170
  children: [tab.icon ? /*#__PURE__*/_jsx(View, {
126
- style: styles.tabIcon,
171
+ style: [styles.tabIcon, {
172
+ marginRight: tabIconSpacing
173
+ }, tabIconStyle],
127
174
  children: tab.icon
128
175
  }) : null, /*#__PURE__*/_jsx(Text, {
129
176
  style: [styles.tabLabel, {
130
177
  color: isActive ? theme.colors.primary : theme.colors.text.tertiary,
131
178
  fontSize: theme.typography.fontSize.base,
132
179
  fontWeight: isActive ? theme.typography.fontWeight.semibold : theme.typography.fontWeight.medium
133
- }],
180
+ }, tabLabelStyle],
134
181
  numberOfLines: 1,
135
182
  children: tab.label
136
- })]
183
+ }), tab.badge !== undefined && tab.badge !== null ? /*#__PURE__*/_jsx(View, {
184
+ style: [styles.tabBadge, {
185
+ marginLeft: badgeGap,
186
+ backgroundColor: theme.colors.primaryMuted,
187
+ borderRadius: theme.radius.full,
188
+ paddingHorizontal: theme.spacing.xs
189
+ }, tabBadgeStyle],
190
+ children: /*#__PURE__*/_jsx(Text, {
191
+ style: {
192
+ color: theme.colors.primary,
193
+ fontSize: theme.typography.fontSize.xs,
194
+ fontWeight: theme.typography.fontWeight.semibold
195
+ },
196
+ numberOfLines: 1,
197
+ children: String(tab.badge)
198
+ })
199
+ }) : null]
137
200
  }, tab.key);
138
201
  });
139
202
  const baseRowStyle = {
@@ -147,7 +210,7 @@ const Tabs = /*#__PURE__*/forwardRef((props, ref) => {
147
210
  style: [styles.container, {
148
211
  borderBottomColor: theme.colors.border.secondary,
149
212
  borderBottomWidth: variant === 'underline' ? StyleSheet.hairlineWidth : 0
150
- }, style],
213
+ }, variant === 'underline' ? dividerStyle : null, style, containerStyle],
151
214
  accessibilityRole: "tablist",
152
215
  accessibilityLabel: accessibilityLabel,
153
216
  testID: testID,
@@ -177,23 +240,23 @@ const buildStyles = _theme => StyleSheet.create({
177
240
  tabFlex: {
178
241
  flex: 1
179
242
  },
180
- tabIcon: {
181
- marginRight: 6
182
- },
243
+ tabIcon: {},
183
244
  tabLabel: {
184
245
  includeFontPadding: false
185
246
  },
247
+ tabBadge: {
248
+ minWidth: 18,
249
+ alignItems: 'center',
250
+ justifyContent: 'center'
251
+ },
186
252
  indicatorUnderline: {
187
253
  position: 'absolute',
188
254
  left: 0,
189
- bottom: 0,
190
- height: 2
255
+ bottom: 0
191
256
  },
192
257
  indicatorPill: {
193
258
  position: 'absolute',
194
259
  left: 0,
195
- top: 4,
196
- bottom: 4,
197
260
  zIndex: -1
198
261
  }
199
262
  });
@@ -2,9 +2,23 @@
2
2
 
3
3
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { AccessibilityInfo, Animated, Easing, FlatList, Modal, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
5
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
6
  import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
7
  import { triggerHaptic } from "../../utils/hapticUtils.js";
7
8
  import Button from "../Button/Button.js";
9
+ import { PickerTrigger } from "../PickerTrigger/PickerTrigger.js";
10
+
11
+ /**
12
+ * TimePicker supports two modes:
13
+ *
14
+ * 1. Controlled-modal mode — pass `visible`, `onSelect`, `onClose`. The
15
+ * component renders only the modal sheet and the caller owns open/close
16
+ * state plus its own trigger UI.
17
+ * 2. Trigger mode — omit `visible`. The component renders a PickerTrigger
18
+ * field (label / value / placeholder / chevron / clear / helper / error)
19
+ * and manages its own modal open state. `onSelect` is still called on
20
+ * confirm.
21
+ */
8
22
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
9
23
  const ITEM_HEIGHT = 40;
10
24
  const VISIBLE_ITEMS = 5;
@@ -78,6 +92,8 @@ const Wheel = ({
78
92
  });
79
93
  }
80
94
  }, [selectedIndex, scrollY]);
95
+ const hapticEnabled = theme.components.timePicker?.haptic ?? false;
96
+ const hapticDebounceMs = theme.components.timePicker?.hapticDebounceMs ?? HAPTIC_DEBOUNCE_MS;
81
97
  const onScroll = useMemo(() => Animated.event([{
82
98
  nativeEvent: {
83
99
  contentOffset: {
@@ -91,14 +107,14 @@ const Wheel = ({
91
107
  const idx = Math.round(y / ITEM_HEIGHT);
92
108
  if (idx !== lastIndexRef.current && idx >= 0 && idx < data.length) {
93
109
  const now = Date.now();
94
- if (now - lastHapticAtRef.current >= HAPTIC_DEBOUNCE_MS) {
110
+ if (hapticEnabled && now - lastHapticAtRef.current >= hapticDebounceMs) {
95
111
  triggerHaptic('selection');
96
112
  lastHapticAtRef.current = now;
97
113
  }
98
114
  lastIndexRef.current = idx;
99
115
  }
100
116
  }
101
- }), [scrollY, data.length]);
117
+ }), [scrollY, data.length, hapticEnabled, hapticDebounceMs]);
102
118
  const onMomentumScrollEnd = useCallback(event => {
103
119
  const y = event.nativeEvent.contentOffset.y;
104
120
  const idx = Math.max(0, Math.min(data.length - 1, Math.round(y / ITEM_HEIGHT)));
@@ -197,21 +213,48 @@ const Wheel = ({
197
213
  })]
198
214
  });
199
215
  };
200
- const TimePicker = ({
201
- visible,
202
- value,
203
- onSelect,
204
- onClose,
205
- format = '12h',
206
- minuteStep = 1,
207
- title = 'Select Time',
208
- confirmLabel = 'Confirm',
209
- cancelLabel = 'Cancel',
210
- testID,
211
- style
212
- }) => {
216
+ const TimePicker = props => {
217
+ const {
218
+ visible,
219
+ value,
220
+ onSelect,
221
+ onClose,
222
+ format = '12h',
223
+ minuteStep = 1,
224
+ title = 'Select Time',
225
+ confirmLabel = 'Confirm',
226
+ cancelLabel = 'Cancel',
227
+ testID,
228
+ style,
229
+ containerStyle,
230
+ headerLabelStyle,
231
+ footerButtonStyle,
232
+ label,
233
+ placeholder,
234
+ helperText,
235
+ error,
236
+ required,
237
+ disabled,
238
+ size,
239
+ variant,
240
+ clearable,
241
+ onClear,
242
+ formatValue,
243
+ triggerStyle
244
+ } = props;
245
+ const isControlled = props.visible !== undefined;
246
+ const [internalOpen, setInternalOpen] = useState(false);
247
+ const open = isControlled ? visible : internalOpen;
213
248
  const theme = useTheme();
249
+ const insets = useSafeAreaInsets();
214
250
  const styles = useMemo(() => buildStyles(theme), [theme]);
251
+ const handleCloseModal = useCallback(() => {
252
+ if (isControlled) {
253
+ onClose?.();
254
+ } else {
255
+ setInternalOpen(false);
256
+ }
257
+ }, [isControlled, onClose]);
215
258
  const hours = useMemo(() => format === '24h' ? range(0, 23) : range(1, 12), [format]);
216
259
  const minutes = useMemo(() => range(0, 59, minuteStep), [minuteStep]);
217
260
  const periods = useMemo(() => ['AM', 'PM'], []);
@@ -240,9 +283,9 @@ const TimePicker = ({
240
283
  });
241
284
  const [periodIndex, setPeriodIndex] = useState(() => initial.period === 'PM' ? 1 : 0);
242
285
 
243
- // Re-sync on visible toggle / value change / format / step.
286
+ // Re-sync on open toggle / value change / format / step.
244
287
  useEffect(() => {
245
- if (!visible) return;
288
+ if (!open) return;
246
289
  const h24 = value && Number.isFinite(value.hour) ? value.hour : new Date().getHours();
247
290
  const m = value && Number.isFinite(value.minute) ? value.minute : new Date().getMinutes();
248
291
  const stepped = clampMinuteToStep(m, minuteStep);
@@ -255,18 +298,20 @@ const TimePicker = ({
255
298
  const mIdx = minutes.indexOf(stepped);
256
299
  setMinuteIndex(mIdx >= 0 ? mIdx : 0);
257
300
  setPeriodIndex(period === 'PM' ? 1 : 0);
258
- }, [visible, value, format, minuteStep, hours, minutes]);
301
+ }, [open, value, format, minuteStep, hours, minutes]);
259
302
 
260
303
  // Sheet animations.
261
304
  const opacity = useRef(createAnimatedValue(0)).current;
262
305
  const translateY = useRef(createAnimatedValue(40)).current;
263
306
  useEffect(() => {
264
- if (visible) {
265
- Animated.parallel([Animated.timing(opacity, {
307
+ if (open) {
308
+ Animated.parallel([
309
+ // Backdrop opacity uses JS driver — see Modal.tsx for the Fabric reason.
310
+ Animated.timing(opacity, {
266
311
  toValue: 1,
267
312
  duration: theme.motion.duration.normal,
268
313
  easing: Easing.out(Easing.cubic),
269
- useNativeDriver: true
314
+ useNativeDriver: false
270
315
  }), Animated.spring(translateY, {
271
316
  toValue: 0,
272
317
  damping: theme.motion.spring.snappy.damping,
@@ -275,10 +320,10 @@ const TimePicker = ({
275
320
  useNativeDriver: true
276
321
  })]).start();
277
322
  } else {
278
- setNativeValue(opacity, 0);
323
+ opacity.setValue(0);
279
324
  setNativeValue(translateY, 40);
280
325
  }
281
- }, [visible, opacity, translateY, theme.motion]);
326
+ }, [open, opacity, translateY, theme.motion]);
282
327
  const announce = useCallback(msg => {
283
328
  if (Platform.OS === 'ios' || Platform.OS === 'android') {
284
329
  AccessibilityInfo.announceForAccessibility(msg);
@@ -297,21 +342,21 @@ const TimePicker = ({
297
342
  announce(periods[idx] ?? '');
298
343
  }, [periods, announce]);
299
344
  const handleCancel = useCallback(() => {
300
- triggerHaptic('selection');
301
- onClose();
302
- }, [onClose]);
345
+ if (theme.components.timePicker?.haptic) triggerHaptic('selection');
346
+ handleCloseModal();
347
+ }, [handleCloseModal, theme.components.timePicker]);
303
348
  const handleConfirm = useCallback(() => {
304
349
  const displayHour = hours[hourIndex] ?? 0;
305
350
  const period = periods[periodIndex] ?? 'AM';
306
351
  const hour24 = to24h(displayHour, period, format);
307
352
  const minute = minutes[minuteIndex] ?? 0;
308
- triggerHaptic('notificationSuccess');
309
- onSelect({
353
+ if (theme.components.timePicker?.haptic) triggerHaptic('notificationSuccess');
354
+ onSelect?.({
310
355
  hour: hour24,
311
356
  minute
312
357
  });
313
- onClose();
314
- }, [hours, hourIndex, periods, periodIndex, minutes, minuteIndex, format, onSelect, onClose]);
358
+ handleCloseModal();
359
+ }, [hours, hourIndex, periods, periodIndex, minutes, minuteIndex, format, onSelect, handleCloseModal, theme.components.timePicker]);
315
360
  const summary = useMemo(() => {
316
361
  const displayHour = hours[hourIndex] ?? 0;
317
362
  const minute = minutes[minuteIndex] ?? 0;
@@ -322,28 +367,43 @@ const TimePicker = ({
322
367
  const formatHourItem = useCallback(item => pad2(Number(item)), []);
323
368
  const formatMinuteItem = useCallback(item => pad2(Number(item)), []);
324
369
  const formatPeriodItem = useCallback(item => String(item), []);
325
- return /*#__PURE__*/_jsx(Modal, {
326
- visible: visible,
370
+ const defaultFormatValue = useCallback(t => {
371
+ if (format === '24h') return `${pad2(t.hour)}:${pad2(t.minute)}`;
372
+ const {
373
+ displayHour,
374
+ period
375
+ } = from24h(t.hour, '12h');
376
+ return `${displayHour}:${pad2(t.minute)} ${period}`;
377
+ }, [format]);
378
+ const triggerValue = !isControlled && value && Number.isFinite(value.hour) && Number.isFinite(value.minute) ? (formatValue ?? defaultFormatValue)(value) : undefined;
379
+ const modal = /*#__PURE__*/_jsx(Modal, {
380
+ visible: open,
327
381
  transparent: true,
328
- statusBarTranslucent: true,
382
+ statusBarTranslucent: true
383
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
384
+ // @ts-ignore — Android-only RN 0.71+; iOS ignores it.
385
+ ,
386
+ navigationBarTranslucent: true,
329
387
  animationType: "none",
330
- onRequestClose: onClose,
388
+ onRequestClose: handleCloseModal,
331
389
  testID: testID,
332
- children: /*#__PURE__*/_jsxs(Animated.View, {
333
- style: [styles.backdrop, {
334
- opacity
335
- }],
390
+ children: /*#__PURE__*/_jsxs(View
391
+ // Plain View + collapsable={false} — see Modal.tsx backdrop comment.
392
+ , {
393
+ collapsable: false,
394
+ style: styles.backdrop,
336
395
  children: [/*#__PURE__*/_jsx(Pressable, {
337
396
  style: StyleSheet.absoluteFill,
338
- onPress: onClose,
397
+ onPress: handleCloseModal,
339
398
  accessibilityLabel: "Close time picker",
340
399
  accessibilityRole: "button"
341
400
  }), /*#__PURE__*/_jsxs(Animated.View, {
342
401
  style: [styles.sheet, {
402
+ paddingBottom: insets.bottom + theme.spacing.xl,
343
403
  transform: [{
344
404
  translateY
345
405
  }]
346
- }, style],
406
+ }, style, containerStyle],
347
407
  accessibilityViewIsModal: true,
348
408
  accessible: true,
349
409
  accessibilityRole: "none",
@@ -351,7 +411,7 @@ const TimePicker = ({
351
411
  children: [/*#__PURE__*/_jsx(View, {
352
412
  style: styles.handle
353
413
  }), /*#__PURE__*/_jsx(Text, {
354
- style: styles.title,
414
+ style: [styles.title, headerLabelStyle],
355
415
  accessibilityRole: "header",
356
416
  children: title
357
417
  }), /*#__PURE__*/_jsx(View, {
@@ -414,7 +474,7 @@ const TimePicker = ({
414
474
  variant: "ghost",
415
475
  tone: "neutral",
416
476
  onPress: handleCancel,
417
- style: styles.footerBtn,
477
+ style: [styles.footerBtn, footerButtonStyle],
418
478
  accessibilityHint: "Dismiss without changes",
419
479
  testID: "time-picker-cancel"
420
480
  }), /*#__PURE__*/_jsx(Button, {
@@ -423,7 +483,7 @@ const TimePicker = ({
423
483
  tone: "primary",
424
484
  haptic: false,
425
485
  onPress: handleConfirm,
426
- style: styles.footerBtn,
486
+ style: [styles.footerBtn, footerButtonStyle],
427
487
  accessibilityHint: "Confirm selected time",
428
488
  testID: "time-picker-confirm"
429
489
  })]
@@ -431,6 +491,24 @@ const TimePicker = ({
431
491
  })]
432
492
  })
433
493
  });
494
+ if (isControlled) return modal;
495
+ return /*#__PURE__*/_jsxs(_Fragment, {
496
+ children: [/*#__PURE__*/_jsx(PickerTrigger, {
497
+ label: label,
498
+ placeholder: placeholder,
499
+ helperText: helperText,
500
+ error: error,
501
+ required: required,
502
+ disabled: disabled,
503
+ size: size,
504
+ variant: variant,
505
+ clearable: clearable,
506
+ onClear: onClear,
507
+ value: triggerValue,
508
+ onPress: () => setInternalOpen(true),
509
+ triggerStyle: triggerStyle
510
+ }), modal]
511
+ });
434
512
  };
435
513
  const styles = StyleSheet.create({
436
514
  wheelColumn: {
@@ -475,8 +553,8 @@ const buildStyles = theme => {
475
553
  },
476
554
  handle: {
477
555
  alignSelf: 'center',
478
- width: 36,
479
- height: 4,
556
+ width: theme.components.timePicker?.handleWidth ?? 36,
557
+ height: theme.components.timePicker?.handleHeight ?? 4,
480
558
  borderRadius: theme.radius.full,
481
559
  backgroundColor: theme.colors.border.secondary,
482
560
  marginBottom: theme.spacing.sm
@@ -4,10 +4,11 @@ import React, { useEffect, useMemo, useRef } from 'react';
4
4
  import { Animated, PanResponder, Pressable, StyleSheet, Text, View } from 'react-native';
5
5
  import { fontFor, useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
- const SWIPE_DISMISS_THRESHOLD = 80;
8
- const SWIPE_VELOCITY_THRESHOLD = 0.5;
9
- const STACK_OFFSET = 8;
10
- const STACK_SCALE_STEP = 0.04;
7
+ const DEFAULT_SWIPE_DISMISS_THRESHOLD = 80;
8
+ const DEFAULT_SWIPE_VELOCITY_THRESHOLD = 0.5;
9
+ const DEFAULT_STACK_OFFSET = 8;
10
+ const DEFAULT_STACK_SCALE_STEP = 0.04;
11
+ const DEFAULT_AUTO_DISMISS_MS = 3000;
11
12
  const Toast = ({
12
13
  toast,
13
14
  index,
@@ -23,6 +24,16 @@ const Toast = ({
23
24
  const iconCircleBorderRadius = toastTokens?.iconCircleBorderRadius ?? 14;
24
25
  const iconGlyphFontSize = toastTokens?.iconGlyphFontSize ?? 16;
25
26
  const tintBarWidth = toastTokens?.tintBarWidth ?? 4;
27
+ const swipeDismissThreshold = toastTokens?.swipeDismissThreshold ?? DEFAULT_SWIPE_DISMISS_THRESHOLD;
28
+ const swipeVelocityThreshold = toastTokens?.swipeVelocityThreshold ?? DEFAULT_SWIPE_VELOCITY_THRESHOLD;
29
+ const stackOffset = toastTokens?.stackOffset ?? DEFAULT_STACK_OFFSET;
30
+ const stackScaleStep = toastTokens?.stackScaleStep ?? DEFAULT_STACK_SCALE_STEP;
31
+ const stackMinScale = toastTokens?.stackMinScale ?? 0.9;
32
+ const defaultDurationMs = toastTokens?.defaultDurationMs ?? DEFAULT_AUTO_DISMISS_MS;
33
+ const enableSwipeDismiss = toastTokens?.enableSwipeDismiss ?? true;
34
+ const enterSpringDamping = toastTokens?.enterSpringDamping ?? 18;
35
+ const enterSpringStiffness = toastTokens?.enterSpringStiffness ?? 180;
36
+ const enterSpringMass = toastTokens?.enterSpringMass ?? 1;
26
37
  const enterFrom = position === 'top' ? -120 : 120;
27
38
  const translateY = useRef(createAnimatedValue(enterFrom)).current;
28
39
  const translateX = useRef(createAnimatedValue(0)).current;
@@ -48,16 +59,16 @@ const Toast = ({
48
59
  useEffect(() => {
49
60
  Animated.parallel([Animated.spring(translateY, {
50
61
  toValue: 0,
51
- damping: 18,
52
- stiffness: 180,
53
- mass: 1,
62
+ damping: enterSpringDamping,
63
+ stiffness: enterSpringStiffness,
64
+ mass: enterSpringMass,
54
65
  useNativeDriver: true
55
66
  }), Animated.timing(opacity, {
56
67
  toValue: 1,
57
68
  duration: theme.motion.duration.fast,
58
69
  useNativeDriver: true
59
70
  })]).start();
60
- const duration = toast.duration ?? 3000;
71
+ const duration = toast.duration ?? defaultDurationMs;
61
72
  if (duration > 0) {
62
73
  const timer = setTimeout(() => dismiss(true), duration);
63
74
  return () => clearTimeout(timer);
@@ -73,7 +84,7 @@ const Toast = ({
73
84
  setNativeValue(opacity, fade);
74
85
  },
75
86
  onPanResponderRelease: (_evt, gesture) => {
76
- if (Math.abs(gesture.dx) > SWIPE_DISMISS_THRESHOLD || Math.abs(gesture.vx) > SWIPE_VELOCITY_THRESHOLD) {
87
+ if (Math.abs(gesture.dx) > swipeDismissThreshold || Math.abs(gesture.vx) > swipeVelocityThreshold) {
77
88
  const direction = gesture.dx >= 0 ? 1 : -1;
78
89
  Animated.parallel([Animated.timing(translateX, {
79
90
  toValue: direction * 500,
@@ -100,8 +111,8 @@ const Toast = ({
100
111
  }),
101
112
  // eslint-disable-next-line react-hooks/exhaustive-deps
102
113
  []);
103
- const stackTranslate = position === 'top' ? index * STACK_OFFSET : -index * STACK_OFFSET;
104
- const stackScale = Math.max(1 - index * STACK_SCALE_STEP, 0.9);
114
+ const stackTranslate = position === 'top' ? index * stackOffset : -index * stackOffset;
115
+ const stackScale = Math.max(1 - index * stackScaleStep, stackMinScale);
105
116
  const handleActionPress = () => {
106
117
  toast.action?.onPress();
107
118
  dismiss(true);
@@ -116,7 +127,7 @@ const Toast = ({
116
127
  height: iconCircleSize,
117
128
  borderRadius: iconCircleBorderRadius,
118
129
  backgroundColor: tint + '22'
119
- }],
130
+ }, toast.iconCircleStyle],
120
131
  children: /*#__PURE__*/_jsx(Text, {
121
132
  style: [styles.iconGlyph, {
122
133
  color: tint,
@@ -129,7 +140,7 @@ const Toast = ({
129
140
  };
130
141
  const a11yLabel = toast.description ? `${toast.message}. ${toast.description}` : toast.message;
131
142
  return /*#__PURE__*/_jsxs(Animated.View, {
132
- ...panResponder.panHandlers,
143
+ ...(enableSwipeDismiss ? panResponder.panHandlers : {}),
133
144
  accessibilityRole: "alert",
134
145
  accessibilityLiveRegion: "assertive",
135
146
  accessibilityLabel: a11yLabel,
@@ -147,7 +158,7 @@ const Toast = ({
147
158
  }, {
148
159
  scale: stackScale
149
160
  }]
150
- }, toast.style],
161
+ }, toast.style, toast.containerStyle],
151
162
  children: [/*#__PURE__*/_jsx(View, {
152
163
  style: [styles.tintBar, {
153
164
  width: tintBarWidth,
@@ -162,7 +173,7 @@ const Toast = ({
162
173
  color: theme.colors.text.primary,
163
174
  fontSize: theme.typography.fontSize.base,
164
175
  ...fontFor(theme, 'semibold')
165
- }, toast.textStyle],
176
+ }, toast.textStyle, toast.messageStyle],
166
177
  numberOfLines: 2,
167
178
  children: toast.message
168
179
  }), toast.description ? /*#__PURE__*/_jsx(Text, {
@@ -181,7 +192,7 @@ const Toast = ({
181
192
  pressed
182
193
  }) => [styles.actionBtn, {
183
194
  backgroundColor: pressed ? theme.colors.surface.pressed : 'transparent'
184
- }],
195
+ }, toast.actionButtonStyle],
185
196
  children: /*#__PURE__*/_jsx(Text, {
186
197
  style: {
187
198
  color: tint,