@seakoi/native-ui 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/commonjs/components/base/carousel/carousel-indicator.js +56 -0
  3. package/dist/commonjs/components/base/carousel/carousel-slides.js +140 -0
  4. package/dist/commonjs/components/base/carousel/carousel.js +114 -122
  5. package/dist/commonjs/components/base/carousel/hooks/index.js +0 -14
  6. package/dist/commonjs/components/base/carousel/hooks/use-carousel-index.js +16 -13
  7. package/dist/commonjs/components/base/carousel/hooks/use-carousel-lifecycle.js +6 -2
  8. package/dist/commonjs/components/base/carousel/hooks/use-carousel-pan-responder.js +40 -12
  9. package/dist/commonjs/components/base/carousel/hooks/use-carousel-position.js +6 -2
  10. package/dist/commonjs/components/base/carousel/index.js +1 -15
  11. package/dist/commonjs/components/base/carousel/style/index.js +12 -0
  12. package/dist/commonjs/components/base/date-picker/date-picker.js +56 -44
  13. package/dist/commonjs/components/base/date-picker/date-range-picker.js +142 -50
  14. package/dist/commonjs/components/base/date-picker/style/index.js +15 -0
  15. package/dist/commonjs/components/base/date-picker-view/date-picker-view.js +19 -53
  16. package/dist/commonjs/components/base/date-picker-view/index.js +0 -22
  17. package/dist/commonjs/components/base/index.js +30 -8
  18. package/dist/commonjs/components/base/picker/index.js +26 -4
  19. package/dist/commonjs/components/base/picker/picker-content.js +60 -0
  20. package/dist/commonjs/components/base/picker/picker-context.js +9 -0
  21. package/dist/commonjs/components/base/picker/picker-field.js +37 -0
  22. package/dist/commonjs/components/base/picker/picker.js +22 -96
  23. package/dist/commonjs/components/base/picker/style/index.js +1 -3
  24. package/dist/commonjs/components/base/picker-backup/base-picker-container.js +50 -0
  25. package/dist/commonjs/components/base/picker-backup/index.js +27 -0
  26. package/dist/commonjs/components/base/picker-backup/picker-backup.js +75 -0
  27. package/dist/commonjs/components/base/picker-backup/picker-copy.js +106 -0
  28. package/dist/commonjs/components/base/{picker → picker-backup}/picker-trigger.js +5 -5
  29. package/dist/commonjs/components/base/picker-backup/style/index.js +19 -0
  30. package/dist/commonjs/components/base/picker-backup/utils.js +53 -0
  31. package/dist/commonjs/components/base/picker-view/picker-view-column.js +15 -0
  32. package/dist/commonjs/components/base/picker-view/picker-view.js +4 -4
  33. package/dist/commonjs/components/base/tag/index.js +20 -0
  34. package/dist/commonjs/components/base/tag/style/index.js +89 -0
  35. package/dist/commonjs/components/base/tag/tag-context.js +12 -0
  36. package/dist/commonjs/components/base/tag/tag-group.js +35 -0
  37. package/dist/commonjs/components/base/tag/tag.js +47 -0
  38. package/dist/commonjs/components/base/tag/types.js +5 -0
  39. package/dist/module/components/base/carousel/carousel-indicator.js +51 -0
  40. package/dist/module/components/base/carousel/carousel-slides.js +135 -0
  41. package/dist/module/components/base/carousel/carousel.js +116 -124
  42. package/dist/module/components/base/carousel/hooks/index.js +0 -2
  43. package/dist/module/components/base/carousel/hooks/use-carousel-index.js +15 -11
  44. package/dist/module/components/base/carousel/hooks/use-carousel-lifecycle.js +6 -2
  45. package/dist/module/components/base/carousel/hooks/use-carousel-pan-responder.js +40 -11
  46. package/dist/module/components/base/carousel/hooks/use-carousel-position.js +5 -1
  47. package/dist/module/components/base/carousel/index.js +1 -5
  48. package/dist/module/components/base/carousel/style/index.js +12 -0
  49. package/dist/module/components/base/date-picker/date-picker.js +60 -48
  50. package/dist/module/components/base/date-picker/date-range-picker.js +146 -54
  51. package/dist/module/components/base/date-picker/style/index.js +11 -0
  52. package/dist/module/components/base/date-picker-view/date-picker-view.js +23 -57
  53. package/dist/module/components/base/date-picker-view/index.js +1 -3
  54. package/dist/module/components/base/index.js +2 -0
  55. package/dist/module/components/base/picker/index.js +9 -1
  56. package/dist/module/components/base/picker/picker-content.js +54 -0
  57. package/dist/module/components/base/picker/picker-context.js +4 -0
  58. package/dist/module/components/base/picker/picker-field.js +32 -0
  59. package/dist/module/components/base/picker/picker.js +25 -99
  60. package/dist/module/components/base/picker/style/index.js +1 -3
  61. package/dist/module/components/base/picker-backup/base-picker-container.js +44 -0
  62. package/dist/module/components/base/picker-backup/index.js +4 -0
  63. package/dist/module/components/base/picker-backup/picker-backup.js +69 -0
  64. package/dist/module/components/base/picker-backup/picker-copy.js +101 -0
  65. package/dist/module/components/base/{picker → picker-backup}/picker-trigger.js +2 -2
  66. package/dist/module/components/base/picker-backup/style/index.js +15 -0
  67. package/dist/module/components/base/picker-backup/utils.js +48 -0
  68. package/dist/module/components/base/picker-view/picker-view-column.js +15 -0
  69. package/dist/module/components/base/picker-view/picker-view.js +4 -4
  70. package/dist/module/components/base/tag/index.js +5 -0
  71. package/dist/module/components/base/tag/style/index.js +85 -0
  72. package/dist/module/components/base/tag/tag-context.js +8 -0
  73. package/dist/module/components/base/tag/tag-group.js +29 -0
  74. package/dist/module/components/base/tag/tag.js +41 -0
  75. package/dist/module/components/base/tag/types.js +3 -0
  76. package/dist/typescript/components/base/carousel/carousel-indicator.d.ts +42 -0
  77. package/dist/typescript/components/base/carousel/carousel-indicator.d.ts.map +1 -0
  78. package/dist/typescript/components/base/carousel/carousel-slides.d.ts +49 -0
  79. package/dist/typescript/components/base/carousel/carousel-slides.d.ts.map +1 -0
  80. package/dist/typescript/components/base/carousel/carousel.d.ts +16 -11
  81. package/dist/typescript/components/base/carousel/carousel.d.ts.map +1 -1
  82. package/dist/typescript/components/base/carousel/hooks/index.d.ts +0 -2
  83. package/dist/typescript/components/base/carousel/hooks/index.d.ts.map +1 -1
  84. package/dist/typescript/components/base/carousel/hooks/use-carousel-index.d.ts.map +1 -1
  85. package/dist/typescript/components/base/carousel/hooks/use-carousel-lifecycle.d.ts.map +1 -1
  86. package/dist/typescript/components/base/carousel/hooks/use-carousel-pan-responder.d.ts.map +1 -1
  87. package/dist/typescript/components/base/carousel/hooks/use-carousel-position.d.ts.map +1 -1
  88. package/dist/typescript/components/base/carousel/index.d.ts +1 -4
  89. package/dist/typescript/components/base/carousel/index.d.ts.map +1 -1
  90. package/dist/typescript/components/base/carousel/style/index.d.ts +12 -0
  91. package/dist/typescript/components/base/carousel/style/index.d.ts.map +1 -1
  92. package/dist/typescript/components/base/carousel/types.d.ts +8 -17
  93. package/dist/typescript/components/base/carousel/types.d.ts.map +1 -1
  94. package/dist/typescript/components/base/date-picker/date-picker.d.ts +4 -2
  95. package/dist/typescript/components/base/date-picker/date-picker.d.ts.map +1 -1
  96. package/dist/typescript/components/base/date-picker/date-range-picker.d.ts +12 -3
  97. package/dist/typescript/components/base/date-picker/date-range-picker.d.ts.map +1 -1
  98. package/dist/typescript/components/base/date-picker/style/index.d.ts +9 -0
  99. package/dist/typescript/components/base/date-picker/style/index.d.ts.map +1 -0
  100. package/dist/typescript/components/base/date-picker-view/date-picker-view.d.ts +1 -6
  101. package/dist/typescript/components/base/date-picker-view/date-picker-view.d.ts.map +1 -1
  102. package/dist/typescript/components/base/date-picker-view/index.d.ts +0 -2
  103. package/dist/typescript/components/base/date-picker-view/index.d.ts.map +1 -1
  104. package/dist/typescript/components/base/date-picker-view/types.d.ts +1 -1
  105. package/dist/typescript/components/base/index.d.ts +2 -0
  106. package/dist/typescript/components/base/index.d.ts.map +1 -1
  107. package/dist/typescript/components/base/picker/index.d.ts +7 -1
  108. package/dist/typescript/components/base/picker/index.d.ts.map +1 -1
  109. package/dist/typescript/components/base/picker/picker-content.d.ts +15 -0
  110. package/dist/typescript/components/base/picker/picker-content.d.ts.map +1 -0
  111. package/dist/typescript/components/base/picker/picker-context.d.ts +18 -0
  112. package/dist/typescript/components/base/picker/picker-context.d.ts.map +1 -0
  113. package/dist/typescript/components/base/picker/picker-field.d.ts +10 -0
  114. package/dist/typescript/components/base/picker/picker-field.d.ts.map +1 -0
  115. package/dist/typescript/components/base/picker/picker.d.ts +13 -11
  116. package/dist/typescript/components/base/picker/picker.d.ts.map +1 -1
  117. package/dist/typescript/components/base/picker/style/index.d.ts +0 -2
  118. package/dist/typescript/components/base/picker/style/index.d.ts.map +1 -1
  119. package/dist/typescript/components/base/picker-backup/base-picker-container.d.ts +15 -0
  120. package/dist/typescript/components/base/picker-backup/base-picker-container.d.ts.map +1 -0
  121. package/dist/typescript/components/base/picker-backup/index.d.ts +3 -0
  122. package/dist/typescript/components/base/picker-backup/index.d.ts.map +1 -0
  123. package/dist/typescript/components/base/picker-backup/picker-backup.d.ts +26 -0
  124. package/dist/typescript/components/base/picker-backup/picker-backup.d.ts.map +1 -0
  125. package/dist/typescript/components/base/picker-backup/picker-copy.d.ts +13 -0
  126. package/dist/typescript/components/base/picker-backup/picker-copy.d.ts.map +1 -0
  127. package/dist/typescript/components/base/picker-backup/picker-trigger.d.ts.map +1 -0
  128. package/dist/typescript/components/base/picker-backup/style/index.d.ts +13 -0
  129. package/dist/typescript/components/base/picker-backup/style/index.d.ts.map +1 -0
  130. package/dist/typescript/components/base/picker-backup/utils.d.ts +8 -0
  131. package/dist/typescript/components/base/picker-backup/utils.d.ts.map +1 -0
  132. package/dist/typescript/components/base/picker-view/picker-view-column.d.ts.map +1 -1
  133. package/dist/typescript/components/base/picker-view/utils/picker.d.ts +3 -3
  134. package/dist/typescript/components/base/picker-view/utils/picker.d.ts.map +1 -1
  135. package/dist/typescript/components/base/tag/index.d.ts +5 -0
  136. package/dist/typescript/components/base/tag/index.d.ts.map +1 -0
  137. package/dist/typescript/components/base/tag/style/index.d.ts +61 -0
  138. package/dist/typescript/components/base/tag/style/index.d.ts.map +1 -0
  139. package/dist/typescript/components/base/tag/tag-context.d.ts +3 -0
  140. package/dist/typescript/components/base/tag/tag-context.d.ts.map +1 -0
  141. package/dist/typescript/components/base/tag/tag-group.d.ts +4 -0
  142. package/dist/typescript/components/base/tag/tag-group.d.ts.map +1 -0
  143. package/dist/typescript/components/base/tag/tag.d.ts +4 -0
  144. package/dist/typescript/components/base/tag/tag.d.ts.map +1 -0
  145. package/dist/typescript/components/base/tag/types.d.ts +48 -0
  146. package/dist/typescript/components/base/tag/types.d.ts.map +1 -0
  147. package/package.json +1 -1
  148. package/src/components/base/carousel/carousel-indicator.tsx +80 -0
  149. package/src/components/base/carousel/carousel-slides.tsx +177 -0
  150. package/src/components/base/carousel/carousel.tsx +108 -118
  151. package/src/components/base/carousel/hooks/index.ts +0 -2
  152. package/src/components/base/carousel/hooks/use-carousel-index.ts +13 -9
  153. package/src/components/base/carousel/hooks/use-carousel-lifecycle.ts +4 -3
  154. package/src/components/base/carousel/hooks/use-carousel-pan-responder.ts +40 -16
  155. package/src/components/base/carousel/hooks/use-carousel-position.ts +4 -1
  156. package/src/components/base/carousel/index.ts +1 -3
  157. package/src/components/base/carousel/style/index.ts +12 -0
  158. package/src/components/base/carousel/types.ts +8 -21
  159. package/src/components/base/date-picker/date-picker.tsx +64 -61
  160. package/src/components/base/date-picker/date-range-picker.tsx +178 -70
  161. package/src/components/base/date-picker/style/index.ts +10 -0
  162. package/src/components/base/date-picker-view/date-picker-view.tsx +21 -68
  163. package/src/components/base/date-picker-view/index.ts +0 -2
  164. package/src/components/base/date-picker-view/types.ts +1 -1
  165. package/src/components/base/index.ts +2 -0
  166. package/src/components/base/picker/index.ts +11 -1
  167. package/src/components/base/picker/picker-content.tsx +75 -0
  168. package/src/components/base/picker/picker-context.ts +19 -0
  169. package/src/components/base/picker/picker-field.tsx +50 -0
  170. package/src/components/base/picker/picker.tsx +38 -114
  171. package/src/components/base/picker/style/index.ts +0 -2
  172. package/src/components/base/picker-backup/base-picker-container.tsx +55 -0
  173. package/src/components/base/picker-backup/index.ts +2 -0
  174. package/src/components/base/picker-backup/picker-backup.tsx +110 -0
  175. package/src/components/base/picker-backup/picker-copy.tsx +125 -0
  176. package/src/components/base/{picker → picker-backup}/picker-trigger.tsx +2 -2
  177. package/src/components/base/picker-backup/style/index.ts +14 -0
  178. package/src/components/base/picker-backup/utils.ts +62 -0
  179. package/src/components/base/picker-view/picker-view-column.tsx +20 -0
  180. package/src/components/base/picker-view/picker-view.tsx +4 -4
  181. package/src/components/base/picker-view/utils/picker.ts +3 -5
  182. package/src/components/base/tag/index.ts +5 -0
  183. package/src/components/base/tag/style/index.tsx +84 -0
  184. package/src/components/base/tag/tag-context.ts +9 -0
  185. package/src/components/base/tag/tag-group.tsx +31 -0
  186. package/src/components/base/tag/tag.tsx +50 -0
  187. package/src/components/base/tag/types.ts +71 -0
  188. package/dist/commonjs/components/base/carousel/carousel-item.js +0 -45
  189. package/dist/commonjs/components/base/carousel/constants.js +0 -25
  190. package/dist/commonjs/components/base/carousel/hooks/use-carousel-indicator.js +0 -63
  191. package/dist/commonjs/components/base/carousel/hooks/use-carousel-slides.js +0 -95
  192. package/dist/commonjs/components/base/carousel/utils.js +0 -63
  193. package/dist/commonjs/components/base/date-picker-view/date-range-picker-view.js +0 -145
  194. package/dist/commonjs/components/base/date-picker-view/date-time-picker.js +0 -39
  195. package/dist/module/components/base/carousel/carousel-item.js +0 -40
  196. package/dist/module/components/base/carousel/constants.js +0 -21
  197. package/dist/module/components/base/carousel/hooks/use-carousel-indicator.js +0 -58
  198. package/dist/module/components/base/carousel/hooks/use-carousel-slides.js +0 -90
  199. package/dist/module/components/base/carousel/utils.js +0 -55
  200. package/dist/module/components/base/date-picker-view/date-range-picker-view.js +0 -138
  201. package/dist/module/components/base/date-picker-view/date-time-picker.js +0 -34
  202. package/dist/typescript/components/base/carousel/carousel-item.d.ts +0 -26
  203. package/dist/typescript/components/base/carousel/carousel-item.d.ts.map +0 -1
  204. package/dist/typescript/components/base/carousel/constants.d.ts +0 -17
  205. package/dist/typescript/components/base/carousel/constants.d.ts.map +0 -1
  206. package/dist/typescript/components/base/carousel/hooks/use-carousel-indicator.d.ts +0 -37
  207. package/dist/typescript/components/base/carousel/hooks/use-carousel-indicator.d.ts.map +0 -1
  208. package/dist/typescript/components/base/carousel/hooks/use-carousel-slides.d.ts +0 -51
  209. package/dist/typescript/components/base/carousel/hooks/use-carousel-slides.d.ts.map +0 -1
  210. package/dist/typescript/components/base/carousel/utils.d.ts +0 -25
  211. package/dist/typescript/components/base/carousel/utils.d.ts.map +0 -1
  212. package/dist/typescript/components/base/date-picker-view/date-range-picker-view.d.ts +0 -26
  213. package/dist/typescript/components/base/date-picker-view/date-range-picker-view.d.ts.map +0 -1
  214. package/dist/typescript/components/base/date-picker-view/date-time-picker.d.ts +0 -3
  215. package/dist/typescript/components/base/date-picker-view/date-time-picker.d.ts.map +0 -1
  216. package/dist/typescript/components/base/picker/picker-trigger.d.ts.map +0 -1
  217. package/src/components/base/carousel/carousel-item.tsx +0 -35
  218. package/src/components/base/carousel/constants.ts +0 -19
  219. package/src/components/base/carousel/hooks/use-carousel-indicator.tsx +0 -84
  220. package/src/components/base/carousel/hooks/use-carousel-slides.tsx +0 -131
  221. package/src/components/base/carousel/utils.ts +0 -55
  222. package/src/components/base/date-picker-view/date-range-picker-view.tsx +0 -191
  223. package/src/components/base/date-picker-view/date-time-picker.tsx +0 -34
  224. /package/dist/typescript/components/base/{picker → picker-backup}/picker-trigger.d.ts +0 -0
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ import type { Any } from '#shared/utils';
4
+
5
+ export interface PickerContextState<Value = Any> {
6
+ /** 当前选中的值 */
7
+ value?: Value;
8
+ /** 选择器是否可见 */
9
+ visible?: boolean;
10
+ /** 是否禁用 */
11
+ disabled?: boolean;
12
+ /** 设置禁用状态 */
13
+ setDisabled?: React.Dispatch<React.SetStateAction<boolean>>;
14
+ /** 切换选择器可见状态 */
15
+ toggleVisible?: () => void;
16
+ /** 值变化回调 */
17
+ onChange?: (value: Value | undefined) => void;
18
+ }
19
+ export const PickerContext = React.createContext<PickerContextState | null>(null);
@@ -0,0 +1,50 @@
1
+ import { useLatest } from 'ahooks';
2
+ import React, { useContext, useMemo } from 'react';
3
+ import { TouchableOpacity } from 'react-native';
4
+
5
+ import { Text } from '#components/base';
6
+ import { useTheme } from '#native-provider';
7
+ import { type Any, safetyRenderChildren } from '#shared/utils';
8
+
9
+ import { PickerContext } from './picker-context';
10
+ import { usePickerFieldStyles } from './style';
11
+
12
+ export interface PickerFieldProps<Value = Any> {
13
+ /** 未选中时显示的占位内容 */
14
+ placeholder?: React.ReactNode;
15
+ /** 自定义渲染选中值的显示内容 */
16
+ renderValue?: (value: Value) => React.ReactNode;
17
+ }
18
+ export function PickerField<Value>(props: PickerFieldProps<Value>) {
19
+ const theme = useTheme();
20
+ const context = useContext(PickerContext);
21
+
22
+ const renderValueRef = useLatest(props.renderValue);
23
+
24
+ const styles = usePickerFieldStyles();
25
+
26
+ // const hasValue = !!context?.value || typeof context?.value === 'number';
27
+
28
+ const displayValue = useMemo(
29
+ () => renderValueRef.current?.(context?.value),
30
+ [context?.value, renderValueRef],
31
+ );
32
+
33
+ const textColor = displayValue
34
+ ? theme.palette.fontGray1
35
+ : theme.palette.fontGray3;
36
+
37
+ return (
38
+ <TouchableOpacity
39
+ style={[styles.container, context?.disabled && styles.disabled]}
40
+ onPress={context?.toggleVisible}
41
+ disabled={!!context?.disabled}
42
+ >
43
+ {safetyRenderChildren(displayValue ?? props.placeholder, displayText => (
44
+ <Text size={14} color={textColor}>
45
+ {displayText}
46
+ </Text>
47
+ ))}
48
+ </TouchableOpacity>
49
+ );
50
+ }
@@ -1,129 +1,53 @@
1
- import { useControllableValue, useMemoizedFn } from 'ahooks';
2
- import { useMemo, useRef, useState } from 'react';
1
+ import { useControllableValue, useMemoizedFn, useToggle } from 'ahooks';
2
+ import { type PropsWithChildren, useMemo, useState } from 'react';
3
3
 
