Skip to content

Commit

Permalink
feat: 添加collapse组件
Browse files Browse the repository at this point in the history
  • Loading branch information
yatessss committed Jun 25, 2023
1 parent 1e3e6fe commit 05476a3
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 0 deletions.
4 changes: 4 additions & 0 deletions example/src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"title": "Badge 徽标",
"key": "Badge"
},
{
"title": "Collapse 折叠面板",
"key": "Collapse"
},
{
"title": "Highlight 高亮",
"key": "Highlight"
Expand Down
33 changes: 33 additions & 0 deletions src/_utils/use-toggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';

export interface Actions<T> {
setLeft: () => void;
setRight: () => void;
set: (value: T) => void;
toggle: () => void;
}

export function useToggle<T = boolean>(): [boolean, Actions<T>];
export function useToggle<T>(defaultValue: T): [T, Actions<T>];
export function useToggle<T, U>(defaultValue: T, reverseValue: U): [T | U, Actions<T | U>];
// eslint-disable-next-line default-param-last
export function useToggle<D, R>(defaultValue: D = false as unknown as D, reverseValue?: R): [D | R, Actions<D | R>] {
const [state, setState] = React.useState<D | R>(defaultValue);

const actions = React.useMemo(() => {
const reverseValueOrigin = (reverseValue === undefined ? !defaultValue : reverseValue) as D | R;
const toggle = () => setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
const set = (value: D | R) => setState(value);
const setLeft = () => set(defaultValue);
const setRight = () => set(reverseValueOrigin);

return {
setLeft,
setRight,
set,
toggle,
};
}, [defaultValue, reverseValue]);

return [state, actions];
}
7 changes: 7 additions & 0 deletions src/components/Collapse/Collapse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:: BASE_DOC ::

## API
### Base Props

临时md,后期会自动生成

118 changes: 118 additions & 0 deletions src/components/Collapse/Collapse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useRef } from 'react';
import { View as RNView } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withTiming, runOnJS } from 'react-native-reanimated';
import { ChevronDownIcon } from 'tdesign-icons-react-native/src';
import { isString } from 'lodash';
import { View, Text, Touchable } from '../index';
import { useToggle } from '../../_utils/use-toggle';
import { useTheme } from '../../theme';
import { CollapseProps } from './types';

const DURATION = 300;

export const Collapse = (props: CollapseProps) => {
const {
style,
className,
headerContainerStyle,
header,
headerStyle,
headerRight,
headerRightStyle,
children,
expandable,
icon,
duration = DURATION,
} = props;
const { theme } = useTheme();
const [toggleState, { toggle }] = useToggle(expandable || false);
const contentRef = useRef<RNView>(null);
const wrapperHeight = useRef<number>();
const isAnimating = useRef<boolean>(false);
const height = useSharedValue(expandable ? 'auto' : 0);
const translateY = useSharedValue(0);
const scaleY = useSharedValue(1);

const animatedStyle = useAnimatedStyle(() => ({
height: height?.value,
transform: [{ translateY: translateY.value }, { scaleY: scaleY.value }],
}));

const isAnimatingCallback = (value: boolean) => {
isAnimating.current = value;
};

const toggleExpand = () => {
if (isAnimating.current) {
return;
}
contentRef?.current?.measure?.((x: number, y: number, width: number, _height: number) => {
wrapperHeight.current = _height;
isAnimating.current = true;
toggle();
if (!toggleState) {
// 展开
height.value = 0;
translateY.value = -_height / 2;
height.value = withTiming(_height, { duration }, () => {
runOnJS(isAnimatingCallback)(false);
height.value = 'auto';
});
translateY.value = withTiming(0, { duration });
scaleY.value = withTiming(1, { duration });
} else {
// 收起
height.value = _height;
height.value = withTiming(0, { duration }, () => {
runOnJS(isAnimatingCallback)(false);
});
translateY.value = withTiming(-_height / 2, { duration });
scaleY.value = withTiming(0, { duration });
}
});
};
return (
<View style={style} className={className}>
<Touchable
onPress={() => {
toggleExpand();
}}
>
<View className="flexRow flexBetween" style={headerContainerStyle}>
{isString(header) ? (
<Text className="text4 fontGray1" style={headerStyle}>
{header}
</Text>
) : (
header
)}
<View className="flexRow flexCenter gapX4">
{isString(headerRight) ? <Text style={headerRightStyle}>{headerRight}</Text> : headerRight}
{icon ? (
icon(toggleState)
) : (
<View style={toggleState ? { transform: [{ rotateZ: '180deg' }] } : {}}>
<ChevronDownIcon width="20" height="20" color={theme.colors.fontGray3} />
</View>
)}
</View>
</View>
</Touchable>
<View style={[{ overflow: 'hidden', height: 'auto' }]}>
<Animated.View style={animatedStyle}>{children}</Animated.View>
</View>

<View
style={{
position: 'absolute',
opacity: 0,
zIndex: -99,
height: 'auto',
}}
ref={contentRef}
>
{children}
</View>
</View>
);
};
109 changes: 109 additions & 0 deletions src/components/Collapse/_example/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* title: Collapse 折叠面板
* description: 可以将较多或较复杂的内容进行分组,分组内容区可以折叠展开或隐藏。
* spline: base
* isComponent: true
* toc: false
*/

