@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.
- package/LICENSE +20 -0
- package/README.md +103 -0
- package/lib/module/README.md +99 -0
- package/lib/module/action-sheet/index.js +63 -0
- package/lib/module/action-sheet/index.js.map +1 -0
- package/lib/module/action-sheet/index.md +69 -0
- package/lib/module/action-sheet/style/index.js +38 -0
- package/lib/module/action-sheet/style/index.js.map +1 -0
- package/lib/module/avatar/index.js +42 -0
- package/lib/module/avatar/index.js.map +1 -0
- package/lib/module/avatar/index.md +51 -0
- package/lib/module/avatar/style/index.js +17 -0
- package/lib/module/avatar/style/index.js.map +1 -0
- package/lib/module/button/index.js +52 -0
- package/lib/module/button/index.js.map +1 -0
- package/lib/module/button/index.md +90 -0
- package/lib/module/button/style/index.js +94 -0
- package/lib/module/button/style/index.js.map +1 -0
- package/lib/module/center/index.js +21 -0
- package/lib/module/center/index.js.map +1 -0
- package/lib/module/center/index.md +28 -0
- package/lib/module/center/style/index.js +10 -0
- package/lib/module/center/style/index.js.map +1 -0
- package/lib/module/chip/index.js +59 -0
- package/lib/module/chip/index.js.map +1 -0
- package/lib/module/chip/index.md +60 -0
- package/lib/module/chip/style/index.js +40 -0
- package/lib/module/chip/style/index.js.map +1 -0
- package/lib/module/column/index.js +30 -0
- package/lib/module/column/index.js.map +1 -0
- package/lib/module/column/index.md +34 -0
- package/lib/module/column/style/index.js +9 -0
- package/lib/module/column/style/index.js.map +1 -0
- package/lib/module/divider/index.js +21 -0
- package/lib/module/divider/index.js.map +1 -0
- package/lib/module/divider/index.md +44 -0
- package/lib/module/divider/style/index.js +17 -0
- package/lib/module/divider/style/index.js.map +1 -0
- package/lib/module/hooks/index.js +4 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/index.md +71 -0
- package/lib/module/hooks/useMergeStyles.js +27 -0
- package/lib/module/hooks/useMergeStyles.js.map +1 -0
- package/lib/module/index.js +29 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/input/index.js +69 -0
- package/lib/module/input/index.js.map +1 -0
- package/lib/module/input/index.md +73 -0
- package/lib/module/input/style/index.js +27 -0
- package/lib/module/input/style/index.js.map +1 -0
- package/lib/module/list-item/index.js +69 -0
- package/lib/module/list-item/index.js.map +1 -0
- package/lib/module/list-item/index.md +101 -0
- package/lib/module/list-item/style/index.js +39 -0
- package/lib/module/list-item/style/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/popover/index.js +60 -0
- package/lib/module/popover/index.js.map +1 -0
- package/lib/module/popover/index.md +57 -0
- package/lib/module/popover/style/index.js +22 -0
- package/lib/module/popover/style/index.js.map +1 -0
- package/lib/module/row/index.js +31 -0
- package/lib/module/row/index.js.map +1 -0
- package/lib/module/row/index.md +46 -0
- package/lib/module/row/style/index.js +15 -0
- package/lib/module/row/style/index.js.map +1 -0
- package/lib/module/space/index.js +20 -0
- package/lib/module/space/index.js.map +1 -0
- package/lib/module/space/index.md +31 -0
- package/lib/module/space/style/index.js +9 -0
- package/lib/module/space/style/index.js.map +1 -0
- package/lib/module/text/index.js +30 -0
- package/lib/module/text/index.js.map +1 -0
- package/lib/module/text/index.md +63 -0
- package/lib/module/text/style/index.js +51 -0
- package/lib/module/text/style/index.js.map +1 -0
- package/lib/module/theme/config.js +27 -0
- package/lib/module/theme/config.js.map +1 -0
- package/lib/module/theme/tokens.js +67 -0
- package/lib/module/theme/tokens.js.map +1 -0
- package/lib/module/touchable/index.js +35 -0
- package/lib/module/touchable/index.js.map +1 -0
- package/lib/module/touchable/index.md +42 -0
- package/lib/module/touchable/style/index.js +9 -0
- package/lib/module/touchable/style/index.js.map +1 -0
- package/lib/module/wave-animation/index.js +67 -0
- package/lib/module/wave-animation/index.js.map +1 -0
- package/lib/module/wave-animation/index.md +50 -0
- package/lib/typescript/jest.setup.d.ts +1 -0
- package/lib/typescript/jest.setup.d.ts.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/action-sheet/index.d.ts +22 -0
- package/lib/typescript/src/action-sheet/index.d.ts.map +1 -0
- package/lib/typescript/src/action-sheet/style/index.d.ts +16 -0
- package/lib/typescript/src/action-sheet/style/index.d.ts.map +1 -0
- package/lib/typescript/src/avatar/index.d.ts +19 -0
- package/lib/typescript/src/avatar/index.d.ts.map +1 -0
- package/lib/typescript/src/avatar/style/index.d.ts +7 -0
- package/lib/typescript/src/avatar/style/index.d.ts.map +1 -0
- package/lib/typescript/src/button/index.d.ts +24 -0
- package/lib/typescript/src/button/index.d.ts.map +1 -0
- package/lib/typescript/src/button/style/index.d.ts +16 -0
- package/lib/typescript/src/button/style/index.d.ts.map +1 -0
- package/lib/typescript/src/center/index.d.ts +11 -0
- package/lib/typescript/src/center/index.d.ts.map +1 -0
- package/lib/typescript/src/center/style/index.d.ts +5 -0
- package/lib/typescript/src/center/style/index.d.ts.map +1 -0
- package/lib/typescript/src/chip/index.d.ts +22 -0
- package/lib/typescript/src/chip/index.d.ts.map +1 -0
- package/lib/typescript/src/chip/style/index.d.ts +10 -0
- package/lib/typescript/src/chip/style/index.d.ts.map +1 -0
- package/lib/typescript/src/column/index.d.ts +20 -0
- package/lib/typescript/src/column/index.d.ts.map +1 -0
- package/lib/typescript/src/column/style/index.d.ts +4 -0
- package/lib/typescript/src/column/style/index.d.ts.map +1 -0
- package/lib/typescript/src/divider/index.d.ts +13 -0
- package/lib/typescript/src/divider/index.d.ts.map +1 -0
- package/lib/typescript/src/divider/style/index.d.ts +9 -0
- package/lib/typescript/src/divider/style/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +2 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useMergeStyles.d.ts +13 -0
- package/lib/typescript/src/hooks/useMergeStyles.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +39 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/input/index.d.ts +29 -0
- package/lib/typescript/src/input/index.d.ts.map +1 -0
- package/lib/typescript/src/input/style/index.d.ts +8 -0
- package/lib/typescript/src/input/style/index.d.ts.map +1 -0
- package/lib/typescript/src/list-item/index.d.ts +25 -0
- package/lib/typescript/src/list-item/index.d.ts.map +1 -0
- package/lib/typescript/src/list-item/style/index.d.ts +17 -0
- package/lib/typescript/src/list-item/style/index.d.ts.map +1 -0
- package/lib/typescript/src/popover/index.d.ts +27 -0
- package/lib/typescript/src/popover/index.d.ts.map +1 -0
- package/lib/typescript/src/popover/style/index.d.ts +7 -0
- package/lib/typescript/src/popover/style/index.d.ts.map +1 -0
- package/lib/typescript/src/row/index.d.ts +22 -0
- package/lib/typescript/src/row/index.d.ts.map +1 -0
- package/lib/typescript/src/row/style/index.d.ts +8 -0
- package/lib/typescript/src/row/style/index.d.ts.map +1 -0
- package/lib/typescript/src/space/index.d.ts +15 -0
- package/lib/typescript/src/space/index.d.ts.map +1 -0
- package/lib/typescript/src/space/style/index.d.ts +7 -0
- package/lib/typescript/src/space/style/index.d.ts.map +1 -0
- package/lib/typescript/src/text/index.d.ts +18 -0
- package/lib/typescript/src/text/index.d.ts.map +1 -0
- package/lib/typescript/src/text/style/index.d.ts +35 -0
- package/lib/typescript/src/text/style/index.d.ts.map +1 -0
- package/lib/typescript/src/theme/config.d.ts +18 -0
- package/lib/typescript/src/theme/config.d.ts.map +1 -0
- package/lib/typescript/src/theme/tokens.d.ts +61 -0
- package/lib/typescript/src/theme/tokens.d.ts.map +1 -0
- package/lib/typescript/src/touchable/index.d.ts +19 -0
- package/lib/typescript/src/touchable/index.d.ts.map +1 -0
- package/lib/typescript/src/touchable/style/index.d.ts +4 -0
- package/lib/typescript/src/touchable/style/index.d.ts.map +1 -0
- package/lib/typescript/src/wave-animation/index.d.ts +20 -0
- package/lib/typescript/src/wave-animation/index.d.ts.map +1 -0
- package/lib/typescript/tests/component/component.coverage.test.d.ts +2 -0
- package/lib/typescript/tests/component/component.coverage.test.d.ts.map +1 -0
- package/lib/typescript/tests/component/component.interaction.test.d.ts +2 -0
- package/lib/typescript/tests/component/component.interaction.test.d.ts.map +1 -0
- package/lib/typescript/tests/component/component.snapshot.test.d.ts +2 -0
- package/lib/typescript/tests/component/component.snapshot.test.d.ts.map +1 -0
- package/lib/typescript/tests/e2e/app.e2e.test.d.ts +2 -0
- package/lib/typescript/tests/e2e/app.e2e.test.d.ts.map +1 -0
- package/lib/typescript/tests/integration/ui.integration.test.d.ts +2 -0
- package/lib/typescript/tests/integration/ui.integration.test.d.ts.map +1 -0
- package/lib/typescript/tests/unit/mergeStyles.unit.test.d.ts +2 -0
- package/lib/typescript/tests/unit/mergeStyles.unit.test.d.ts.map +1 -0
- package/lib/typescript/tests/unit/public-api.unit.test.d.ts +2 -0
- package/lib/typescript/tests/unit/public-api.unit.test.d.ts.map +1 -0
- package/lib/typescript/tests/unit/theme-config.unit.test.d.ts +2 -0
- package/lib/typescript/tests/unit/theme-config.unit.test.d.ts.map +1 -0
- package/package.json +134 -0
- package/src/README.md +99 -0
- package/src/action-sheet/index.md +69 -0
- package/src/action-sheet/index.tsx +85 -0
- package/src/action-sheet/style/index.tsx +52 -0
- package/src/avatar/index.md +51 -0
- package/src/avatar/index.tsx +56 -0
- package/src/avatar/style/index.tsx +21 -0
- package/src/button/index.md +90 -0
- package/src/button/index.tsx +86 -0
- package/src/button/style/index.tsx +67 -0
- package/src/center/index.md +28 -0
- package/src/center/index.tsx +18 -0
- package/src/center/style/index.tsx +8 -0
- package/src/chip/index.md +60 -0
- package/src/chip/index.tsx +80 -0
- package/src/chip/style/index.tsx +47 -0
- package/src/column/index.md +34 -0
- package/src/column/index.tsx +43 -0
- package/src/column/style/index.tsx +7 -0
- package/src/divider/index.md +44 -0
- package/src/divider/index.tsx +30 -0
- package/src/divider/style/index.tsx +13 -0
- package/src/hooks/index.md +71 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useMergeStyles.ts +27 -0
- package/src/index.tsx +49 -0
- package/src/input/index.md +73 -0
- package/src/input/index.tsx +95 -0
- package/src/input/style/index.tsx +32 -0
- package/src/list-item/index.md +101 -0
- package/src/list-item/index.tsx +91 -0
- package/src/list-item/style/index.tsx +41 -0
- package/src/popover/index.md +57 -0
- package/src/popover/index.tsx +80 -0
- package/src/popover/style/index.tsx +23 -0
- package/src/row/index.md +46 -0
- package/src/row/index.tsx +47 -0
- package/src/row/style/index.tsx +14 -0
- package/src/space/index.md +31 -0
- package/src/space/index.tsx +28 -0
- package/src/space/style/index.tsx +3 -0
- package/src/text/index.md +63 -0
- package/src/text/index.tsx +45 -0
- package/src/text/style/index.tsx +32 -0
- package/src/theme/config.ts +26 -0
- package/src/theme/tokens.ts +66 -0
- package/src/touchable/index.md +42 -0
- package/src/touchable/index.tsx +45 -0
- package/src/touchable/style/index.tsx +5 -0
- package/src/wave-animation/index.md +50 -0
- 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 | 内容容器 |
|