@unif/react-native-ui 0.1.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 (227) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +103 -0
  3. package/lib/module/README.md +99 -0
  4. package/lib/module/action-sheet/index.js +63 -0
  5. package/lib/module/action-sheet/index.js.map +1 -0
  6. package/lib/module/action-sheet/index.md +69 -0
  7. package/lib/module/action-sheet/style/index.js +38 -0
  8. package/lib/module/action-sheet/style/index.js.map +1 -0
  9. package/lib/module/avatar/index.js +42 -0
  10. package/lib/module/avatar/index.js.map +1 -0
  11. package/lib/module/avatar/index.md +51 -0
  12. package/lib/module/avatar/style/index.js +17 -0
  13. package/lib/module/avatar/style/index.js.map +1 -0
  14. package/lib/module/button/index.js +52 -0
  15. package/lib/module/button/index.js.map +1 -0
  16. package/lib/module/button/index.md +90 -0
  17. package/lib/module/button/style/index.js +94 -0
  18. package/lib/module/button/style/index.js.map +1 -0
  19. package/lib/module/center/index.js +21 -0
  20. package/lib/module/center/index.js.map +1 -0
  21. package/lib/module/center/index.md +28 -0
  22. package/lib/module/center/style/index.js +10 -0
  23. package/lib/module/center/style/index.js.map +1 -0
  24. package/lib/module/chip/index.js +59 -0
  25. package/lib/module/chip/index.js.map +1 -0
  26. package/lib/module/chip/index.md +60 -0
  27. package/lib/module/chip/style/index.js +40 -0
  28. package/lib/module/chip/style/index.js.map +1 -0
  29. package/lib/module/column/index.js +30 -0
  30. package/lib/module/column/index.js.map +1 -0
  31. package/lib/module/column/index.md +34 -0
  32. package/lib/module/column/style/index.js +9 -0
  33. package/lib/module/column/style/index.js.map +1 -0
  34. package/lib/module/divider/index.js +21 -0
  35. package/lib/module/divider/index.js.map +1 -0
  36. package/lib/module/divider/index.md +44 -0
  37. package/lib/module/divider/style/index.js +17 -0
  38. package/lib/module/divider/style/index.js.map +1 -0
  39. package/lib/module/hooks/index.js +4 -0
  40. package/lib/module/hooks/index.js.map +1 -0
  41. package/lib/module/hooks/index.md +71 -0
  42. package/lib/module/hooks/useMergeStyles.js +27 -0
  43. package/lib/module/hooks/useMergeStyles.js.map +1 -0
  44. package/lib/module/index.js +29 -0
  45. package/lib/module/index.js.map +1 -0
  46. package/lib/module/input/index.js +69 -0
  47. package/lib/module/input/index.js.map +1 -0
  48. package/lib/module/input/index.md +73 -0
  49. package/lib/module/input/style/index.js +27 -0
  50. package/lib/module/input/style/index.js.map +1 -0
  51. package/lib/module/list-item/index.js +69 -0
  52. package/lib/module/list-item/index.js.map +1 -0
  53. package/lib/module/list-item/index.md +101 -0
  54. package/lib/module/list-item/style/index.js +39 -0
  55. package/lib/module/list-item/style/index.js.map +1 -0
  56. package/lib/module/package.json +1 -0
  57. package/lib/module/popover/index.js +60 -0
  58. package/lib/module/popover/index.js.map +1 -0
  59. package/lib/module/popover/index.md +57 -0
  60. package/lib/module/popover/style/index.js +22 -0
  61. package/lib/module/popover/style/index.js.map +1 -0
  62. package/lib/module/row/index.js +31 -0
  63. package/lib/module/row/index.js.map +1 -0
  64. package/lib/module/row/index.md +46 -0
  65. package/lib/module/row/style/index.js +15 -0
  66. package/lib/module/row/style/index.js.map +1 -0
  67. package/lib/module/space/index.js +20 -0
  68. package/lib/module/space/index.js.map +1 -0
  69. package/lib/module/space/index.md +31 -0
  70. package/lib/module/space/style/index.js +9 -0
  71. package/lib/module/space/style/index.js.map +1 -0
  72. package/lib/module/text/index.js +30 -0
  73. package/lib/module/text/index.js.map +1 -0
  74. package/lib/module/text/index.md +63 -0
  75. package/lib/module/text/style/index.js +51 -0
  76. package/lib/module/text/style/index.js.map +1 -0
  77. package/lib/module/theme/config.js +27 -0
  78. package/lib/module/theme/config.js.map +1 -0
  79. package/lib/module/theme/tokens.js +67 -0
  80. package/lib/module/theme/tokens.js.map +1 -0
  81. package/lib/module/touchable/index.js +35 -0
  82. package/lib/module/touchable/index.js.map +1 -0
  83. package/lib/module/touchable/index.md +42 -0
  84. package/lib/module/touchable/style/index.js +9 -0
  85. package/lib/module/touchable/style/index.js.map +1 -0
  86. package/lib/module/wave-animation/index.js +67 -0
  87. package/lib/module/wave-animation/index.js.map +1 -0
  88. package/lib/module/wave-animation/index.md +50 -0
  89. package/lib/typescript/jest.setup.d.ts +1 -0
  90. package/lib/typescript/jest.setup.d.ts.map +1 -0
  91. package/lib/typescript/package.json +1 -0
  92. package/lib/typescript/src/action-sheet/index.d.ts +22 -0
  93. package/lib/typescript/src/action-sheet/index.d.ts.map +1 -0
  94. package/lib/typescript/src/action-sheet/style/index.d.ts +16 -0
  95. package/lib/typescript/src/action-sheet/style/index.d.ts.map +1 -0
  96. package/lib/typescript/src/avatar/index.d.ts +19 -0
  97. package/lib/typescript/src/avatar/index.d.ts.map +1 -0
  98. package/lib/typescript/src/avatar/style/index.d.ts +7 -0
  99. package/lib/typescript/src/avatar/style/index.d.ts.map +1 -0
  100. package/lib/typescript/src/button/index.d.ts +24 -0
  101. package/lib/typescript/src/button/index.d.ts.map +1 -0
  102. package/lib/typescript/src/button/style/index.d.ts +16 -0
  103. package/lib/typescript/src/button/style/index.d.ts.map +1 -0
  104. package/lib/typescript/src/center/index.d.ts +11 -0
  105. package/lib/typescript/src/center/index.d.ts.map +1 -0
  106. package/lib/typescript/src/center/style/index.d.ts +5 -0
  107. package/lib/typescript/src/center/style/index.d.ts.map +1 -0
  108. package/lib/typescript/src/chip/index.d.ts +22 -0
  109. package/lib/typescript/src/chip/index.d.ts.map +1 -0
  110. package/lib/typescript/src/chip/style/index.d.ts +10 -0
  111. package/lib/typescript/src/chip/style/index.d.ts.map +1 -0
  112. package/lib/typescript/src/column/index.d.ts +20 -0
  113. package/lib/typescript/src/column/index.d.ts.map +1 -0
  114. package/lib/typescript/src/column/style/index.d.ts +4 -0
  115. package/lib/typescript/src/column/style/index.d.ts.map +1 -0
  116. package/lib/typescript/src/divider/index.d.ts +13 -0
  117. package/lib/typescript/src/divider/index.d.ts.map +1 -0
  118. package/lib/typescript/src/divider/style/index.d.ts +9 -0
  119. package/lib/typescript/src/divider/style/index.d.ts.map +1 -0
  120. package/lib/typescript/src/hooks/index.d.ts +2 -0
  121. package/lib/typescript/src/hooks/index.d.ts.map +1 -0
  122. package/lib/typescript/src/hooks/useMergeStyles.d.ts +13 -0
  123. package/lib/typescript/src/hooks/useMergeStyles.d.ts.map +1 -0
  124. package/lib/typescript/src/index.d.ts +39 -0
  125. package/lib/typescript/src/index.d.ts.map +1 -0
  126. package/lib/typescript/src/input/index.d.ts +29 -0
  127. package/lib/typescript/src/input/index.d.ts.map +1 -0
  128. package/lib/typescript/src/input/style/index.d.ts +8 -0
  129. package/lib/typescript/src/input/style/index.d.ts.map +1 -0
  130. package/lib/typescript/src/list-item/index.d.ts +25 -0
  131. package/lib/typescript/src/list-item/index.d.ts.map +1 -0
  132. package/lib/typescript/src/list-item/style/index.d.ts +17 -0
  133. package/lib/typescript/src/list-item/style/index.d.ts.map +1 -0
  134. package/lib/typescript/src/popover/index.d.ts +27 -0
  135. package/lib/typescript/src/popover/index.d.ts.map +1 -0
  136. package/lib/typescript/src/popover/style/index.d.ts +7 -0
  137. package/lib/typescript/src/popover/style/index.d.ts.map +1 -0
  138. package/lib/typescript/src/row/index.d.ts +22 -0
  139. package/lib/typescript/src/row/index.d.ts.map +1 -0
  140. package/lib/typescript/src/row/style/index.d.ts +8 -0
  141. package/lib/typescript/src/row/style/index.d.ts.map +1 -0
  142. package/lib/typescript/src/space/index.d.ts +15 -0
  143. package/lib/typescript/src/space/index.d.ts.map +1 -0
  144. package/lib/typescript/src/space/style/index.d.ts +7 -0
  145. package/lib/typescript/src/space/style/index.d.ts.map +1 -0
  146. package/lib/typescript/src/text/index.d.ts +18 -0
  147. package/lib/typescript/src/text/index.d.ts.map +1 -0
  148. package/lib/typescript/src/text/style/index.d.ts +35 -0
  149. package/lib/typescript/src/text/style/index.d.ts.map +1 -0
  150. package/lib/typescript/src/theme/config.d.ts +18 -0
  151. package/lib/typescript/src/theme/config.d.ts.map +1 -0
  152. package/lib/typescript/src/theme/tokens.d.ts +61 -0
  153. package/lib/typescript/src/theme/tokens.d.ts.map +1 -0
  154. package/lib/typescript/src/touchable/index.d.ts +19 -0
  155. package/lib/typescript/src/touchable/index.d.ts.map +1 -0
  156. package/lib/typescript/src/touchable/style/index.d.ts +4 -0
  157. package/lib/typescript/src/touchable/style/index.d.ts.map +1 -0
  158. package/lib/typescript/src/wave-animation/index.d.ts +20 -0
  159. package/lib/typescript/src/wave-animation/index.d.ts.map +1 -0
  160. package/lib/typescript/tests/component/component.coverage.test.d.ts +2 -0
  161. package/lib/typescript/tests/component/component.coverage.test.d.ts.map +1 -0
  162. package/lib/typescript/tests/component/component.interaction.test.d.ts +2 -0
  163. package/lib/typescript/tests/component/component.interaction.test.d.ts.map +1 -0
  164. package/lib/typescript/tests/component/component.snapshot.test.d.ts +2 -0
  165. package/lib/typescript/tests/component/component.snapshot.test.d.ts.map +1 -0
  166. package/lib/typescript/tests/e2e/app.e2e.test.d.ts +2 -0
  167. package/lib/typescript/tests/e2e/app.e2e.test.d.ts.map +1 -0
  168. package/lib/typescript/tests/integration/ui.integration.test.d.ts +2 -0
  169. package/lib/typescript/tests/integration/ui.integration.test.d.ts.map +1 -0
  170. package/lib/typescript/tests/unit/mergeStyles.unit.test.d.ts +2 -0
  171. package/lib/typescript/tests/unit/mergeStyles.unit.test.d.ts.map +1 -0
  172. package/lib/typescript/tests/unit/public-api.unit.test.d.ts +2 -0
  173. package/lib/typescript/tests/unit/public-api.unit.test.d.ts.map +1 -0
  174. package/lib/typescript/tests/unit/theme-config.unit.test.d.ts +2 -0
  175. package/lib/typescript/tests/unit/theme-config.unit.test.d.ts.map +1 -0
  176. package/package.json +134 -0
  177. package/src/README.md +99 -0
  178. package/src/action-sheet/index.md +69 -0
  179. package/src/action-sheet/index.tsx +85 -0
  180. package/src/action-sheet/style/index.tsx +52 -0
  181. package/src/avatar/index.md +51 -0
  182. package/src/avatar/index.tsx +56 -0
  183. package/src/avatar/style/index.tsx +21 -0
  184. package/src/button/index.md +90 -0
  185. package/src/button/index.tsx +86 -0
  186. package/src/button/style/index.tsx +67 -0
  187. package/src/center/index.md +28 -0
  188. package/src/center/index.tsx +18 -0
  189. package/src/center/style/index.tsx +8 -0
  190. package/src/chip/index.md +60 -0
  191. package/src/chip/index.tsx +80 -0
  192. package/src/chip/style/index.tsx +47 -0
  193. package/src/column/index.md +34 -0
  194. package/src/column/index.tsx +43 -0
  195. package/src/column/style/index.tsx +7 -0
  196. package/src/divider/index.md +44 -0
  197. package/src/divider/index.tsx +30 -0
  198. package/src/divider/style/index.tsx +13 -0
  199. package/src/hooks/index.md +71 -0
  200. package/src/hooks/index.ts +1 -0
  201. package/src/hooks/useMergeStyles.ts +27 -0
  202. package/src/index.tsx +49 -0
  203. package/src/input/index.md +73 -0
  204. package/src/input/index.tsx +95 -0
  205. package/src/input/style/index.tsx +32 -0
  206. package/src/list-item/index.md +101 -0
  207. package/src/list-item/index.tsx +91 -0
  208. package/src/list-item/style/index.tsx +41 -0
  209. package/src/popover/index.md +57 -0
  210. package/src/popover/index.tsx +80 -0
  211. package/src/popover/style/index.tsx +23 -0
  212. package/src/row/index.md +46 -0
  213. package/src/row/index.tsx +47 -0
  214. package/src/row/style/index.tsx +14 -0
  215. package/src/space/index.md +31 -0
  216. package/src/space/index.tsx +28 -0
  217. package/src/space/style/index.tsx +3 -0
  218. package/src/text/index.md +63 -0
  219. package/src/text/index.tsx +45 -0
  220. package/src/text/style/index.tsx +32 -0
  221. package/src/theme/config.ts +26 -0
  222. package/src/theme/tokens.ts +66 -0
  223. package/src/touchable/index.md +42 -0
  224. package/src/touchable/index.tsx +45 -0
  225. package/src/touchable/style/index.tsx +5 -0
  226. package/src/wave-animation/index.md +50 -0
  227. package/src/wave-animation/index.tsx +93 -0