import { Collapse, Text } from 'tdesign-react-native/components';
import { useCallback } from 'react';
import { Section, CodeSpace, H3, H5 } from '@src/../example/src/components';
import { StarIcon, StarFilledIcon } from 'tdesign-icons-react-native/src';

const IconDemo = () => {
const icon = useCallback((state: boolean) => (state ? <StarFilledIcon /> : <StarIcon />), []);
return (
<Collapse header="展开" headerRight="操作说明" icon={icon} className="p10 bgPage">
<Text>
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
);
};

const Demo = () => {
return (
<>
<Section>
<H3>1.类型</H3>
<H5>基础展开</H5>
<CodeSpace>
<Collapse header="展开" className="p10 bgPage">
<Text>
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
</CodeSpace>
<H5>带操作说明</H5>
<CodeSpace>
<Collapse header="展开" headerRight="操作说明" className="p10 bgPage">
<Text>
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
</CodeSpace>
<H5>自定义icon</H5>
<CodeSpace>
<IconDemo />
</CodeSpace>
</Section>
<Section>
<H3>2.样式</H3>
<H5>容器样式</H5>
<CodeSpace>
<Collapse header="展开" style={{ marginHorizontal: 10 }} className="p6 bgPage">
<Text>
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
</CodeSpace>
<H5>header样式</H5>
<CodeSpace>
<Collapse header="展开" headerStyle={{ color: 'green' }} className="p10 bgPage">
<Text>
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
</CodeSpace>
<H5>header right样式</H5>
<CodeSpace>
<Collapse header="展开" headerRight="操作说明" headerRightStyle={{ color: 'green' }} className="p10 bgPage">
<Text>
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
</CodeSpace>
<H5>内容样式</H5>
<CodeSpace>
<Collapse header="展开" className="p10 bgPage">
<Text className="p10 bg">
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
</CodeSpace>
</Section>

<Section>
<H3>3.其他</H3>
<H5>展开状态</H5>
<CodeSpace>
<Collapse header="展开" expandable className="p10 bgPage">
<Text>
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
</CodeSpace>
<H5>动画时长</H5>
<CodeSpace>
<Collapse header="展开" duration={1000} className="p10 bgPage">
<Text>
此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容此处可自定义内容可自定义内容
</Text>
</Collapse>
</CodeSpace>
</Section>
</>
);
};
export default Demo;
2 changes: 2 additions & 0 deletions src/components/Collapse/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Collapse';
export * from './types';
44 changes: 44 additions & 0 deletions src/components/Collapse/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ReactNode, PropsWithChildren } from 'react';
import { ViewStyle, TextStyle } from 'react-native';
import { ContainerProps } from '../common';

type CollapseProps = PropsWithChildren<
{
/**
* header容器样式
*/
headerContainerStyle?: ViewStyle;
/**
* header
*/
header?: string | ReactNode;
/**
* header 样式
*/
headerStyle?: TextStyle;
/**
* header right
*/
headerRight?: string | ReactNode;
/**
* header right 样式
*/
headerRightStyle?: TextStyle;
/**
* 展开状态
* @default 'false'
*/
expandable?: boolean;
/**
* 动画时长
* @default 300
*/
duration?: number;
/**
* 自定义icon
*/
icon?: (state: boolean) => ReactNode;
} & ContainerProps
>;

export type { CollapseProps };
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './Touchable';
export * from './Highlight';
export * from './Badge';
export * from './Tag';
export * from './Collapse';

0 comments on commit 05476a3

Please sign in to comment.