/** * 响应式堆叠布局组件 * 移动端垂直堆叠,平板/桌面端水平排列 */ import React, { useMemo } from 'react'; import { View, StyleProp, ViewStyle, StyleSheet, FlexAlignType, } from 'react-native'; import { useResponsive, useResponsiveSpacing, FineBreakpointKey, useBreakpointGTE, } from '../../hooks/useResponsive'; export type StackDirection = 'horizontal' | 'vertical' | 'responsive'; export type StackAlignment = 'start' | 'center' | 'end' | 'stretch' | 'baseline'; export type StackJustify = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'; export interface ResponsiveStackProps { /** 子元素 */ children: React.ReactNode; /** 布局方向 */ direction?: StackDirection; /** 切换为水平布局的断点(仅在 direction='responsive' 时有效) */ horizontalBreakpoint?: FineBreakpointKey; /** 间距 */ gap?: Partial> | number; /** 是否允许换行 */ wrap?: boolean; /** 对齐方式(交叉轴) */ align?: StackAlignment; /** 分布方式(主轴) */ justify?: StackJustify; /** 自定义样式 */ style?: StyleProp; /** 子元素样式 */ itemStyle?: StyleProp; /** 是否反转顺序 */ reverse?: boolean; /** 是否等分空间 */ equalItem?: boolean; } const alignMap: Record = { start: 'flex-start', center: 'center', end: 'flex-end', stretch: 'stretch', baseline: 'baseline', }; const justifyMap: Record = { start: 'flex-start', center: 'center', end: 'flex-end', between: 'space-between', around: 'space-around', evenly: 'space-evenly', }; /** * 响应式堆叠布局组件 * * - 移动端垂直堆叠 * - 平板/桌面端水平排列 * - 支持间距和换行配置 * * @example * // 基础用法 - 自动响应式 * * * * * * * @example * // 自定义断点和间距 * * * * * * @example * // 固定水平方向 * * {items.map(item => )} * */ export function ResponsiveStack({ children, direction = 'responsive', horizontalBreakpoint = 'lg', gap: gapConfig, wrap = false, align = 'stretch', justify = 'start', style, itemStyle, reverse = false, equalItem = false, }: ResponsiveStackProps) { const { isMobile, isTablet } = useResponsive(); const isHorizontalBreakpoint = useBreakpointGTE(horizontalBreakpoint); // 计算间距 const gap = useMemo(() => { if (typeof gapConfig === 'number') { return gapConfig; } // 使用 hook 获取响应式间距 return gapConfig; }, [gapConfig]); const responsiveGap = useResponsiveSpacing(typeof gap === 'number' ? undefined : gap); const finalGap = typeof gap === 'number' ? gap : responsiveGap; // 确定布局方向 const isHorizontal = useMemo(() => { if (direction === 'horizontal') return true; if (direction === 'vertical') return false; // direction === 'responsive' return isHorizontalBreakpoint; }, [direction, isHorizontalBreakpoint]); // 构建容器样式 const containerStyle = useMemo((): ViewStyle => { const flexDirection = isHorizontal ? (reverse ? 'row-reverse' : 'row') : (reverse ? 'column-reverse' : 'column'); return { flexDirection, flexWrap: wrap ? 'wrap' : 'nowrap', alignItems: alignMap[align], justifyContent: justifyMap[justify], gap: finalGap, }; }, [isHorizontal, reverse, wrap, align, justify, finalGap]); // 处理子元素 const processedChildren = useMemo(() => { const childrenArray = React.Children.toArray(children); return childrenArray.map((child, index) => { if (!React.isValidElement(child)) { return child; } const childStyle: ViewStyle = {}; if (equalItem) { childStyle.flex = 1; } // 如果不是最后一个元素,添加间距 // 注意:使用 gap 后不需要手动添加 margin return ( {child} ); }); }, [children, equalItem, itemStyle]); return ( {processedChildren} ); } const styles = StyleSheet.create({ container: { width: '100%', }, equalItem: { flex: 1, minWidth: 0, // 防止 flex item 溢出 }, }); /** * 水平堆叠组件(快捷方式) */ export function HStack(props: Omit) { return ; } /** * 垂直堆叠组件(快捷方式) */ export function VStack(props: Omit) { return ; } export default ResponsiveStack;