Files
frontend/src/components/common/ResponsiveGrid.tsx

169 lines
4.2 KiB
TypeScript
Raw Normal View History

/**
*
*
*/
import React, { useMemo } from 'react';
import {
View,
StyleProp,
ViewStyle,
StyleSheet,
Dimensions,
} from 'react-native';
import {
useResponsive,
useColumnCount,
useResponsiveSpacing,
FineBreakpointKey,
refactor: restructure core architecture and responsive system This commit implements a major architectural refactor to improve modularity, type safety, and maintainability across the codebase. Key changes include: - **Responsive System Refactor**: Migrated from a monolithic `useResponsive` hook to a set of specialized, granular hooks (`useBreakpoint`, `useOrientation`, `usePlatform`, etc.) located in `src/presentation/hooks/responsive`. This reduces unnecessary re-renders and improves developer experience. - **Core Service Restructuring**: Introduced a new directory structure for services, including dedicated folders for `datasources`, `mappers`, and domain-specific types (`message`, `post`). - **Data Layer Improvements**: - Centralized JSON parsing logic in `src/database/core/jsonUtils.ts`. - Cleaned up repository implementations by removing redundant local utility functions. - Refactored `MessageMapper` to use the new centralized JSON utility. - **Type System Cleanup**: - Decomposed the large `src/types/dto.ts` into a modular `src/types/dto/` directory. - Simplified `src/types/index.ts` and introduced backward-compatible aliases for core entities. - **Utility Consolidation**: - Created a centralized `src/utils/formatTime.ts` to replace fragmented date formatting logic across various screens and components. - Removed deprecated responsive utility files in favor of the new hook-based system. - **Service Logic Refinement**: Refactored `ApiClient` and `WebSocketService` to use a centralized `showVerificationModal` service, removing duplicated state management for verification prompts.
2026-05-05 19:07:33 +08:00
} from '../../hooks';
export interface ResponsiveGridProps {
/** 子元素 */
children: React.ReactNode[];
/** 自定义样式 */
style?: StyleProp<ViewStyle>;
/** 容器样式 */
containerStyle?: StyleProp<ViewStyle>;
/** 项目样式 */
itemStyle?: StyleProp<ViewStyle>;
/** 列数配置,根据断点设置 */
columns?: Partial<Record<FineBreakpointKey, number>>;
/** 间距配置 */
gap?: Partial<Record<FineBreakpointKey, number>>;
/** 行间距(默认等于 gap */
rowGap?: Partial<Record<FineBreakpointKey, number>>;
/** 列间距(默认等于 gap */
columnGap?: Partial<Record<FineBreakpointKey, number>>;
/** 是否启用等宽列 */
equalColumns?: boolean;
/** 自定义列宽计算函数 */
getColumnWidth?: (containerWidth: number, columns: number, gap: number) => number;
/** 渲染空状态 */
renderEmpty?: () => React.ReactNode;
/** key 提取函数 */
keyExtractor?: (item: React.ReactNode, index: number) => string;
}
/**
*
*
* /
*
* @example
* <ResponsiveGrid
* columns={{ xs: 1, sm: 2, md: 3, lg: 4 }}
* gap={{ xs: 8, md: 16 }}
* >
* {items.map(item => <Card key={item.id} {...item} />)}
* </ResponsiveGrid>
*/
export function ResponsiveGrid({
children,
style,
containerStyle,
itemStyle,
columns: columnsConfig,
gap: gapConfig,
rowGap: rowGapConfig,
columnGap: columnGapConfig,
equalColumns = true,
getColumnWidth,
renderEmpty,
keyExtractor,
}: ResponsiveGridProps) {
const { width } = useResponsive();
const columns = useColumnCount(columnsConfig);
const defaultGap = useResponsiveSpacing(gapConfig);
const rowGap = useResponsiveSpacing(rowGapConfig ?? gapConfig);
const columnGap = useResponsiveSpacing(columnGapConfig ?? gapConfig);
// 计算列宽
const columnWidth = useMemo(() => {
if (getColumnWidth) {
return getColumnWidth(width, columns, columnGap);
}
if (equalColumns) {
// 等宽列计算:(总宽度 - (列数 - 1) * 列间距) / 列数
return (width - (columns - 1) * columnGap) / columns;
}
return undefined;
}, [width, columns, columnGap, equalColumns, getColumnWidth]);
// 将子元素分组为行
const rows = useMemo(() => {
const items = React.Children.toArray(children);
if (items.length === 0) return [];
const result: React.ReactNode[][] = [];
for (let i = 0; i < items.length; i += columns) {
result.push(items.slice(i, i + columns));
}
return result;
}, [children, columns]);
// 空状态处理
if (rows.length === 0 && renderEmpty) {
return (
<View style={[styles.container, containerStyle]}>
{renderEmpty()}
</View>
);
}
return (
<View style={[styles.container, containerStyle]}>
{rows.map((row, rowIndex) => (
<View
key={`row-${rowIndex}`}
style={[
styles.row,
{
marginBottom: rowIndex < rows.length - 1 ? rowGap : 0,
},
style,
]}
>
{row.map((child, colIndex) => {
const index = rowIndex * columns + colIndex;
const key = keyExtractor?.(child, index) ?? `grid-item-${index}`;
return (
<View
key={key}
style={[
styles.item,
{
width: columnWidth,
marginRight: colIndex < row.length - 1 ? columnGap : 0,
},
itemStyle,
]}
>
{child}
</View>
);
})}
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
},
row: {
flexDirection: 'row',
alignItems: 'flex-start',
},
item: {
flexShrink: 0,
},
});
export default ResponsiveGrid;