u
This commit is contained in:
1 parent
dee2eab404
commit
098a061308
10 files changed
+869
-155
No files matched your search
@@ -0,0 +1,283 @@
|
||||
import React, {
|
||||
useState,
|
||||
createContext,
|
||||
useContext,
|
||||
useMemo,
|
||||
forwardRef,
|
||||
} from 'react';
|
||||
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
|
||||
import { View, Dimensions, Platform, ViewProps } from 'react-native';
|
||||
import { gridStyle, gridItemStyle } from './styles';
|
||||
import { cssInterop } from 'nativewind';
|
||||
import {
|
||||
useBreakpointValue,
|
||||
getBreakPointValue,
|
||||
} from '@gluestack-ui/utils/hooks';
|
||||
const { width: DEVICE_WIDTH } = Dimensions.get('window');
|
||||
const GridContext = createContext<any>({});
|
||||
function arrangeChildrenIntoRows({
|
||||
childrenArray,
|
||||
colSpanArr,
|
||||
numColumns,
|
||||
}: {
|
||||
childrenArray: React.ReactNode[];
|
||||
colSpanArr: number[];
|
||||
numColumns: number;
|
||||
}) {
|
||||
let currentRow = 1;
|
||||
let currentRowTotalColSpan = 0;
|
||||
// store how many items in each row
|
||||
const rowItemsCount: {
|
||||
[key: number]: number[];
|
||||
} = {};
|
||||
for (let i = 0; i < childrenArray.length; i++) {
|
||||
const colSpan = colSpanArr[i];
|
||||
// if current row is full, go to next row
|
||||
if (currentRowTotalColSpan + colSpan > numColumns) {
|
||||
currentRow++;
|
||||
currentRowTotalColSpan = colSpan;
|
||||
} else {
|
||||
// if current row is not full, add colSpan to current row
|
||||
currentRowTotalColSpan += colSpan;
|
||||
}
|
||||
rowItemsCount[currentRow] = rowItemsCount[currentRow]
|
||||
? [...rowItemsCount[currentRow], i]
|
||||
: [i];
|
||||
}
|
||||
return rowItemsCount;
|
||||
}
|
||||
function generateResponsiveNumColumns({ gridClass }: { gridClass: string }) {
|
||||
const gridClassNamePattern = /\b(?:\w+:)?grid-cols-?\d+\b/g;
|
||||
const numColumns = gridClass?.match(gridClassNamePattern);
|
||||
if (!numColumns) {
|
||||
return 12;
|
||||
}
|
||||
const regex = /^(?:(\w+):)?grid-cols-?(\d+)$/;
|
||||
const result: any = {};
|
||||
numColumns.forEach((classname) => {
|
||||
const match = classname.match(regex);
|
||||
if (match) {
|
||||
const prefix = match[1] || 'default';
|
||||
const value = parseInt(match[2], 10);
|
||||
result[prefix] = value;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function generateResponsiveColSpans({
|
||||
gridItemClassName,
|
||||
}: {
|
||||
gridItemClassName: string;
|
||||
}) {
|
||||
const gridClassNamePattern = /\b(?:\w+:)?col-span-?\d+\b/g;
|
||||
const colSpan: any = gridItemClassName?.match(gridClassNamePattern);
|
||||
if (!colSpan) {
|
||||
return 1;
|
||||
}
|
||||
const regex = /^(?:(\w+):)?col-span-?(\d+)$/;
|
||||
const result: any = {};
|
||||
colSpan.forEach((classname: any) => {
|
||||
const match = classname.match(regex);
|
||||
if (match) {
|
||||
const prefix = match[1] || 'default';
|
||||
const value = parseInt(match[2], 10);
|
||||
result[prefix] = value;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
type IGridProps = ViewProps &
|
||||
VariantProps<typeof gridStyle> & {
|
||||
gap?: number;
|
||||
rowGap?: number;
|
||||
columnGap?: number;
|
||||
flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
||||
padding?: number;
|
||||
paddingLeft?: number;
|
||||
paddingRight?: number;
|
||||
paddingStart?: number;
|
||||
paddingEnd?: number;
|
||||
borderWidth?: number;
|
||||
borderLeftWidth?: number;
|
||||
borderRightWidth?: number;
|
||||
_extra: {
|
||||
className: string;
|
||||
};
|
||||
};
|
||||
const Grid = forwardRef<React.ComponentRef<typeof View>, IGridProps>(
|
||||
function Grid({ className, _extra, children, ...props }, ref) {
|
||||
const [calculatedWidth, setCalculatedWidth] = useState<number | null>(null);
|
||||
const gridClass = _extra?.className;
|
||||
const obj = generateResponsiveNumColumns({ gridClass });
|
||||
const responsiveNumColumns: any = useBreakpointValue(obj);
|
||||
const itemsPerRow = useMemo(() => {
|
||||
// get the colSpan of each child
|
||||
const colSpanArr = React.Children.map(children, (child: any) => {
|
||||
const gridItemClassName = child?.props?._extra?.className;
|
||||
const colSpan2 = getBreakPointValue(
|
||||
generateResponsiveColSpans({ gridItemClassName }),
|
||||
DEVICE_WIDTH
|
||||
);
|
||||
const colSpan = colSpan2 ? colSpan2 : 1;
|
||||
if (colSpan > responsiveNumColumns) {
|
||||
return responsiveNumColumns;
|
||||
}
|
||||
return colSpan;
|
||||
});
|
||||
const childrenArray = React.Children.toArray(children);
|
||||
const rowItemsCount = arrangeChildrenIntoRows({
|
||||
childrenArray,
|
||||
colSpanArr,
|
||||
numColumns: responsiveNumColumns,
|
||||
});
|
||||
return rowItemsCount;
|
||||
}, [responsiveNumColumns, children]);
|
||||
const childrenWithProps = React.Children.map(children, (child, index) => {
|
||||
if (React.isValidElement(child)) {
|
||||
return React.cloneElement(child, { key: index, index: index } as any);
|
||||
}
|
||||
return child;
|
||||
});
|
||||
const gridClassMerged = `${Platform.select({
|
||||
web: gridClass ?? '',
|
||||
})}`;
|
||||
const contextValue = useMemo(() => {
|
||||
return {
|
||||
calculatedWidth,
|
||||
numColumns: responsiveNumColumns,
|
||||
itemsPerRow,
|
||||
flexDirection: props?.flexDirection || 'row',
|
||||
gap: props?.gap || 0,
|
||||
columnGap: props?.columnGap || 0,
|
||||
};
|
||||
}, [calculatedWidth, itemsPerRow, responsiveNumColumns, props]);
|
||||
const borderLeftWidth = props?.borderLeftWidth || props?.borderWidth || 0;
|
||||
const borderRightWidth = props?.borderRightWidth || props?.borderWidth || 0;
|
||||
const borderWidthToSubtract = borderLeftWidth + borderRightWidth;
|
||||
return (
|
||||
<GridContext.Provider value={contextValue}>
|
||||
<View
|
||||
ref={ref}
|
||||
className={gridStyle({
|
||||
class: className + ' ' + gridClassMerged,
|
||||
})}
|
||||
onLayout={(event) => {
|
||||
const paddingLeftToSubtract =
|
||||
props?.paddingStart || props?.paddingLeft || props?.padding || 0;
|
||||
const paddingRightToSubtract =
|
||||
props?.paddingEnd || props?.paddingRight || props?.padding || 0;
|
||||
const gridWidth =
|
||||
Math.floor(event.nativeEvent.layout.width) -
|
||||
paddingLeftToSubtract -
|
||||
paddingRightToSubtract -
|
||||
borderWidthToSubtract;
|
||||
setCalculatedWidth(gridWidth);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{calculatedWidth && childrenWithProps}
|
||||
</View>
|
||||
</GridContext.Provider>
|
||||
);
|
||||
}
|
||||
);
|
||||
cssInterop(Grid, {
|
||||
className: {
|
||||
target: 'style',
|
||||
nativeStyleToProp: {
|
||||
gap: 'gap',
|
||||
rowGap: 'rowGap',
|
||||
columnGap: 'columnGap',
|
||||
flexDirection: 'flexDirection',
|
||||
padding: 'padding',
|
||||
paddingLeft: 'paddingLeft',
|
||||
paddingRight: 'paddingRight',
|
||||
paddingStart: 'paddingStart',
|
||||
paddingEnd: 'paddingEnd',
|
||||
borderWidth: 'borderWidth',
|
||||
borderLeftWidth: 'borderLeftWidth',
|
||||
borderRightWidth: 'borderRightWidth',
|
||||
},
|
||||
},
|
||||
});
|
||||
type IGridItemProps = ViewProps &
|
||||
VariantProps<typeof gridItemStyle> & {
|
||||
index?: number;
|
||||
_extra: {
|
||||
className: string;
|
||||
};
|
||||
};
|
||||
const GridItem = forwardRef<React.ComponentRef<typeof View>, IGridItemProps>(
|
||||
function GridItem({ className, _extra, ...props }, ref) {
|
||||
const {
|
||||
calculatedWidth,
|
||||
numColumns,
|
||||
itemsPerRow,
|
||||
flexDirection,
|
||||
gap,
|
||||
columnGap,
|
||||
} = useContext(GridContext);
|
||||
const gridItemClass = _extra?.className;
|
||||
const responsiveColSpan = (useBreakpointValue(
|
||||
generateResponsiveColSpans({ gridItemClassName: gridItemClass })
|
||||
) ?? 1) as number;
|
||||
const flexBasisValue = useMemo(() => {
|
||||
if (!calculatedWidth || !numColumns || responsiveColSpan <= 0) {
|
||||
return 'auto';
|
||||
}
|
||||
if (flexDirection?.includes('column')) {
|
||||
return 'auto';
|
||||
}
|
||||
// Find which row this item is in
|
||||
const row = Object.keys(itemsPerRow).find((key) => {
|
||||
return itemsPerRow[key].includes(props?.index);
|
||||
});
|
||||
if (!row) {
|
||||
return 'auto';
|
||||
}
|
||||
const rowColsCount = itemsPerRow[row]?.length || 1;
|
||||
const space = columnGap || gap || 0;
|
||||
// Calculate available width accounting for gaps
|
||||
const totalGapWidth = space * (rowColsCount - 1);
|
||||
const availableWidth = calculatedWidth - totalGapWidth;
|
||||
// Calculate the width for this item based on its column span
|
||||
const itemWidth = (availableWidth * responsiveColSpan) / numColumns;
|
||||
// Return the width directly instead of percentage for better native compatibility
|
||||
return Math.max(0, Math.floor(itemWidth));
|
||||
}, [
|
||||
calculatedWidth,
|
||||
responsiveColSpan,
|
||||
numColumns,
|
||||
columnGap,
|
||||
gap,
|
||||
flexDirection,
|
||||
itemsPerRow,
|
||||
props?.index,
|
||||
]);
|
||||
return (
|
||||
<View
|
||||
ref={ref}
|
||||
// @ts-expect-error : internal implementation for r-19/react-native-web
|
||||
gridItemClass={gridItemClass}
|
||||
className={gridItemStyle({
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
style={[
|
||||
{
|
||||
width:
|
||||
typeof flexBasisValue === 'number' ? flexBasisValue : undefined,
|
||||
flexBasis:
|
||||
typeof flexBasisValue === 'string' ? flexBasisValue : undefined,
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
},
|
||||
props.style,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Grid.displayName = 'Grid';
|
||||
GridItem.displayName = 'GridItem';
|
||||
export { Grid, GridItem };
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { gridStyle, gridItemStyle } from './styles';
|
||||
|
||||
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
|
||||
|
||||
type IGridProps = React.ComponentPropsWithoutRef<'div'> &
|
||||
VariantProps<typeof gridStyle> & {
|
||||
gap?: number;
|
||||
rowGap?: number;
|
||||
columnGap?: number;
|
||||
flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
||||
padding?: number;
|
||||
paddingLeft?: number;
|
||||
paddingRight?: number;
|
||||
paddingStart?: number;
|
||||
paddingEnd?: number;
|
||||
_extra: {
|
||||
className: string;
|
||||
};
|
||||
};
|
||||
|
||||
const Grid = React.forwardRef<HTMLDivElement, IGridProps>(function Grid(
|
||||
{ className, _extra, ...props },
|
||||
ref
|
||||
) {
|
||||
const gridClass = _extra?.className;
|
||||
const finalGridClass = gridClass ?? '';
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={gridStyle({
|
||||
class: className + ' ' + finalGridClass,
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
type IGridItemProps = React.ComponentPropsWithoutRef<'div'> &
|
||||
VariantProps<typeof gridItemStyle> & {
|
||||
index?: number;
|
||||
_extra: {
|
||||
className: string;
|
||||
};
|
||||
};
|
||||
const GridItem = React.forwardRef<HTMLDivElement, IGridItemProps>(
|
||||
function GridItem({ className, _extra, ...props }, ref) {
|
||||
const gridItemClass = _extra?.className;
|
||||
|
||||
const finalGridItemClass = gridItemClass ?? '';
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={gridItemStyle({
|
||||
class: className + ' ' + finalGridItemClass,
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Grid.displayName = 'Grid';
|
||||
GridItem.displayName = 'GridItem';
|
||||
|
||||
export { Grid, GridItem };
|
||||
@@ -0,0 +1,15 @@
|
||||
import { tva } from '@gluestack-ui/utils/nativewind-utils';
|
||||
import { isWeb } from '@gluestack-ui/utils/nativewind-utils';
|
||||
|
||||
const gridBaseStyle = isWeb
|
||||
? 'grid grid-cols-12'
|
||||
: 'box-border flex-row flex-wrap justify-start';
|
||||
const gridItemBaseStyle = isWeb ? 'w-auto col-span-1' : '';
|
||||
|
||||
export const gridStyle = tva({
|
||||
base: `w-full ${gridBaseStyle}`,
|
||||
});
|
||||
|
||||
export const gridItemStyle = tva({
|
||||
base: `w-full ${gridItemBaseStyle}`,
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { createProgress } from '@gluestack-ui/core/progress/creator';
|
||||
import { View } from 'react-native';
|
||||
import { tva } from '@gluestack-ui/utils/nativewind-utils';
|
||||
import {
|
||||
withStyleContext,
|
||||
useStyleContext,
|
||||
} from '@gluestack-ui/utils/nativewind-utils';
|
||||
import { cssInterop } from 'nativewind';
|
||||
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
|
||||
|
||||
const SCOPE = 'PROGRESS';
|
||||
export const UIProgress = createProgress({
|
||||
Root: withStyleContext(View, SCOPE),
|
||||
FilledTrack: View,
|
||||
});
|
||||
|
||||
cssInterop(UIProgress, { className: 'style' });
|
||||
cssInterop(UIProgress.FilledTrack, { className: 'style' });
|
||||
|
||||
const progressStyle = tva({
|
||||
base: 'bg-background-300 rounded-full w-full',
|
||||
variants: {
|
||||
orientation: {
|
||||
horizontal: 'w-full',
|
||||
vertical: 'h-full',
|
||||
},
|
||||
size: {
|
||||
'xs': 'h-1',
|
||||
'sm': 'h-2',
|
||||
'md': 'h-3',
|
||||
'lg': 'h-4',
|
||||
'xl': 'h-5',
|
||||
'2xl': 'h-6',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'xs',
|
||||
class: 'h-full w-1 justify-end',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'sm',
|
||||
class: 'h-full w-2 justify-end',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'md',
|
||||
class: 'h-full w-3 justify-end',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'lg',
|
||||
class: 'h-full w-4 justify-end',
|
||||
},
|
||||
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'xl',
|
||||
class: 'h-full w-5 justify-end',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: '2xl',
|
||||
class: 'h-full w-6 justify-end',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const progressFilledTrackStyle = tva({
|
||||
base: 'bg-primary-500 rounded-full',
|
||||
parentVariants: {
|
||||
orientation: {
|
||||
horizontal: 'w-full',
|
||||
vertical: 'h-full',
|
||||
},
|
||||
size: {
|
||||
'xs': 'h-1',
|
||||
'sm': 'h-2',
|
||||
'md': 'h-3',
|
||||
'lg': 'h-4',
|
||||
'xl': 'h-5',
|
||||
'2xl': 'h-6',
|
||||
},
|
||||
},
|
||||
parentCompoundVariants: [
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'xs',
|
||||
class: 'h-full w-1',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'sm',
|
||||
class: 'h-full w-2',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'md',
|
||||
class: 'h-full w-3',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'lg',
|
||||
class: 'h-full w-4',
|
||||
},
|
||||
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: 'xl',
|
||||
class: 'h-full w-5',
|
||||
},
|
||||
{
|
||||
orientation: 'vertical',
|
||||
size: '2xl',
|
||||
class: 'h-full w-6',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
type IProgressProps = VariantProps<typeof progressStyle> &
|
||||
React.ComponentProps<typeof UIProgress>;
|
||||
type IProgressFilledTrackProps = VariantProps<typeof progressFilledTrackStyle> &
|
||||
React.ComponentProps<typeof UIProgress.FilledTrack>;
|
||||
|
||||
const Progress = React.forwardRef<
|
||||
React.ComponentRef<typeof UIProgress>,
|
||||
IProgressProps
|
||||
>(function Progress(
|
||||
{ className, size = 'md', orientation = 'horizontal', ...props },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<UIProgress
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={progressStyle({ size, orientation, class: className })}
|
||||
context={{ size, orientation }}
|
||||
orientation={orientation}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const ProgressFilledTrack = React.forwardRef<
|
||||
React.ComponentRef<typeof UIProgress.FilledTrack>,
|
||||
IProgressFilledTrackProps
|
||||
>(function ProgressFilledTrack({ className, ...props }, ref) {
|
||||
const { size: parentSize, orientation: parentOrientation } =
|
||||
useStyleContext(SCOPE);
|
||||
|
||||
return (
|
||||
<UIProgress.FilledTrack
|
||||
ref={ref}
|
||||
className={progressFilledTrackStyle({
|
||||
parentVariants: {
|
||||
size: parentSize,
|
||||
orientation: parentOrientation,
|
||||
},
|
||||
class: className,
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export { Progress, ProgressFilledTrack };
|
||||
Reference in new issue
Block a user