@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,21 @@
1
+ import type { ViewStyle, ImageStyle } from 'react-native';
2
+ import { tokens } from '../../theme/tokens';
3
+
4
+ export type AvatarSemanticStyles = {
5
+ root?: ViewStyle;
6
+ image?: ImageStyle;
7
+ };
8
+
9
+ export const DEFAULT_STYLES: AvatarSemanticStyles = {
10
+ root: {
11
+ justifyContent: 'center',
12
+ alignItems: 'center',
13
+ backgroundColor: tokens.colorBgElevated,
14
+ overflow: 'hidden',
15
+ ...tokens.shadowSm,
16
+ },
17
+ image: {
18
+ width: '100%' as any,
19
+ height: '100%' as any,
20
+ },
21
+ };
@@ -0,0 +1,90 @@
1
+ # Button 语义化按钮
2
+
3
+ 支持 `type` / `size` 变体和 `styles` 语义插槽覆盖。使用 `Pressable` 实现按下态反馈。
4
+
5
+ ## 何时使用
6
+
7
+ - 替代 `<TouchableOpacity>` + 手写按钮样式的组合
8
+ - 需要统一按钮风格(主按钮、描边按钮、幽灵按钮)时
9
+
10
+ ## 代码演示
11
+
12
+ ```tsx
13
+ // 主按钮
14
+ <Button onPress={handleConfirm}>确认下单</Button>
15
+
16
+ // 描边按钮
17
+ <Button type="outline" onPress={handleModify}>修改</Button>
18
+
19
+ // 幽灵按钮
20
+ <Button type="ghost" onPress={handlePush}>推送经销商</Button>
21
+
22
+ // 并排等宽
23
+ <View style={{flexDirection: 'row', gap: 10}}>
24
+ <Button type="outline" block onPress={handleModify}>修改</Button>
25
+ <Button block onPress={handleConfirm}>确认</Button>
26
+ </View>
27
+
28
+ // 带图标
29
+ <Button icon={<Icon name="add" size={16} color="#FFF" />} onPress={handleAdd}>
30
+ 添加
31
+ </Button>
32
+
33
+ // 语义插槽覆盖
34
+ <Button
35
+ type="outline"
36
+ onPress={handleAction}
37
+ styles={{root: {borderColor: 'red'}, content: {color: 'red'}}}>
38
+ 危险操作
39
+ </Button>
40
+ ```
41
+
42
+ ## API
43
+
44
+ | 属性 | 说明 | 类型 | 默认值 |
45
+ |------|------|------|--------|
46
+ | type | 按钮类型 | `'primary'` \| `'outline'` \| `'ghost'` \| `'danger'` | `'primary'` |
47
+ | size | 按钮大小 | `'default'` \| `'small'` | `'default'` |
48
+ | icon | 左侧图标 | `ReactNode` | - |
49
+ | disabled | 禁用状态 | `boolean` | `false` |
50
+ | block | 撑满父容器(flex: 1) | `boolean` | `false` |
51
+ | loading | 加载状态 | `boolean` | `false` |
52
+ | onPress | 点击回调 | `() => void` | **必填** |
53
+ | style | 外层容器样式 | `ViewStyle` | - |
54
+ | styles | 语义插槽样式 | `Partial<ButtonSemanticStyles>` | - |
55
+ | testID | 测试标识 | `string` | - |
56
+ | children | 按钮内容 | `ReactNode` | **必填** |
57
+
58
+ ## Semantic Styles
59
+
60
+ 通过 `styles` prop 精确覆盖组件内部各 slot 的样式,不侵入组件结构。
61
+
62
+ | 插槽 | 类型 | 说明 |
63
+ |------|------|------|
64
+ | root | `ViewStyle` | 按钮容器(背景、边框、圆角等) |
65
+ | icon | `ViewStyle` | 图标区域(间距等) |
66
+ | content | `TextStyle` | 文字区域(字号、颜色、字重等) |
67
+
68
+ ## Type 规格
69
+
70
+ | type | 背景 | 文字色 | 边框 |
71
+ |------|------|--------|------|
72
+ | primary | colorPrimary | #FFF | 无 |
73
+ | outline | 透明 | colorTextSecondary | colorBorder |
74
+ | ghost | 透明 | colorPrimary | colorPrimary |
75
+ | danger | colorError | #FFF | 无 |
76
+
77
+ ## Size 规格
78
+
79
+ | size | paddingVertical | borderRadius | fontSize |
80
+ |------|----------------|--------------|----------|
81
+ | default | 10 | 8 | 14 |
82
+ | small | 6 | 6 | 12 |
83
+
84
+ ## 样式合并顺序
85
+
86
+ ```
87
+ DEFAULT_STYLES → SIZE_STYLES → TYPE_STYLES → DISABLED_STYLES → BLOCK_STYLES → semanticStyles
88
+ ```
89
+
90
+ 内部使用 `mergeStyles` + `useMemo` 按 slot 维度合并,仅在 props 变化时重算。
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 按钮组件
3
+ *
4
+ * type + size + semantic styles。
5
+ */
6
+
7
+ import React, { useMemo } from 'react';
8
+ import { Pressable, Text, View, ActivityIndicator } from 'react-native';
9
+ import type { ViewStyle } from 'react-native';
10
+ import { mergeStyles } from '../hooks';
11
+ import type { ButtonSemanticStyles } from './style';
12
+ import {
13
+ DEFAULT_STYLES,
14
+ SIZE_STYLES,
15
+ TYPE_STYLES,
16
+ DISABLED_STYLES,
17
+ BLOCK_STYLES,
18
+ PRESSED_STYLE,
19
+ loadingStyle,
20
+ } from './style';
21
+
22
+ export interface ButtonProps {
23
+ type?: 'primary' | 'outline' | 'ghost' | 'danger';
24
+ size?: 'default' | 'small';
25
+ icon?: React.ReactNode;
26
+ disabled?: boolean;
27
+ block?: boolean;
28
+ loading?: boolean;
29
+ onPress: () => void;
30
+ style?: ViewStyle;
31
+ styles?: Partial<ButtonSemanticStyles>;
32
+ testID?: string;
33
+ children: React.ReactNode;
34
+ }
35
+
36
+ const Button: React.FC<ButtonProps> = ({
37
+ type = 'primary',
38
+ size = 'default',
39
+ icon,
40
+ disabled,
41
+ block,
42
+ loading,
43
+ onPress,
44
+ style,
45
+ styles: semanticStyles,
46
+ testID,
47
+ children,
48
+ }: ButtonProps) => {
49
+ const s = useMemo(
50
+ () =>
51
+ mergeStyles<ButtonSemanticStyles>(
52
+ DEFAULT_STYLES,
53
+ SIZE_STYLES[size],
54
+ TYPE_STYLES[type],
55
+ disabled ? DISABLED_STYLES : undefined,
56
+ block ? BLOCK_STYLES : undefined,
57
+ semanticStyles,
58
+ ),
59
+ [type, size, disabled, block, semanticStyles],
60
+ );
61
+
62
+ return (
63
+ <Pressable
64
+ style={({ pressed }) => [s.root, pressed && PRESSED_STYLE, style]}
65
+ onPress={onPress}
66
+ disabled={disabled || loading}
67
+ testID={testID}
68
+ >
69
+ {icon && <View style={s.icon}>{icon}</View>}
70
+ {loading && (
71
+ <ActivityIndicator
72
+ color={s.content?.color as string}
73
+ size="small"
74
+ style={loadingStyle}
75
+ />
76
+ )}
77
+ {typeof children === 'string' ? (
78
+ <Text style={s.content}>{children}</Text>
79
+ ) : (
80
+ <View style={s.content as ViewStyle}>{children}</View>
81
+ )}
82
+ </Pressable>
83
+ );
84
+ };
85
+
86
+ export default Button;
@@ -0,0 +1,67 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import type { ViewStyle, TextStyle } from 'react-native';
3
+ import { tokens } from '../../theme/tokens';
4
+
5
+ export type ButtonSemanticStyles = {
6
+ root?: ViewStyle;
7
+ icon?: ViewStyle;
8
+ content?: TextStyle;
9
+ };
10
+
11
+ export const DEFAULT_STYLES: ButtonSemanticStyles = {
12
+ root: {
13
+ flexDirection: 'row',
14
+ alignItems: 'center',
15
+ justifyContent: 'center',
16
+ },
17
+ icon: {
18
+ marginRight: 6,
19
+ },
20
+ content: {
21
+ fontSize: 14,
22
+ },
23
+ };
24
+
25
+ export const SIZE_STYLES: Record<string, Partial<ButtonSemanticStyles>> = {
26
+ default: {
27
+ root: { paddingVertical: 10, borderRadius: 8 },
28
+ content: { fontSize: 14 },
29
+ },
30
+ small: {
31
+ root: { paddingVertical: 6, borderRadius: 6 },
32
+ content: { fontSize: 12 },
33
+ },
34
+ };
35
+
36
+ export const TYPE_STYLES: Record<string, Partial<ButtonSemanticStyles>> = {
37
+ primary: {
38
+ root: { backgroundColor: tokens.colorPrimary },
39
+ content: { color: '#FFF', fontWeight: '600' },
40
+ },
41
+ outline: {
42
+ root: { borderWidth: 1, borderColor: tokens.colorBorder },
43
+ content: { color: tokens.colorTextSecondary },
44
+ },
45
+ ghost: {
46
+ root: { borderWidth: 1, borderColor: tokens.colorPrimary },
47
+ content: { color: tokens.colorPrimary },
48
+ },
49
+ danger: {
50
+ root: { backgroundColor: tokens.colorError },
51
+ content: { color: '#FFF', fontWeight: '600' },
52
+ },
53
+ };
54
+
55
+ export const DISABLED_STYLES: Partial<ButtonSemanticStyles> = {
56
+ root: { opacity: 0.5 },
57
+ };
58
+
59
+ export const BLOCK_STYLES: Partial<ButtonSemanticStyles> = {
60
+ root: { flex: 1 },
61
+ };
62
+
63
+ export const PRESSED_STYLE: ViewStyle = { opacity: 0.7 };
64
+
65
+ export const loadingStyle = StyleSheet.create({
66
+ s: { marginRight: 6 },
67
+ }).s;
@@ -0,0 +1,28 @@
1
+ # Center 居中布局
2
+
3
+ 内容水平 + 垂直居中。替代 `justifyContent: 'center' + alignItems: 'center'`。
4
+
5
+ ## 何时使用
6
+
7
+ - 圆形图标按钮、头像容器
8
+ - 空状态页面居中
9
+ - 任何需要内容完全居中的场景
10
+
11
+ ## 代码演示
12
+
13
+ ```tsx
14
+ // 圆形按钮
15
+ <Center style={{width: 32, height: 32, borderRadius: 16, backgroundColor: '#EB6E00'}}>
16
+ <Icon name="add" size={18} color="#FFF" />
17
+ </Center>
18
+
19
+ // 空状态
20
+ <Center style={{flex: 1}}>
21
+ <Icon name="inbox" size={48} />
22
+ <Text secondary>暂无数据</Text>
23
+ </Center>
24
+ ```
25
+
26
+ ## API
27
+
28
+ > 继承所有 RN `ViewProps`,无额外属性。
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 居中布局容器
3
+ *
4
+ * justifyContent + alignItems 均为 'center'。
5
+ */
6
+
7
+ import React from 'react';
8
+ import { View } from 'react-native';
9
+ import type { ViewProps } from 'react-native';
10
+ import { baseStyle } from './style';
11
+
12
+ export type CenterProps = ViewProps;
13
+
14
+ const Center: React.FC<CenterProps> = ({ style, ...rest }) => (
15
+ <View style={[baseStyle, style]} {...rest} />
16
+ );
17
+
18
+ export default Center;
@@ -0,0 +1,8 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export const baseStyle = StyleSheet.create({
4
+ center: {
5
+ justifyContent: 'center',
6
+ alignItems: 'center',
7
+ },
8
+ }).center;
@@ -0,0 +1,60 @@
1
+ ---
2
+ title: Chip
3
+ nav:
4
+ title: 组件
5
+ order: 1
6
+ group:
7
+ title: 基础
8
+ order: 2
9
+ ---
10
+
11
+ # Chip 胶囊标签
12
+
13
+ 用于展示标签或可选项的胶囊形组件。
14
+
15
+ ## 何时使用
16
+
17
+ - 展示快捷建议
18
+ - 标签选择
19
+ - 筛选项
20
+
21
+ ## 代码演示
22
+
23
+ ```tsx
24
+ import { Chip, Icon } from '@unif/react-native-ui';
25
+
26
+ // 基本用法
27
+ <Chip onPress={() => {}}>标签文本</Chip>
28
+
29
+ // 带图标
30
+ <Chip icon={<Icon name="search-outline" size={14} />} onPress={() => {}}>
31
+ 搜索
32
+ </Chip>
33
+
34
+ // 选中态
35
+ <Chip selected onPress={() => {}}>已选中</Chip>
36
+
37
+ // 小尺寸
38
+ <Chip size="small">小标签</Chip>
39
+ ```
40
+
41
+ ## API
42
+
43
+ | 属性 | 说明 | 类型 | 默认值 |
44
+ | -------- | ------------ | ------------------------- | ----------- |
45
+ | children | 内容 | `ReactNode` | - |
46
+ | onPress | 点击回调 | `() => void` | - |
47
+ | selected | 选中状态 | `boolean` | - |
48
+ | disabled | 禁用状态 | `boolean` | - |
49
+ | size | 尺寸 | `'default'` \| `'small'` | `'default'` |
50
+ | icon | 左侧图标 | `ReactNode` | - |
51
+ | style | 容器样式 | `ViewStyle` | - |
52
+ | styles | 语义样式 | `ChipSemanticStyles` | - |
53
+ | testID | 测试标识 | `string` | - |
54
+
55
+ ## Semantic Styles
56
+
57
+ | Slot | 说明 |
58
+ | ------- | ---------- |
59
+ | root | 外层容器 |
60
+ | content | 文本样式 |
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Chip 基础组件
3
+ *
4
+ * 胶囊标签,支持选中态、禁用态和图标。
5
+ */
6
+
7
+ import React, { useMemo } from 'react';
8
+ import { View, Text, Pressable } from 'react-native';
9
+ import type { ViewStyle } from 'react-native';
10
+ import { mergeStyles } from '../hooks';
11
+ import type { ChipSemanticStyles } from './style';
12
+ import { DEFAULT_STYLES, SELECTED_STYLES, DISABLED_STYLES, SMALL_STYLES } from './style';
13
+
14
+ export interface ChipProps {
15
+ children: React.ReactNode;
16
+ onPress?: () => void;
17
+ selected?: boolean;
18
+ disabled?: boolean;
19
+ size?: 'default' | 'small';
20
+ icon?: React.ReactNode;
21
+ style?: ViewStyle;
22
+ styles?: Partial<ChipSemanticStyles>;
23
+ testID?: string;
24
+ }
25
+
26
+ const PRESSED_STYLE: ViewStyle = { opacity: 0.7 };
27
+
28
+ const Chip: React.FC<ChipProps> = ({
29
+ children,
30
+ onPress,
31
+ selected,
32
+ disabled,
33
+ size = 'default',
34
+ icon,
35
+ style,
36
+ styles: semanticStyles,
37
+ testID,
38
+ }) => {
39
+ const s = useMemo(
40
+ () =>
41
+ mergeStyles<ChipSemanticStyles>(
42
+ DEFAULT_STYLES,
43
+ size === 'small' ? SMALL_STYLES : undefined,
44
+ selected ? SELECTED_STYLES : undefined,
45
+ disabled ? DISABLED_STYLES : undefined,
46
+ semanticStyles,
47
+ ),
48
+ [size, selected, disabled, semanticStyles],
49
+ );
50
+
51
+ const content = (
52
+ <View style={[s.root, { flexDirection: 'row', alignItems: 'center' }, style]}>
53
+ {icon && <View style={{ marginRight: 4 }}>{icon}</View>}
54
+ {typeof children === 'string' ? (
55
+ <Text style={s.content} numberOfLines={1}>
56
+ {children}
57
+ </Text>
58
+ ) : (
59
+ children
60
+ )}
61
+ </View>
62
+ );
63
+
64
+ if (onPress) {
65
+ return (
66
+ <Pressable
67
+ onPress={onPress}
68
+ disabled={disabled}
69
+ style={({ pressed }) => pressed && PRESSED_STYLE}
70
+ testID={testID}
71
+ >
72
+ {content}
73
+ </Pressable>
74
+ );
75
+ }
76
+
77
+ return content;
78
+ };
79
+
80
+ export default Chip;
@@ -0,0 +1,47 @@
1
+ import type { ViewStyle, TextStyle } from 'react-native';
2
+ import { tokens } from '../../theme/tokens';
3
+
4
+ export type ChipSemanticStyles = {
5
+ root?: ViewStyle;
6
+ content?: TextStyle;
7
+ };
8
+
9
+ export const DEFAULT_STYLES: ChipSemanticStyles = {
10
+ root: {
11
+ backgroundColor: '#F0F0F5',
12
+ borderRadius: tokens.radiusFull,
13
+ paddingHorizontal: 14,
14
+ paddingVertical: 7,
15
+ },
16
+ content: {
17
+ fontSize: tokens.fontSizeSm,
18
+ color: tokens.colorText,
19
+ },
20
+ };
21
+
22
+ export const SELECTED_STYLES: Partial<ChipSemanticStyles> = {
23
+ root: {
24
+ borderWidth: 1,
25
+ borderColor: tokens.colorPrimary,
26
+ backgroundColor: '#FFF5EB',
27
+ },
28
+ content: {
29
+ color: tokens.colorPrimary,
30
+ },
31
+ };
32
+
33
+ export const DISABLED_STYLES: Partial<ChipSemanticStyles> = {
34
+ root: {
35
+ opacity: 0.5,
36
+ },
37
+ };
38
+
39
+ export const SMALL_STYLES: Partial<ChipSemanticStyles> = {
40
+ root: {
41
+ paddingHorizontal: 10,
42
+ paddingVertical: 4,
43
+ },
44
+ content: {
45
+ fontSize: tokens.fontSizeXs,
46
+ },
47
+ };
@@ -0,0 +1,34 @@
1
+ # Column 垂直布局
2
+
3
+ 垂直排列子元素。语义化替代裸 `<View>`,显式声明布局方向。
4
+
5
+ ## 何时使用
6
+
7
+ - 需要显式声明垂直布局方向时
8
+ - 需要 `gap`、`align`、`justify` 等布局属性时
9
+
10
+ ## 代码演示
11
+
12
+ ```tsx
13
+ // 基础用法
14
+ <Column gap={8}>
15
+ <Text variant="heading">标题</Text>
16
+ <Text variant="caption" secondary>描述文字</Text>
17
+ </Column>
18
+
19
+ // 居中对齐
20
+ <Column align="center" justify="center" style={{flex: 1}}>
21
+ <Icon name="empty" />
22
+ <Text>暂无数据</Text>
23
+ </Column>
24
+ ```
25
+
26
+ ## API
27
+
28
+ | 属性 | 说明 | 类型 | 默认值 |
29
+ |------|------|------|--------|
30
+ | align | alignItems | `ViewStyle['alignItems']` | - |
31
+ | justify | justifyContent | `ViewStyle['justifyContent']` | - |
32
+ | gap | 子元素间距 | `number` | - |
33
+
34
+ > 继承所有 RN `ViewProps`
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 垂直布局容器
3
+ *
4
+ * 语义化的 View,显式 flexDirection: 'column'。
5
+ */
6
+
7
+ import React from 'react';
8
+ import { View } from 'react-native';
9
+ import type { ViewProps, ViewStyle } from 'react-native';
10
+ import { baseStyle } from './style';
11
+
12
+ type AlignItems = ViewStyle['alignItems'];
13
+ type JustifyContent = ViewStyle['justifyContent'];
14
+
15
+ export interface ColumnProps extends ViewProps {
16
+ /** alignItems */
17
+ align?: AlignItems;
18
+ /** justifyContent */
19
+ justify?: JustifyContent;
20
+ /** 子元素间距 */
21
+ gap?: number;
22
+ }
23
+
24
+ const Column: React.FC<ColumnProps> = ({
25
+ align,
26
+ justify,
27
+ gap,
28
+ style,
29
+ ...rest
30
+ }: ColumnProps) => (
31
+ <View
32
+ style={[
33
+ baseStyle,
34
+ align != null && { alignItems: align },
35
+ justify != null && { justifyContent: justify },
36
+ gap != null && { gap },
37
+ style,
38
+ ]}
39
+ {...rest}
40
+ />
41
+ );
42
+
43
+ export default Column;
@@ -0,0 +1,7 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export const baseStyle = StyleSheet.create({
4
+ column: {
5
+ flexDirection: 'column',
6
+ },
7
+ }).column;
@@ -0,0 +1,44 @@
1
+ # Divider 分割线
2
+
3
+ 用于独立的视觉分隔元素。
4
+
5
+ ## 何时使用
6
+
7
+ - 替代独立的 `<View style={{height: 1, backgroundColor: border}} />`
8
+ - 列表项之间的分隔线(如 `FlatList` 的 `ItemSeparatorComponent`)
9
+
10
+ ## 何时不使用
11
+
12
+ - **不替换**容器自带的 `borderBottomWidth`(内嵌 border 保留原样)
13
+ - 容器 header / footer 的 `borderTopWidth` / `borderBottomWidth` 属于布局边界,改为独立 View 会影响 flex 布局
14
+
15
+ | 场景 | 处理 | 原因 |
16
+ |------|------|------|
17
+ | `<View style={styles.separator} />` | ✅ 用 `<Divider />` | 独立分割元素 |
18
+ | `styles.itemRow { borderBottomWidth }` | ❌ 保留原样 | 内嵌于容器 |
19
+ | `styles.header { borderBottomWidth }` | ❌ 保留原样 | 布局边界 |
20
+
21
+ ## 代码演示
22
+
23
+ ```tsx
24
+ // 默认 hairline
25
+ <Divider />
26
+
27
+ // 1px 带水平边距(列表分隔)
28
+ <Divider hairline={false} marginHorizontal={12} />
29
+
30
+ // 作为 FlatList 分隔
31
+ const Separator = () => <Divider hairline={false} marginHorizontal={12} />;
32
+ <FlatList ItemSeparatorComponent={Separator} ... />
33
+
34
+ // 自定义样式
35
+ <Divider style={{backgroundColor: 'red', marginVertical: 8}} />
36
+ ```
37
+
38
+ ## API
39
+
40
+ | 属性 | 说明 | 类型 | 默认值 |
41
+ |------|------|------|--------|
42
+ | hairline | 使用 `StyleSheet.hairlineWidth` | `boolean` | `true` |
43
+ | marginHorizontal | 水平边距 | `number` | - |
44
+ | style | 自定义样式 | `ViewStyle` | - |
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 分割线组件
3
+ */
4
+
5
+ import React from 'react';
6
+ import { View } from 'react-native';
7
+ import type { ViewStyle } from 'react-native';
8
+ import { hairlineStyle, standardStyle } from './style';
9
+
10
+ interface DividerProps {
11
+ hairline?: boolean;
12
+ marginHorizontal?: number;
13
+ style?: ViewStyle;
14
+ }
15
+
16
+ const Divider: React.FC<DividerProps> = ({
17
+ hairline = true,
18
+ marginHorizontal,
19
+ style,
20
+ }: DividerProps) => (
21
+ <View
22
+ style={[
23
+ hairline ? hairlineStyle : standardStyle,
24
+ marginHorizontal != null && { marginHorizontal },
25
+ style,
26
+ ]}
27
+ />
28
+ );
29
+
30
+ export default Divider;
@@ -0,0 +1,13 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { tokens } from '../../theme/tokens';
3
+
4
+ export const hairlineStyle = StyleSheet.create({
5
+ line: {
6
+ height: StyleSheet.hairlineWidth,
7
+ backgroundColor: tokens.colorBorder,
8
+ },
9
+ }).line;
10
+
11
+ export const standardStyle = StyleSheet.create({
12
+ line: { height: 1, backgroundColor: tokens.colorBorder },
13
+ }).line;