@@ -0,0 +1,71 @@
1
+ # Hooks
2
+
3
+ 基础组件库的工具 hooks。
4
+
5
+ ## mergeStyles
6
+
7
+ 按 slot 维度合并多来源样式的纯函数。借鉴 antd v6 `useMergeSemantic`(`components/_util/hooks/useMergeSemantic.ts:69-80`)。
8
+
9
+ ### 何时使用
10
+
11
+ - 组件内部合并默认样式 + 变体样式 + 用户传入的 `styles` 语义插槽
12
+ - 构建支持 Semantic Styles 的自定义组件时
13
+
14
+ ### 代码演示
15
+
16
+ ```tsx
17
+ import {mergeStyles} from '../hooks';
18
+
19
+ type MySemanticStyles = {
20
+ root?: ViewStyle;
21
+ content?: TextStyle;
22
+ };
23
+
24
+ const DEFAULT: MySemanticStyles = {
25
+ root: {padding: 16},
26
+ content: {fontSize: 14, color: tokens.colorText},
27
+ };
28
+
29
+ const MyComponent = ({styles: userStyles}: {styles?: Partial<MySemanticStyles>}) => {
30
+ // ✅ 配合 useMemo,仅 userStyles 变化时重算
31
+ const s = useMemo(
32
+ () => mergeStyles(DEFAULT, userStyles),
33
+ [userStyles],
34
+ );
35
+
36
+ return (
37
+ <View style={s.root}>
38
+ <Text style={s.content}>...</Text>
39
+ </View>
40
+ );
41
+ };
42
+ ```
43
+
44
+ ### API
45
+
46
+ ```ts
47
+ function mergeStyles<T extends Record<string, StyleValue | undefined>>(
48
+ ...sources: (Partial<T> | undefined)[]
49
+ ): T
50
+ ```
51
+
52
+ | 参数 | 说明 | 类型 |
53
+ |------|------|------|
54
+ | sources | 多个样式源,按顺序合并 | `(Partial<T> \| undefined)[]` |
55
+
56
+ ### 合并规则
57
+
58
+ - 按 slot key 维度合并(`root`、`content` 等分别合并)
59
+ - 同一 slot 内使用对象展开(`{...prev, ...next}`),后传入的覆盖先传入的
60
+ - `undefined` 源自动跳过
61
+ - 纯函数,不包含 `useMemo`,调用方自行控制缓存
62
+
63
+ ### 与 antd v6 对比
64
+
65
+ | 特性 | antd v6 `mergeStyles` | RN 版 `mergeStyles` |
66
+ |------|----------------------|---------------------|
67
+ | classNames 合并 | ✅ | ❌(RN 无 CSS 类名) |
68
+ | schema 嵌套 | ✅ | ❌(当前不需要) |
69
+ | 函数形式 style | ✅ | ❌(不需要动态计算) |
70
+ | slot 展开合并 | ✅ | ✅(核心逻辑一致) |
71
+ | 纯函数 | ✅ | ✅ |
@@ -0,0 +1 @@
1
+ export { mergeStyles } from './useMergeStyles';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 语义样式合并
3
+ *
4
+ * 纯函数版本,调用方通过 useMemo 控制缓存策略。
5
+ */
6
+
7
+ import type { ImageStyle, TextStyle, ViewStyle } from 'react-native';
8
+
9
+ type StyleValue = ViewStyle | TextStyle | ImageStyle;
10
+
11
+ /**
12
+ * 按 slot 维度合并多来源样式。
13
+ */
14
+ export function mergeStyles<T extends Record<string, StyleValue | undefined>>(
15
+ ...sources: (Partial<T> | undefined)[]
16
+ ): T {
17
+ const merged = {} as Record<string, StyleValue>;
18
+ for (const source of sources) {
19
+ if (!source) {
20
+ continue;
21
+ }
22
+ for (const key of Object.keys(source)) {
23
+ merged[key] = { ...(merged[key] as object), ...(source[key] as object) };
24
+ }
25
+ }
26
+ return merged as T;
27
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,49 @@
1
+ // Theme
2
+ export { configure } from './theme/config';
3
+ export type { ThemeConfig } from './theme/config';
4
+ export { tokens } from './theme/tokens';
5
+
6
+ // Hooks
7
+ export { mergeStyles } from './hooks';
8
+
9
+ // Layout
10
+ export { default as Row } from './row';
11
+ export type { RowProps } from './row';
12
+ export { default as Column } from './column';
13
+ export type { ColumnProps } from './column';
14
+ export { default as Center } from './center';
15
+ export type { CenterProps } from './center';
16
+ export { default as Space } from './space';
17
+ export type { SpaceProps } from './space';
18
+
19
+ // Components
20
+ export { default as Text } from './text';
21
+ export type { TextProps } from './text';
22
+ export { default as Button } from './button';
23
+ export type { ButtonProps } from './button';
24
+ export type { ButtonSemanticStyles } from './button/style';
25
+ export { default as Divider } from './divider';
26
+ export { default as ListItem } from './list-item';
27
+ export { default as Touchable } from './touchable';
28
+ export type { TouchableProps } from './touchable';
29
+
30
+ // Atoms
31
+ export { default as Avatar } from './avatar';
32
+ export type { AvatarProps } from './avatar';
33
+ export type { AvatarSemanticStyles } from './avatar/style';
34
+ export { default as WaveAnimation } from './wave-animation';
35
+ export type { WaveAnimationProps } from './wave-animation';
36
+
37
+ // Base (new)
38
+ export { default as Input } from './input';
39
+ export type { InputProps } from './input';
40
+ export type { InputSemanticStyles } from './input/style';
41
+ export { default as Chip } from './chip';
42
+ export type { ChipProps } from './chip';
43
+ export type { ChipSemanticStyles } from './chip/style';
44
+ export { default as ActionSheet } from './action-sheet';
45
+ export type { ActionSheetProps } from './action-sheet';
46
+ export type { ActionSheetOption, ActionSheetSemanticStyles } from './action-sheet/style';
47
+ export { default as Popover } from './popover';
48
+ export type { PopoverProps } from './popover';
49
+ export type { PopoverSemanticStyles } from './popover/style';
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: Input
3
+ nav:
4
+ title: 组件
5
+ order: 1
6
+ group:
7
+ title: 基础
8
+ order: 2
9
+ ---
10
+
11
+ # Input 输入框
12
+
13
+ TextInput 容器,支持多行输入、底部工具栏和自动聚焦。
14
+
15
+ ## 何时使用
16
+
17
+ - 需要文本输入时
18
+ - 需要带工具栏的输入框时
19
+ - 聊天输入场景
20
+
21
+ ## 代码演示
22
+
23
+ ```tsx
24
+ import { Input, Icon } from '@unif/react-native-ui';
25
+
26
+ // 基本用法
27
+ <Input
28
+ value={text}
29
+ onChangeText={setText}
30
+ placeholder="请输入..."
31
+ />
32
+
33
+ // 多行 + 工具栏
34
+ <Input
35
+ multiline
36
+ maxHeight={100}
37
+ autoFocus
38
+ toolbar={
39
+ <View style={{ flexDirection: 'row' }}>
40
+ <Icon name="mic-outline" size={24} />
41
+ </View>
42
+ }
43
+ />
44
+ ```
45
+
46
+ ## API
47
+
48
+ | 属性 | 说明 | 类型 | 默认值 |
49
+ | --------------- | ---------------- | ---------------------- | ------- |
50
+ | value | 输入值 | `string` | - |
51
+ | onChangeText | 文本变化回调 | `(text: string) => void` | - |
52
+ | placeholder | 占位文本 | `string` | - |
53
+ | multiline | 多行模式 | `boolean` | `false` |
54
+ | maxLength | 最大字符数 | `number` | - |
55
+ | maxHeight | 最大高度 | `number` | - |
56
+ | autoFocus | 自动聚焦 | `boolean` | - |
57
+ | returnKeyType | 回车键类型 | `ReturnKeyTypeOptions` | - |
58
+ | onSubmitEditing | 提交回调 | `() => void` | - |
59
+ | onBlur | 失焦回调 | `() => void` | - |
60
+ | onFocus | 聚焦回调 | `() => void` | - |
61
+ | disabled | 禁用状态 | `boolean` | - |
62
+ | toolbar | 底部工具栏 | `ReactNode` | - |
63
+ | style | 容器样式 | `ViewStyle` | - |
64
+ | styles | 语义样式 | `InputSemanticStyles` | - |
65
+ | testID | 测试标识 | `string` | - |
66
+
67
+ ## Semantic Styles
68
+
69
+ | Slot | 说明 |
70
+ | ------- | -------------- |
71
+ | root | 外层容器 |
72
+ | input | TextInput 本身 |
73
+ | toolbar | 工具栏容器 |
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Input 基础组件
3
+ *
4
+ * TextInput 容器,支持多行、工具栏插槽、自动聚焦。
5
+ */
6
+
7
+ import React, { useRef, useEffect, useMemo, forwardRef } from 'react';
8
+ import { View, TextInput } from 'react-native';
9
+ import type { ViewStyle, ReturnKeyTypeOptions } from 'react-native';
10
+ import { mergeStyles } from '../hooks';
11
+ import { tokens } from '../theme/tokens';
12
+ import type { InputSemanticStyles } from './style';
13
+ import { DEFAULT_STYLES } from './style';
14
+
15
+ export interface InputProps {
16
+ value?: string;
17
+ onChangeText?: (text: string) => void;
18
+ placeholder?: string;
19
+ multiline?: boolean;
20
+ maxLength?: number;
21
+ maxHeight?: number;
22
+ autoFocus?: boolean;
23
+ returnKeyType?: ReturnKeyTypeOptions;
24
+ onSubmitEditing?: () => void;
25
+ onBlur?: () => void;
26
+ onFocus?: () => void;
27
+ disabled?: boolean;
28
+ toolbar?: React.ReactNode;
29
+ style?: ViewStyle;
30
+ styles?: Partial<InputSemanticStyles>;
31
+ testID?: string;
32
+ }
33
+
34
+ type InputRef = React.ElementRef<typeof TextInput>;
35
+
36
+ const Input = forwardRef<InputRef, InputProps>(({
37
+ value,
38
+ onChangeText,
39
+ placeholder,
40
+ multiline = false,
41
+ maxLength,
42
+ maxHeight,
43
+ autoFocus,
44
+ returnKeyType,
45
+ onSubmitEditing,
46
+ onBlur,
47
+ onFocus,
48
+ disabled,
49
+ toolbar,
50
+ style,
51
+ styles: semanticStyles,
52
+ testID,
53
+ }, ref) => {
54
+ const inputRef = useRef<InputRef>(null);
55
+
56
+ React.useImperativeHandle(ref, () => inputRef.current as InputRef);
57
+
58
+ const s = useMemo(
59
+ () => mergeStyles<InputSemanticStyles>(DEFAULT_STYLES, semanticStyles),
60
+ [semanticStyles],
61
+ );
62
+
63
+ useEffect(() => {
64
+ if (autoFocus) {
65
+ inputRef.current?.focus();
66
+ }
67
+ }, [autoFocus]);
68
+
69
+ return (
70
+ <View style={[s.root, style]}>
71
+ <TextInput
72
+ ref={inputRef}
73
+ testID={testID}
74
+ style={[s.input, maxHeight ? { maxHeight } : undefined]}
75
+ value={value}
76
+ onChangeText={onChangeText}
77
+ placeholder={placeholder}
78
+ placeholderTextColor={tokens.colorTextPlaceholder}
79
+ multiline={multiline}
80
+ maxLength={maxLength}
81
+ returnKeyType={returnKeyType}
82
+ onSubmitEditing={onSubmitEditing}
83
+ onBlur={onBlur}
84
+ onFocus={onFocus}
85
+ editable={!disabled}
86
+ blurOnSubmit={false}
87
+ />
88
+ {toolbar && <View style={s.toolbar}>{toolbar}</View>}
89
+ </View>
90
+ );
91
+ });
92
+
93
+ Input.displayName = 'Input';
94
+
95
+ export default Input;
@@ -0,0 +1,32 @@
1
+ import { Platform } from 'react-native';
2
+ import type { ViewStyle, TextStyle } from 'react-native';
3
+ import { tokens } from '../../theme/tokens';
4
+
5
+ export type InputSemanticStyles = {
6
+ root?: ViewStyle;
7
+ input?: TextStyle;
8
+ toolbar?: ViewStyle;
9
+ };
10
+
11
+ export const DEFAULT_STYLES: InputSemanticStyles = {
12
+ root: {
13
+ backgroundColor: tokens.colorBgElevated,
14
+ borderRadius: tokens.radiusXl,
15
+ paddingHorizontal: 12,
16
+ paddingTop: 8,
17
+ paddingBottom: 6,
18
+ ...tokens.shadow,
19
+ },
20
+ input: {
21
+ fontSize: tokens.fontSize,
22
+ color: tokens.colorText,
23
+ lineHeight: tokens.lineHeight,
24
+ paddingVertical: Platform.OS === 'ios' ? 4 : 2,
25
+ paddingHorizontal: 4,
26
+ },
27
+ toolbar: {
28
+ flexDirection: 'row',
29
+ alignItems: 'center',
30
+ paddingTop: 4,
31
+ },
32
+ };
@@ -0,0 +1,101 @@
1
+ # ListItem 列表行
2
+
3
+ 4 个语义 slot(root / content / description / extra),支持 `styles` 精确覆盖。
4
+
5
+ ## 何时使用
6
+
7
+ - 替代 `<TouchableOpacity>` + icon + text + arrow 的手写列表行
8
+ - 统一列表行样式:设置菜单、选择列表、会话列表等
9
+
10
+ ## 代码演示
11
+
12
+ ```tsx
13
+ // 基础 — 带箭头
14
+ <ListItem title="关于" arrow onPress={handleAbout} />
15
+
16
+ // 带右侧额外信息
17
+ <ListItem title="关于" extra="v1.0.0" onPress={handleAbout} />
18
+
19
+ // 带左侧图标
20
+ <ListItem
21
+ thumb={<Icon name="information-circle-outline" size={22} color={tokens.colorTextSecondary} />}
22
+ title="关于"
23
+ extra="v1.0.0"
24
+ onPress={handleAbout}
25
+ />
26
+
27
+ // 带描述文字(双行)
28
+ <ListItem
29
+ thumb={<Icon name="chatbubble-outline" size={16} />}
30
+ title="今天的会话"
31
+ description="最后一条消息预览..."
32
+ onPress={handleSelect}
33
+ onLongPress={handleDelete}
34
+ />
35
+
36
+ // 语义插槽覆盖 — 选中态
37
+ <ListItem
38
+ title={model.name}
39
+ description={model.desc}
40
+ extra={isSelected ? <Icon name="checkmark-circle" size={22} color={tokens.colorPrimary} /> : undefined}
41
+ onPress={() => handleSelect(model.id)}
42
+ styles={isSelected ? {
43
+ root: {backgroundColor: '#FFF7F0'},
44
+ content: {color: tokens.colorPrimary},
45
+ } : undefined}
46
+ />
47
+
48
+ // 语义插槽覆盖 — 危险操作
49
+ <ListItem
50
+ thumb={<Icon name="log-out-outline" size={22} color={tokens.colorError} />}
51
+ title="退出登录"
52
+ onPress={handleLogout}
53
+ styles={{content: {color: tokens.colorError}}}
54
+ />
55
+ ```
56
+
57
+ ## API
58
+
59
+ | 属性 | 说明 | 类型 | 默认值 |
60
+ |------|------|------|--------|
61
+ | thumb | 左侧缩略(图标、头像等) | `ReactNode` | - |
62
+ | title | 主标题 | `string` \| `ReactNode` | **必填** |
63
+ | description | 描述文字(单行截断) | `string` | - |
64
+ | extra | 右侧额外内容 | `ReactNode` \| `string` | - |
65
+ | arrow | 显示右箭头 | `boolean` | `false` |
66
+ | onPress | 点击回调(有值时渲染为 Pressable) | `() => void` | - |
67
+ | onLongPress | 长按回调 | `() => void` | - |
68
+ | disabled | 禁用状态 | `boolean` | `false` |
69
+ | style | 外层容器样式 | `ViewStyle` | - |
70
+ | styles | 语义插槽样式 | `Partial<ListItemSemanticStyles>` | - |
71
+ | testID | 测试标识 | `string` | - |
72
+
73
+ ## Semantic Styles
74
+
75
+ 通过 `styles` prop 精确覆盖组件内部各 slot 的样式。
76
+
77
+ | 插槽 | 类型 | 说明 |
78
+ |------|------|------|
79
+ | root | `ViewStyle` | 整行容器(padding、背景等) |
80
+ | content | `TextStyle` | 主标题文字(字号、颜色、flex 等) |
81
+ | description | `TextStyle` | 描述文字(字号、颜色等) |
82
+ | extra | `ViewStyle & TextStyle` | 右侧额外内容区域 |
83
+
84
+ ## 内部布局
85
+
86
+ ```
87
+ ┌─ root ──────────────────────────────────┐
88
+ │ [thumb] ┌─ line ─────────────────────┐ │
89
+ │ │ ┌─ contentWrap ──────────┐ │ │
90
+ │ │ │ [content] │ │ │
91
+ │ │ │ [description] │ │ │
92
+ │ │ └────────────────────────┘ │ │
93
+ │ │ [extra] [arrow] │ │
94
+ │ └────────────────────────────┘ │
95
+ └─────────────────────────────────────────┘
96
+ ```
97
+
98
+ ## 交互行为
99
+
100
+ - 有 `onPress` 时自动包裹 `Pressable`,无 `onPress` 时渲染为纯 `View`
101
+ - `onLongPress` 仅在有 `onPress` 时生效
@@ -0,0 +1,91 @@
1
+ /**
2
+ * 列表行组件
3
+ *
4
+ * 语义 slot:root / content / description / extra。
5
+ */
6
+
7
+ import React, { useMemo } from 'react';
8
+ import { View, Text, Pressable } from 'react-native';
9
+ import type { ViewStyle, TextStyle } from 'react-native';
10
+ import { mergeStyles } from '../hooks';
11
+ import { tokens } from '../theme/tokens';
12
+ import type { ListItemSemanticStyles } from './style';
13
+ import { DEFAULT_SEMANTIC, lineStyle, contentWrapStyle } from './style';
14
+
15
+ interface ListItemProps {
16
+ thumb?: React.ReactNode;
17
+ title: string | React.ReactNode;
18
+ description?: string;
19
+ extra?: React.ReactNode | string;
20
+ arrow?: boolean;
21
+ arrowIcon?: React.ReactNode;
22
+ onPress?: () => void;
23
+ onLongPress?: () => void;
24
+ disabled?: boolean;
25
+ style?: ViewStyle;
26
+ styles?: Partial<ListItemSemanticStyles>;
27
+ testID?: string;
28
+ }
29
+
30
+ const defaultArrowStyle = { fontSize: 18, color: tokens.colorTextSecondary };
31
+
32
+ const ListItem: React.FC<ListItemProps> = ({
33
+ thumb,
34
+ title,
35
+ description,
36
+ extra,
37
+ arrow,
38
+ arrowIcon,
39
+ style,
40
+ styles: semanticStyles,
41
+ ...props
42
+ }: ListItemProps) => {
43
+ const s = useMemo(
44
+ () => mergeStyles(DEFAULT_SEMANTIC, semanticStyles),
45
+ [semanticStyles],
46
+ );
47
+
48
+ const content = (
49
+ <View style={[s.root, style]}>
50
+ {thumb}
51
+ <View style={lineStyle}>
52
+ <View style={contentWrapStyle}>
53
+ {typeof title === 'string' ? (
54
+ <Text style={s.content}>{title}</Text>
55
+ ) : (
56
+ title
57
+ )}
58
+ {description != null && (
59
+ <Text style={s.description} numberOfLines={1}>
60
+ {description}
61
+ </Text>
62
+ )}
63
+ </View>
64
+ {extra != null &&
65
+ (typeof extra === 'string' ? (
66
+ <Text style={s.extra as TextStyle}>{extra}</Text>
67
+ ) : (
68
+ <View style={s.extra as ViewStyle}>{extra}</View>
69
+ ))}
70
+ {arrow &&
71
+ (arrowIcon ?? <Text style={defaultArrowStyle}>›</Text>)}
72
+ </View>
73
+ </View>
74
+ );
75
+
76
+ if (props.onPress) {
77
+ return (
78
+ <Pressable
79
+ onPress={props.onPress}
80
+ onLongPress={props.onLongPress}
81
+ disabled={props.disabled}
82
+ testID={props.testID}
83
+ >
84
+ {content}
85
+ </Pressable>
86
+ );
87
+ }
88
+ return content;
89
+ };
90
+
91
+ export default ListItem;
@@ -0,0 +1,41 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import type { ViewStyle, TextStyle } from 'react-native';
3
+ import { tokens } from '../../theme/tokens';
4
+
5
+ export type ListItemSemanticStyles = {
6
+ root?: ViewStyle;
7
+ content?: TextStyle;
8
+ description?: TextStyle;
9
+ extra?: ViewStyle & TextStyle;
10
+ };
11
+
12
+ export const DEFAULT_SEMANTIC: ListItemSemanticStyles = {
13
+ root: {
14
+ flexDirection: 'row',
15
+ alignItems: 'center',
16
+ paddingHorizontal: 16,
17
+ paddingVertical: 14,
18
+ },
19
+ content: {
20
+ fontSize: 15,
21
+ color: tokens.colorText,
22
+ flex: 1,
23
+ },
24
+ description: {
25
+ fontSize: 12,
26
+ color: tokens.colorTextSecondary,
27
+ marginTop: 2,
28
+ },
29
+ extra: {
30
+ color: tokens.colorTextSecondary,
31
+ fontSize: 14,
32
+ },
33
+ };
34
+
35
+ export const lineStyle = StyleSheet.create({
36
+ s: { flex: 1, flexDirection: 'row', alignItems: 'center' },
37
+ }).s;
38
+
39
+ export const contentWrapStyle = StyleSheet.create({
40
+ s: { flex: 1 },
41
+ }).s;
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: Popover
3
+ nav:
4
+ title: 组件
5
+ order: 1
6
+ group:
7
+ title: 基础
8
+ order: 2
9
+ ---
10
+
11
+ # Popover 弹出定位容器
12
+
13
+ 锚定在指定元素下方的弹出容器,用于下拉菜单等场景。
14
+
15
+ ## 何时使用
16
+
17
+ - 下拉选择菜单
18
+ - 模型选择器
19
+ - 任何需要锚定定位的浮层
20
+
21
+ ## 代码演示
22
+
23
+ ```tsx
24
+ import { Popover } from '@unif/react-native-ui';
25
+
26
+ <Popover
27
+ visible={visible}
28
+ onClose={() => setVisible(false)}
29
+ anchorLayout={{ x: 100, y: 50, width: 120, height: 40 }}
30
+ width={180}
31
+ >
32
+ <View>
33
+ <Text>下拉内容</Text>
34
+ </View>
35
+ </Popover>
36
+ ```
37
+
38
+ ## API
39
+
40
+ | 属性 | 说明 | 类型 | 默认值 |
41
+ | ------------ | -------------- | --------------------------------------------- | ---------- |
42
+ | visible | 是否可见 | `boolean` | - |
43
+ | onClose | 关闭回调 | `() => void` | - |
44
+ | anchorLayout | 锚点布局 | `{ x, y, width, height }` | - |
45
+ | width | 内容宽度 | `number` | `180` |
46
+ | placement | 弹出方向 | `'bottom'` \| `'top'` | `'bottom'` |
47
+ | children | 弹出内容 | `ReactNode` | - |
48
+ | style | 内容容器样式 | `ViewStyle` | - |
49
+ | styles | 语义样式 | `PopoverSemanticStyles` | - |
50
+ | testID | 测试标识 | `string` | - |
51
+
52
+ ## Semantic Styles
53
+
54
+ | Slot | 说明 |
55
+ | ------- | ------------ |
56
+ | overlay | 遮罩层 |
57
+ | content | 内容容器 |