-
Matiss Janis Aboltins authoredMatiss Janis Aboltins authored
Stack.tsx 2.33 KiB
// @ts-strict-ignore
import React, {
Children,
type ComponentProps,
Fragment,
cloneElement,
forwardRef,
type ReactNode,
} from 'react';
import { type CSSProperties } from '../../style';
import { Text } from './Text';
import { View } from './View';
function getChildren(key, children) {
return Children.toArray(children).reduce(
(list, child) => {
if (child) {
if (
typeof child === 'object' &&
'type' in child &&
child.type === Fragment
) {
return list.concat(getChildren(child.key, child.props.children));
}
list.push({ key: key + child['key'], child });
return list;
}
return list;
},
[] as Array<{ key: string; child: ReactNode }>,
);
}
type StackProps = ComponentProps<typeof View> & {
direction?: CSSProperties['flexDirection'];
align?: string;
justify?: string;
spacing?: number;
debug?: boolean;
};
export const Stack = forwardRef<HTMLDivElement, StackProps>(
(
{
direction = 'column',
align,
justify,
spacing = 3,
children,
debug,
style,
...props
},
ref,
) => {
const isReversed = direction.endsWith('reverse');
const isHorizontal = direction.startsWith('row');
const validChildren = getChildren('', children);
return (
<View
style={{
flexDirection: direction,
alignItems: align,
justifyContent: justify,
...style,
}}
innerRef={ref}
{...props}
>
{validChildren.map(({ key, child }, index) => {
const isLastChild = validChildren.length === index + 1;
let marginProp;
if (isHorizontal) {
marginProp = isReversed ? 'marginLeft' : 'marginRight';
} else {
marginProp = isReversed ? 'marginTop' : 'marginBottom';
}
return cloneElement(
typeof child === 'string' ? <Text>{child}</Text> : child,
{
key,
style: {
...(debug && { borderWidth: 1, borderColor: 'red' }),
...(isLastChild ? null : { [marginProp]: spacing * 5 }),
...(child.props ? child.props.style : null),
},
},
);
})}
</View>
);
},
);
Stack.displayName = 'Stack';