|
|
@@ -0,0 +1,184 @@
|
|
|
+import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
|
+import { View, ScrollView } from '@tarojs/components';
|
|
|
+import './CategorySidebar.css';
|
|
|
+
|
|
|
+export interface CategorySidebarProps {
|
|
|
+ /** 当前选中的分类索引 */
|
|
|
+ activeKey?: number;
|
|
|
+ /** 自定义类名 */
|
|
|
+ className?: string;
|
|
|
+ /** 选中项改变时的回调 */
|
|
|
+ onChange?: (index: number) => void;
|
|
|
+ /** 子组件 */
|
|
|
+ children?: React.ReactNode;
|
|
|
+}
|
|
|
+
|
|
|
+interface CategorySidebarContextType {
|
|
|
+ registerItem: (item: any) => void;
|
|
|
+ unregisterItem: (item: any) => void;
|
|
|
+ setActive: (index: number) => Promise<void>;
|
|
|
+ activeKey: number;
|
|
|
+}
|
|
|
+
|
|
|
+export const CategorySidebarContext = React.createContext<CategorySidebarContextType | null>(null);
|
|
|
+
|
|
|
+const CategorySidebar: React.FC<CategorySidebarProps> = (props) => {
|
|
|
+ const { activeKey = 0, className = '', onChange, children } = props;
|
|
|
+
|
|
|
+ const [currentActive, setCurrentActive] = useState<number>(activeKey);
|
|
|
+ const [childrenList, setChildrenList] = useState<any[]>([]);
|
|
|
+ const [topRightRadiusItemIndexs, setTopRightRadiusItemIndexs] = useState<number[]>([]);
|
|
|
+ const [bottomRightRadiusItemIndexs, setBottomRightRadiusItemIndexs] = useState<number[]>([]);
|
|
|
+
|
|
|
+ const childrenRef = useRef<any[]>([]);
|
|
|
+ const currentActiveRef = useRef<number>(currentActive);
|
|
|
+ const topRightRadiusItemIndexsRef = useRef<number[]>([]);
|
|
|
+ const bottomRightRadiusItemIndexsRef = useRef<number[]>([]);
|
|
|
+
|
|
|
+ // 更新 ref 值
|
|
|
+ useEffect(() => {
|
|
|
+ childrenRef.current = childrenList;
|
|
|
+ currentActiveRef.current = currentActive;
|
|
|
+ topRightRadiusItemIndexsRef.current = topRightRadiusItemIndexs;
|
|
|
+ bottomRightRadiusItemIndexsRef.current = bottomRightRadiusItemIndexs;
|
|
|
+ }, [childrenList, currentActive, topRightRadiusItemIndexs, bottomRightRadiusItemIndexs]);
|
|
|
+
|
|
|
+ // 注册子组件
|
|
|
+ const registerItem = useCallback((item: any) => {
|
|
|
+ setChildrenList(prev => {
|
|
|
+ const newList = [...prev, item];
|
|
|
+ childrenRef.current = newList;
|
|
|
+ return newList;
|
|
|
+ });
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // 注销子组件
|
|
|
+ const unregisterItem = useCallback((item: any) => {
|
|
|
+ setChildrenList(prev => {
|
|
|
+ const newList = prev.filter(i => i !== item);
|
|
|
+ childrenRef.current = newList;
|
|
|
+ return newList;
|
|
|
+ });
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // 计算顶部圆角项索引
|
|
|
+ const getTopRightRadiusItemIndexs = useCallback((activeKey: number, children: any[]) => {
|
|
|
+ const { length } = children;
|
|
|
+ if (activeKey !== 0 && activeKey < length - 1) return [0, activeKey + 1];
|
|
|
+ if (activeKey !== 0) return [0];
|
|
|
+ if (activeKey < length - 1) return [activeKey + 1];
|
|
|
+ return [];
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // 计算底部圆角项索引
|
|
|
+ const getBottomRightRadiusItemIndexs = useCallback((activeKey: number) => {
|
|
|
+ if (activeKey !== 0) return [activeKey - 1];
|
|
|
+ return [];
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // 设置选中状态
|
|
|
+ const setActive = useCallback(async (activeKey: number, isChildrenChange = false) => {
|
|
|
+ const children = childrenRef.current;
|
|
|
+ const currentActive = currentActiveRef.current;
|
|
|
+ const preTopRightRadiusItemIndexs = topRightRadiusItemIndexsRef.current;
|
|
|
+ const preBottomRightRadiusItemIndexs = bottomRightRadiusItemIndexsRef.current;
|
|
|
+
|
|
|
+ if (!children.length) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (activeKey === currentActive && !isChildrenChange) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setCurrentActive(activeKey);
|
|
|
+
|
|
|
+ const newTopRightRadiusItemIndexs = getTopRightRadiusItemIndexs(activeKey, children);
|
|
|
+ const newBottomRightRadiusItemIndexs = getBottomRightRadiusItemIndexs(activeKey);
|
|
|
+
|
|
|
+ setTopRightRadiusItemIndexs(newTopRightRadiusItemIndexs);
|
|
|
+ setBottomRightRadiusItemIndexs(newBottomRightRadiusItemIndexs);
|
|
|
+
|
|
|
+ const promises: Promise<void>[] = [];
|
|
|
+
|
|
|
+ // 将旧的选中项改为 false
|
|
|
+ if (currentActive !== activeKey && children[currentActive]) {
|
|
|
+ promises.push(children[currentActive].setActive(false));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将新的选中项改为 true
|
|
|
+ if (children && children[activeKey]) {
|
|
|
+ promises.push(children[activeKey].setActive(true));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 移除旧的圆角效果
|
|
|
+ preTopRightRadiusItemIndexs.forEach((item) => {
|
|
|
+ if (children[item]) {
|
|
|
+ promises.push(children[item].setTopRightRadius(false));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ preBottomRightRadiusItemIndexs.forEach((item) => {
|
|
|
+ if (children[item]) {
|
|
|
+ promises.push(children[item].setBottomRightRadius(false));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 应用新的圆角效果
|
|
|
+ newTopRightRadiusItemIndexs.forEach((item) => {
|
|
|
+ if (children[item]) {
|
|
|
+ promises.push(children[item].setTopRightRadius(true));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ newBottomRightRadiusItemIndexs.forEach((item) => {
|
|
|
+ if (children[item]) {
|
|
|
+ promises.push(children[item].setBottomRightRadius(true));
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ await Promise.all(promises);
|
|
|
+
|
|
|
+ // 触发 onChange 回调
|
|
|
+ if (onChange && !isChildrenChange) {
|
|
|
+ onChange(activeKey);
|
|
|
+ }
|
|
|
+ }, [getTopRightRadiusItemIndexs, getBottomRightRadiusItemIndexs, onChange]);
|
|
|
+
|
|
|
+ // 监听 activeKey 变化
|
|
|
+ useEffect(() => {
|
|
|
+ if (activeKey !== currentActive) {
|
|
|
+ setActive(activeKey);
|
|
|
+ }
|
|
|
+ }, [activeKey, setActive]);
|
|
|
+
|
|
|
+ // 子组件变化时重新设置选中状态
|
|
|
+ useEffect(() => {
|
|
|
+ if (childrenList.length > 0) {
|
|
|
+ setActive(currentActive, true);
|
|
|
+ }
|
|
|
+ }, [childrenList.length, setActive]);
|
|
|
+
|
|
|
+ const contextValue: CategorySidebarContextType = {
|
|
|
+ registerItem,
|
|
|
+ unregisterItem,
|
|
|
+ setActive,
|
|
|
+ activeKey: currentActive,
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <CategorySidebarContext.Provider value={contextValue}>
|
|
|
+ <ScrollView
|
|
|
+ className={`category-sidebar ${className}`}
|
|
|
+ scrollY
|
|
|
+ scrollWithAnimation
|
|
|
+ >
|
|
|
+ <View className="category-sidebar__content">
|
|
|
+ {children}
|
|
|
+ </View>
|
|
|
+ </ScrollView>
|
|
|
+ </CategorySidebarContext.Provider>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default CategorySidebar;
|