4
- import { Button } from '../button';
5
- import { Flex } from '../flex';
6
- import { Modal, ModalHeader, type ModalHeaderBaseProps } from '../modal';
7
- import {
8
- type PickerOption,
9
- type PickerValue,
10
- PickerView,
11
- type PickerViewProps,
12
- } from '../picker-view';
13
- import { findOptionsByValue, getDefaultValues } from '../picker-view/utils/picker';
14
- import { Portal } from '../portal';
15
- import { PickerTrigger, type PickerTriggerControlledProps } from './picker-trigger';
4
+ import type { Any } from '#shared/utils';
16
5
 
17
- export interface PickerProps
18
- extends PickerTriggerControlledProps<PickerValue[]>,
19
- Omit<PickerViewProps, 'onChange' | 'value' | 'defaultValue'>,
20
- Pick<ModalHeaderBaseProps, 'title'> {
21
- /**
22
- * 自定义格式化函数
23
- */
24
- formatter?: (value: PickerValue[], options: PickerOption[]) => React.ReactNode;
25
- /** 确定按钮文案 */
26
- confirmText?: string;
27
- }
28
-
29
- export const Picker: React.FC<PickerProps> = ({
30
- placeholder = '请选择',
31
- disabled = false,
32
- title = '请选择',
33
- formatter,
34
- children,
35
- style,
36
- confirmText = '确定',
37
- ...restProps
38
- }) => {
39
- const [value, setValue] = useControllableValue<PickerValue[] | undefined>(
40
- restProps,
41
- {
42
- defaultValue: restProps.defaultValue,
43
- },
44
- );
45
-
46
- const [visible, setVisible] = useState(false);
47
-
48
- // 内部状态用于管理滚动时的临时值
49
- const [localValue, setLocalValue] = useState<PickerValue[]>(
50
- restProps.value ?? restProps.defaultValue ?? [],
51
- );
6
+ import type { PickerContentProps } from './picker-content';
7
+ import { PickerContext } from './picker-context';
8
+ import type { PickerFieldProps } from './picker-field';
52
9
 
53
- // ref 同步保存最新值,解决 useMemoizedFn 闭包问题
54
- const localValueRef = useRef(localValue);
55
- localValueRef.current = localValue;
56
-
57
- // 滑动时的临时 options(不用于 displayText)
58
- const localOptionsRef = useRef<PickerOption[]>([]);
59
-
60
- /** 格式化显示文案 - 根据 value 从 columns 计算 */
61
- const displayText = useMemo(() => {
62
- if (!value?.length) return '';
10
+ export interface PickerProps<T = Any> {
11
+ value?: T;
12
+ defaultValue?: T;
13
+ trigger?: string;
14
+ disabled?: boolean;
15
+ onChange?: (value: T) => void;
16
+ }
63
17
 
64
- // columns 计算对应的 options
65
- const options = findOptionsByValue(value, restProps.columns);
18
+ export interface PickerMergeProps<T>
19
+ extends PickerProps<T>,
20
+ PickerFieldProps<T>,
21
+ PickerContentProps {}
66
22
 
67
- if (formatter) {
68
- return formatter(value, options);
69
- }
70
- return options
71
- .map(opt => opt?.label)
72
- .filter(Boolean)
73
- .join(' / ');
74
- }, [value, restProps.columns, formatter]);
23
+ export const Picker: React.FC<PropsWithChildren<PickerProps>> = props => {
24
+ const [visible, { toggle: toggleVisible }] = useToggle();
25
+ const [disabled, setDisabled] = useState(props.disabled);
75
26
 
76
- const handleOpen = useMemoizedFn(() => {
77
- // 打开时同步外部 value 到内部
78
- // 如果 value 为空,使用 columns 的默认值(第一个选项)
79
- const initialValue = value?.length
80
- ? value
81
- : getDefaultValues(restProps.columns);
82
- setLocalValue(initialValue);
83
- setVisible(true);
27
+ const [value, setValue] = useControllableValue(props, {
28
+ trigger: props.trigger,
84
29
  });
85
30
 
86
- const handleClose = useMemoizedFn(() => {
87
- setVisible(false);
31
+ const _onChange = useMemoizedFn(value => {
32
+ setValue(value);
33
+ toggleVisible();
88
34
  });
89
35
 
90
- const handleChange = useMemoizedFn(
91
- (v: PickerValue[], options: PickerOption[]) => {
92
- setLocalValue(v);
93
- localOptionsRef.current = options;
94
- },
36
+ const contextValue = useMemo(
37
+ () => ({
38
+ value,
39
+ visible,
40
+ disabled,
41
+ setDisabled,
42
+ toggleVisible,
43
+ onChange: _onChange,
44
+ }),
45
+ [value, visible, disabled, toggleVisible, setDisabled, setValue],
95
46
  );
96
47
 
97
- const handleConfirm = useMemoizedFn(() => {
98
- setVisible(false);
99
- setValue(localValueRef.current);
100
- });
101
-
102
48
  return (
103
- <>
104
- <PickerTrigger
105
- displayText={displayText}
106
- placeholder={placeholder}
107
- disabled={disabled}
108
- style={style}
109
- onPress={handleOpen}
110
- >
111
- {children}
112
- </PickerTrigger>
113
- <Portal>
114
- <Modal
115
- visible={visible}
116
- position="bottom"
117
- safeAreaInsetBottom
118
- contentHeight={'50%'}
119
- >
120
- <Flex vertical align="stretch" gap={16} style={{ flex: 1, padding: 16 }}>
121
- <ModalHeader title={title} onClose={handleClose} />
122
- <PickerView {...restProps} value={localValue} onChange={handleChange} />
123
- <Button text={confirmText} size="large" onPress={handleConfirm} />
124
- </Flex>
125
- </Modal>
126
- </Portal>
127
- </>
49
+ <PickerContext.Provider value={contextValue}>
50
+ {props.children}
51
+ </PickerContext.Provider>
128
52
  );
129
53
  };
@@ -4,8 +4,6 @@ export const usePickerFieldStyles = createThemedStyles(theme => ({
4
4
  container: {
5
5
  flex: 1,
6
6
  alignSelf: 'stretch',
7
- },
8
- content: {
9
7
  backgroundColor: theme.palette.white,
10
8
  paddingVertical: 12,
11
9
  paddingHorizontal: 12,
@@ -0,0 +1,55 @@
1
+ import React, { useMemo,useState } from 'react';
2
+
3
+ import { Flex } from '../flex';
4
+ import { Modal, ModalHeader, type ModalProps } from '../modal';
5
+ import { Portal } from '../portal';
6
+ import { PickerTrigger, type PickerTriggerProps } from './picker-trigger';
7
+
8
+ export interface PickerController {
9
+ visible: boolean;
10
+
11
+ /** 控制弹窗 */
12
+ setVisible: (v: boolean) => void;
13
+ }
14
+
15
+ export interface BasePickerContainerProps {
16
+ children: (controller: PickerController) => React.ReactNode;
17
+ pickerTriggerProps?: PickerTriggerProps;
18
+ modalProps?: ModalProps;
19
+ }
20
+
21
+ export const BasePickerContainer = ({
22
+ children,
23
+ pickerTriggerProps,
24
+ modalProps,
25
+ }: BasePickerContainerProps) => {
26
+ const [visible, setVisible] = useState(false);
27
+
28
+ const controller = useMemo(
29
+ () => ({
30
+ visible,
31
+ setVisible,
32
+ }),
33
+ [visible],
34
+ );
35
+
36
+ return (
37
+ <>
38
+ <PickerTrigger {...pickerTriggerProps} />
39
+ <Portal>
40
+ <Modal
41
+ visible={visible}
42
+ position="bottom"
43
+ safeAreaInsetBottom
44
+ contentHeight={'50%'}
45
+ {...modalProps}
46
+ >
47
+ <Flex vertical align="stretch" gap={16} style={{ flex: 1, padding: 16 }}>
48
+ <ModalHeader onClose={() => setVisible(false)} />
49
+ {children(controller)}
50
+ </Flex>
51
+ </Modal>
52
+ </Portal>
53
+ </>
54
+ );
55
+ };
@@ -0,0 +1,2 @@
1
+ export * from './picker-backup';
2
+ export * from './picker-trigger';
@@ -0,0 +1,110 @@
1
+ import { useMemoizedFn } from 'ahooks';
2
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
3
+
4
+ import type { AnyObject } from '#shared/utils/types';
5
+
6
+ import { Button } from '../button';
7
+ import { Picker } from '../picker';
8
+ import type { PickerMergeProps } from '../picker/picker';
9
+ import {
10
+ type Column,
11
+ type PickerOption,
12
+ type PickerValue,
13
+ PickerView,
14
+ type PickerViewProps,
15
+ } from '../picker-view';
16
+ import { findOptionsByValue } from '../picker-view/utils/picker';
17
+ import { normalizeColumns } from './utils';
18
+
19
+ export interface FieldNames {
20
+ /** 选项文本对应的字段名 @default 'label' */
21
+ label?: string;
22
+ /** 选项值对应的字段名 @default 'value' */
23
+ value?: string;
24
+ /** 级联子选项对应的字段名 @default 'children' */
25
+ children?: string;
26
+ }
27
+
28
+ export type ColumnInput = Column | AnyObject | AnyObject[];
29
+
30
+ export interface PickerBackupProps
31
+ extends PickerMergeProps<PickerValue[]>,
32
+ Omit<PickerViewProps, 'onChange' | 'columns'> {
33
+ formatter?: (value: PickerValue[], options: PickerOption[]) => React.ReactNode;
34
+ confirmText?: string;
35
+ /** 选择器列,支持标准格式或自定义字段名格式(需配合 fieldNames 使用) */
36
+ columns: ColumnInput[];
37
+ /**
38
+ * 自定义字段名,用于映射数据中的属性到组件内部使用的标准字段
39
+ * @example { label: 'name', value: 'id', children: 'items' }
40
+ */
41
+ fieldNames?: FieldNames;
42
+ }
43
+
44
+ export const PickerBackup: React.FC<PickerBackupProps> = ({
45
+ placeholder = '请选择',
46
+ headerTitle = '请选择',
47
+ formatter,
48
+ confirmText = '确定',
49
+ columns: rawColumns,
50
+ fieldNames,
51
+ ...restProps
52
+ }) => {
53
+ const [draftValue, setDraftValue] = useState<PickerValue[]>();
54
+
55
+ const committedValueRef = useRef<PickerValue[]>(restProps.value ?? []);
56
+
57
+ const handleOpen = useCallback(() => {
58
+ setDraftValue(committedValueRef.current);
59
+ }, []);
60
+
61
+ /** 标准化列数据:将自定义字段名映射为内部标准格式 */
62
+ const columns = useMemo(
63
+ () => normalizeColumns(rawColumns, fieldNames),
64
+ [rawColumns, fieldNames],
65
+ );
66
+
67
+ /** 格式化显示文案 - 根据 value 从 columns 计算 */
68
+ const _renderValue = useMemoizedFn(value => {
69
+ if (!value?.length) return;
70
+
71
+ const options = findOptionsByValue(value, columns);
72
+
73
+ if (formatter) {
74
+ return formatter(value, options);
75
+ }
76
+ return options
77
+ .map(opt => opt?.label)
78
+ .filter(Boolean)
79
+ .join(' / ');
80
+ });
81
+
82
+ return (
83
+ <Picker {...restProps}>
84
+ <Picker.Field placeholder={placeholder} renderValue={_renderValue} />
85
+ <Picker.Content headerTitle={headerTitle} onOpen={handleOpen}>
86
+ {({ onChange, value: contextValue }) => {
87
+ // 同步 ref:始终保持最新的已提交值
88
+ committedValueRef.current = contextValue ?? [];
89
+ return (
90
+ <>
91
+ <PickerView
92
+ columns={columns}
93
+ itemHeight={restProps.itemHeight}
94
+ value={draftValue}
95
+ onChange={setDraftValue}
96
+ />
97
+ <Button
98
+ text={confirmText}
99
+ size="large"
100
+ onPress={() => {
101
+ onChange?.(draftValue);
102
+ }}
103
+ />
104
+ </>
105
+ );
106
+ }}
107
+ </Picker.Content>
108
+ </Picker>
109
+ );
110
+ };
@@ -0,0 +1,125 @@
1
+ import { useControllableValue, useMemoizedFn } from 'ahooks';
2
+ import { useMemo, useRef, useState } from 'react';
3
+
4
+ import { Button } from '../button';
5
+ import { Flex } from '../flex';
6
+ import { Modal, ModalHeader, type ModalHeaderBaseProps } from '../modal';
7
+ import {
8
+ type PickerOption,
9
+ type PickerValue,
10
+ PickerView,
11
+ type PickerViewProps,
12
+ } from '../picker-view';
13
+ import { findOptionsByValue, getDefaultValues } from '../picker-view/utils/picker';
14
+ import { Portal } from '../portal';
15
+ import { PickerTrigger, type PickerTriggerControlledProps } from './picker-trigger';
16
+
17
+ export interface PickerProps
18
+ extends PickerTriggerControlledProps<PickerValue[]>,
19
+ Omit<PickerViewProps, 'onChange' | 'value' | 'defaultValue'>,
20
+ Pick<ModalHeaderBaseProps, 'title'> {
21
+ /**
22
+ * 自定义格式化函数
23
+ */
24
+ formatter?: (value: PickerValue[], options: PickerOption[]) => React.ReactNode;
25
+ /** 确定按钮文案 */
26
+ confirmText?: string;
27
+ }
28
+
29
+ export const Picker: React.FC<PickerProps> = ({
30
+ placeholder = '请选择',
31
+ disabled = false,
32
+ title = '请选择',
33
+ formatter,
34
+ children,
35
+ style,
36
+ confirmText = '确定',
37
+ ...restProps
38
+ }) => {
39
+ const [value, setValue] = useControllableValue<PickerValue[] | undefined>(
40
+ restProps,
41
+ {
42
+ defaultValue: restProps.defaultValue,
43
+ },
44
+ );
45
+
46
+ const [visible, setVisible] = useState(false);
47
+
48
+ // 内部状态用于管理滚动时的临时值
49
+ const [localValue, setLocalValue] = useState<PickerValue[]>(
50
+ restProps.value ?? restProps.defaultValue ?? [],
51
+ );
52
+
53
+ // 滑动时的临时 options(不用于 displayText)
54
+ const localOptionsRef = useRef<PickerOption[]>([]);
55
+
56
+ /** 格式化显示文案 - 根据 value 从 columns 计算 */
57
+ const displayText = useMemo(() => {
58
+ if (!value?.length) return '';
59
+
60
+ // 从 columns 计算对应的 options
61
+ const options = findOptionsByValue(value, restProps.columns);
62
+
63
+ if (formatter) {
64
+ return formatter(value, options);
65
+ }
66
+ return options
67
+ .map(opt => opt?.label)
68
+ .filter(Boolean)
69
+ .join(' / ');
70
+ }, [value, restProps.columns, formatter]);
71
+
72
+ const handleOpen = useMemoizedFn(() => {
73
+ // 打开时同步外部 value 到内部
74
+ // 如果 value 为空,使用 columns 的默认值(第一个选项)
75
+ const initialValue = value?.length
76
+ ? value
77
+ : getDefaultValues(restProps.columns);
78
+ setLocalValue(initialValue);
79
+ setVisible(true);
80
+ });
81
+
82
+ const handleClose = useMemoizedFn(() => {
83
+ setVisible(false);
84
+ });
85
+
86
+ const handleChange = useMemoizedFn(
87
+ (v: PickerValue[], options: PickerOption[]) => {
88
+ setLocalValue(v);
89
+ localOptionsRef.current = options;
90
+ },
91
+ );
92
+
93
+ const handleConfirm = useMemoizedFn(() => {
94
+ setVisible(false);
95
+ setValue(localValue);
96
+ });
97
+
98
+ return (
99
+ <>
100
+ <PickerTrigger
101
+ displayText={displayText}
102
+ placeholder={placeholder}
103
+ disabled={disabled}
104
+ style={style}
105
+ onPress={handleOpen}
106
+ >
107
+ {children}
108
+ </PickerTrigger>
109
+ <Portal>
110
+ <Modal
111
+ visible={visible}
112
+ position="bottom"
113
+ safeAreaInsetBottom
114
+ contentHeight={'50%'}
115
+ >
116
+ <Flex vertical align="stretch" gap={16} style={{ flex: 1, padding: 16 }}>
117
+ <ModalHeader title={title} onClose={handleClose} />
118
+ <PickerView {...restProps} value={localValue} onChange={handleChange} />
119
+ <Button text={confirmText} size="large" onPress={handleConfirm} />
120
+ </Flex>
121
+ </Modal>
122
+ </Portal>
123
+ </>
124
+ );
125
+ };
@@ -5,8 +5,8 @@ import { useTheme } from '#native-provider';
5
5
  import type { AnyObject } from '#shared/utils/types';
6
6
 
7
7
  import { Flex } from '../flex';
8
+ import { usePickerFieldStyles } from '../picker/style';
8
9
  import { Text } from '../text';
9
- import { usePickerFieldStyles } from './style';
10
10
 
11
11
  /** Picker 基础触发器 Props */
12
12
  export interface PickerTriggerProps {
@@ -65,7 +65,7 @@ export const PickerTrigger: React.FC<PickerTriggerProps> = ({
65
65
  <Flex
66
66
  align="center"
67
67
  gap={theme.spacing.sm}
68
- style={[styles.content, disabled && styles.disabled, style]}
68
+ style={[disabled && styles.disabled, style]}
69
69
  >
70
70
  {React.isValidElement(displayText) ? (
71
71
  displayText
@@ -0,0 +1,14 @@
1
+ import { createThemedStyles } from '#native-provider';
2
+
3
+ export const usePickerFieldStyles = createThemedStyles(theme => ({
4
+ container: {
5
+ flex: 1,
6
+ alignSelf: 'stretch',
7
+ backgroundColor: theme.palette.white,
8
+ paddingVertical: 12,
9
+ paddingHorizontal: 12,
10
+ },
11
+ disabled: {
12
+ backgroundColor: theme.palette.blueGray2,
13
+ },
14
+ }));
@@ -0,0 +1,62 @@
1
+ import type { AnyObject } from '#shared/utils/types';
2
+
3
+ import {
4
+ type Column,
5
+ type PickerOption,
6
+ type PickerValue,
7
+ } from '../picker-view/types';
8
+ import type { ColumnInput, FieldNames } from './picker-backup';
9
+
10
+ /** 默认字段名 */
11
+ const DEFAULT_FIELD_NAMES: Required<FieldNames> = {
12
+ label: 'label',
13
+ value: 'value',
14
+ children: 'children',
15
+ };
16
+
17
+ /**
18
+ * 将自定义字段名的数据标准化为内部使用的 { label, value, children } 格式
19
+ * 如果 fieldNames 与默认值相同,则直接返回原始数据(零开销)
20
+ */
21
+ export const normalizeColumns = (
22
+ columns: ColumnInput[],
23
+ fieldNames?: FieldNames,
24
+ ): Column[] => {
25
+ if (!fieldNames) return columns as Column[];
26
+
27
+ const merged = { ...DEFAULT_FIELD_NAMES, ...fieldNames };
28
+
29
+ // 如果所有字段名都和默认值一样,无需转换
30
+ if (
31
+ merged.label === 'label' &&
32
+ merged.value === 'value' &&
33
+ merged.children === 'children'
34
+ ) {
35
+ return columns as Column[];
36
+ }
37
+
38
+ const normalizeOption = (item: AnyObject): PickerOption => {
39
+ const normalized = { ...item } as AnyObject & PickerOption;
40
+ if (merged.label !== 'label') {
41
+ normalized.label = item[merged.label] as PickerValue;
42
+ }
43
+ if (merged.value !== 'value') {
44
+ normalized.value = item[merged.value] as PickerValue;
45
+ }
46
+ if (merged.children !== 'children' && item[merged.children]) {
47
+ normalized.children = (item[merged.children] as AnyObject[]).map(
48
+ normalizeOption,
49
+ );
50
+ } else if (item.children) {
51
+ normalized.children = (item.children as AnyObject[]).map(normalizeOption);
52
+ }
53
+ return normalized;
54
+ };
55
+
56
+ return columns.map(column => {
57
+ if (Array.isArray(column)) {
58
+ return column.map(normalizeOption);
59
+ }
60
+ return normalizeOption(column);
61
+ }) as Column[];
62
+ };
@@ -65,6 +65,26 @@ export const PickerViewColumn: React.FC<PickerViewColumnProps> = memo(
65
65
  options.findIndex(item => item.value === value),
66
66
  );
67
67
 
68
+ /**
69
+ * 自初始化:当 value 为空或不在 options 中时,主动上报第一个可用选项
70
+ * 这从根本上解决了「不滚动就拿不到值」的问题
71
+ */
72
+ useEffect(() => {
73
+ if (options.length === 0) return;
74
+
75
+ const hasValidValue =
76
+ value !== undefined &&
77
+ value !== null &&
78
+ options.some(item => item.value === value);
79
+
80
+ if (!hasValidValue) {
81
+ const defaultIndex = findUsableOptionIndex(options, true, 0, false);
82
+ if (defaultIndex >= 0) {
83
+ onChange?.(options[defaultIndex]);
84
+ }
85
+ }
86
+ }, [options, value, onChange]);
87
+
68
88
  useEffect(() => {
69
89
  if (selectedIndex >= 0 && selectedIndex < options.length) {
70
90
  flatListRef.current?.scrollToIndex({