import { Children, forwardRef, ForwardRefExoticComponent, ReactNode } from 'react'; import { buildStyles } from './boxTransforms'; import styles from './Flex.css'; import FlexItem from './FlexItem'; import wrapWithComponent from './utils/wrapWithComponent'; type Dimension = number | string; type Gap = | 0 | 0.25 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16; type AlignItems = 'start' | 'end' | 'center' | 'baseline' | 'stretch'; type Props = { /** * Aligns the flex container's lines within when there is extra space in the cross axis, similar to how `justify-content` aligns individual items within the main axis. */ alignContent?: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly' | 'stretch'; /** * Defines the default behaviour for how flex items are laid out along the cross axis on the current line. Think of it as the `justify-content` version for the cross axis (perpendicular to the main axis). * * Also available in responsive sizes: `smAlignItems`, `mdAlignItems`, `lgAlignItems`, `xlAlignItems`, `xxlAlignItems`, `xxxlAlignItems`. See the [Screen size page](https://gestalt.pinterest.systems/foundations/screen_sizes#Web-(px)) to learn more about viewport breakpoints in web. */ alignItems?: AlignItems; smAlignItems?: AlignItems; mdAlignItems?: AlignItems; lgAlignItems?: AlignItems; xlAlignItems?: AlignItems; xxlAlignItems?: AlignItems; xxxlAlignItems?: AlignItems; /** * Allows the default alignment (or the one specified by `align-items`) to be overridden for individual flex items. */ alignSelf?: 'auto' | 'start' | 'end' | 'center' | 'baseline' | 'stretch'; /** * Note that each child will be automatically wrapped in [Flex.Item](https://gestalt.pinterest.systems/web/flex#Flex.Item) to apply various styles. If specific flex styles are needed on a child, you can manually wrap it in Flex.Item to apply those styles. See [the Applying flex properties to children example](https://gestalt.pinterest.systems/web/flex#Applying-flex-properties-to-children) to learn more. */ children?: ReactNode; /** * Used to identify the element for testing purposes. */ dataTestId?: string; /** * Establishes the main axis, thus defining the direction flex items are placed in the flex container. */ direction?: 'row' | 'column'; /** * Defines how a flex item will be sized. "grow", equivalent to "flex: 1 1 auto", will size Flex relative to its parent, growing and shrinking based on available space. "shrink", equivalent to "flex: 0 1 auto" (the browser default), allows Flex to shrink if compressed but not grow if given extra space. Finally, "none", equivalent to "flex: 0 0 auto", preserves Flex's size based on child content regardless of its container's size. * Default: 'shrink' */ flex?: 'grow' | 'shrink' | 'none'; /** * Defines spacing between each child along the main and cross axes. Use an object to define different spacing for rows and columns. See the [Gap](https://gestalt.pinterest.systems/web/flex#Gap) variant to learn more. */ gap?: | Gap | { row: Gap; column: Gap; }; /** * Use numbers for pixels: `height={100}` and strings for percentages: `height="100%"`. */ height?: Dimension; /** * Defines the alignment along the main axis. It helps distribute extra free space left over when either all the flex items on a line are inflexible, or are flexible but have reached their maximum size. It also exerts some control over the alignment of items when they overflow the line. */ justifyContent?: 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'; /** * Use numbers for pixels: `maxHeight={100}` and strings for percentages: `maxHeight="100%"`. */ maxHeight?: Dimension; /** * Use numbers for pixels: `maxWidth={100}` and strings for percentages: `maxWidth="100%"`. */ maxWidth?: Dimension; /** * Use numbers for pixels: `minHeight={100}` and strings for percentages: `minHeight="100%"`. */ minHeight?: Dimension; /** * Use numbers for pixels: `minWidth={100}` and strings for percentages: `minWidth="100%"`. */ minWidth?: Dimension; /** * Defines how to handle content that extends beyond the Flex container. */ overflow?: 'visible' | 'hidden' | 'scroll' | 'scrollX' | 'scrollY' | 'auto'; /** * Ref that is forwarded to the underlying div element. */ ref?: HTMLDivElement; /** * Use numbers for pixels: `width={100}` and strings for percentages: `width="100%"`. */ width?: Dimension; /** * By default, flex items will all try to fit onto one line. You can change that and allow the items to wrap onto multiple lines, from top to bottom. */ wrap?: boolean; }; const allowedProps = [ 'alignContent', 'alignItems', 'smAlignItems', 'mdAlignItems', 'lgAlignItems', 'xlAlignItems', 'xxlAlignItems', 'xxxlAlignItems', 'alignSelf', 'children', 'direction', 'flex', 'height', 'justifyContent', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'overflow', 'width', 'wrap', ]; /** * [Flex](https://gestalt.pinterest.systems/web/flex) is a layout component with a very limited subset of the props available to [Box](https://gestalt.pinterest.systems/web/box) and a special prop of its own. * Use this component for flexbox layouts, especially when even spacing between elements is desired (see the `gap` prop!). * * ![Flex light mode](https://raw.githubusercontent.com/pinterest/gestalt/master/playwright/visual-test/Flex.spec.ts-snapshots/Flex-chromium-darwin.png) * */ const FlexWithForwardRef = forwardRef( ( { children: childrenProp, dataTestId, direction = 'row', gap = 0, justifyContent, ...rest }: Props, ref, ) => { const children = gap ? // @ts-expect-error - TS2533 - Object is possibly 'null' or 'undefined'. Children.map(childrenProp, (child, index) => { if (child === null || child === undefined) { return null; } return wrapWithComponent({ element: child, Component: FlexItem, props: {}, index, }); }).filter(Boolean) : childrenProp; const gapStyles = `${styles[`rowGap${typeof gap === 'number' ? gap * 100 : gap.row * 100}`]} ${ styles[`columnGap${typeof gap === 'number' ? gap * 100 : gap.column * 100}`] }`; const { passthroughProps, propsStyles } = buildStyles({ baseStyles: `${styles.Flex} ${gapStyles}`, props: { ...rest, children, direction, justifyContent, }, allowlistProps: allowedProps, }); // @ts-expect-error - TS2322 - Type '{ "data-test-id": string | undefined; className: string | null | undefined; style: InlineStyle | null | undefined; alignContent?: "center" | "start" | "end" | "stretch" | "between" | "around" | "evenly" | undefined; ... 18 more ...; wrap?: boolean | undefined; }' is not assignable to type 'DetailedHTMLProps, HTMLDivElement>'. return
; }, ); // Define the type for FlexWithForwardRef to include the subcomponent, otherwise typescript does not recognize Flex.Item interface FlexWithSubComponents extends ForwardRefExoticComponent> { Item: typeof FlexItem; } // Attach the subcomponent to the main component (FlexWithForwardRef as FlexWithSubComponents).Item = FlexItem; FlexWithForwardRef.displayName = 'Flex'; export default FlexWithForwardRef as FlexWithSubComponents;