Skip to content

Instantly share code, notes, and snippets.

@chaance
Created September 4, 2020 17:15
Show Gist options
  • Save chaance/7ea95688de8024fb2131b5ab69dace09 to your computer and use it in GitHub Desktop.
Save chaance/7ea95688de8024fb2131b5ab69dace09 to your computer and use it in GitHub Desktop.
Interop flex concept
import * as React from 'react';
import omit from 'lodash.omit';
import { cssReset, isUndefined } from '@interop-ui/utils';
import { forwardRef, createStyleObj } from '@interop-ui/react-utils';
/* -------------------------------------------------------------------------------------------------
* Flex
* -----------------------------------------------------------------------------------------------*/
const FLEX_NAME = 'Flex';
const FLEX_DEFAULT_TAG = 'div';
const GAP_PROP_NAMES = ['rowGap', 'columnGap', 'gap'];
type FlexDOMProps = React.ComponentPropsWithoutRef<typeof FLEX_DEFAULT_TAG>;
type FlexOwnProps = { rowGap?: number; columnGap?: number; gap?: number };
type FlexProps = FlexDOMProps & FlexOwnProps;
interface FlexStaticProps {
Item: typeof FlexItem;
}
const FlexContext = React.createContext<{
rowGap?: number;
columnGap?: number;
}>(null as any);
const Flex = forwardRef<typeof FLEX_DEFAULT_TAG, FlexProps, FlexStaticProps>(function Flex(
props,
forwardedRef
) {
const { as: Comp = FLEX_DEFAULT_TAG, style, children, ...restProps } = props;
const flexProps = omit(restProps, GAP_PROP_NAMES);
const columnGap = props.gap ?? props.columnGap;
const rowGap = props.gap ?? props.rowGap;
const hasGap = !isUndefined(rowGap) || !isUndefined(columnGap);
const context = React.useMemo(() => ({ columnGap, rowGap }), [columnGap, rowGap]);
if (hasGap) {
return (
<div style={{ overflow: 'hidden', flexGrow: 1 }}>
<Comp
{...flexProps}
{...interopDataAttrObj('root')}
ref={forwardedRef}
style={{
...style,
marginTop: isUndefined(rowGap) ? undefined : -rowGap,
marginLeft: isUndefined(columnGap) ? undefined : -columnGap,
}}
>
<FlexContext.Provider value={context}>{children}</FlexContext.Provider>
</Comp>
</div>
);
}
return (
<Comp {...flexProps} {...interopDataAttrObj('root')} style={style} ref={forwardedRef}>
{children}
</Comp>
);
});
/* -------------------------------------------------------------------------------------------------
* FlexItem
* -----------------------------------------------------------------------------------------------*/
const ITEM_NAME = 'Flex.Item';
const ITEM_DEFAULT_TAG = 'div';
type FlexItemDOMProps = React.ComponentPropsWithoutRef<typeof ITEM_DEFAULT_TAG>;
type FlexItemOwnProps = { xOffset?: number; yOffset?: number };
type FlexItemProps = FlexItemDOMProps & FlexItemOwnProps;
const FlexItem = forwardRef<typeof ITEM_DEFAULT_TAG, FlexItemProps>(function FlexItem(
props,
forwardedRef
) {
const { as: Comp = ITEM_DEFAULT_TAG, style, xOffset = 0, yOffset = 0, ...itemProps } = props;
const { rowGap, columnGap } = React.useContext(FlexContext) || {};
return (
<Comp
{...itemProps}
{...interopDataAttrObj('item')}
ref={forwardedRef}
style={{
...style,
marginTop: isUndefined(rowGap) ? undefined : rowGap + yOffset,
marginLeft: isUndefined(columnGap) ? undefined : columnGap + xOffset,
}}
/>
);
});
Flex.Item = FlexItem;
Flex.displayName = FLEX_NAME;
Flex.Item.displayName = ITEM_NAME;
const [styles, interopDataAttrObj] = createStyleObj(FLEX_NAME, {
root: {
...cssReset(FLEX_DEFAULT_TAG),
display: 'flex',
},
item: {
...cssReset(ITEM_DEFAULT_TAG),
flex: 1,
},
});
export { Flex, styles };
export type { FlexProps, FlexItemProps };
import * as React from 'react';
import { Flex as FlexPrimitive, styles } from './Flex';
export default { title: 'Flex' };
export const Basic = () => (
<Flex>
<FlexItem />
<FlexItem />
<FlexItem />
<FlexItem />
<FlexItem />
<FlexItem />
</Flex>
);
export const InlineStyle = () => (
<FlexInlineStyle>
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
</FlexInlineStyle>
);
export const Gap = () => (
<FlexInlineStyle gap={5}>
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
</FlexInlineStyle>
);
export const RowGap = () => (
<FlexInlineStyle rowGap={5}>
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
</FlexInlineStyle>
);
export const ColumnGap = () => (
<FlexInlineStyle columnGap={5}>
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
</FlexInlineStyle>
);
export const OffsetGap = () => (
<FlexInlineStyle gap={5}>
<FlexInlineStyleItem />
<FlexInlineStyleItem xOffset={20} />
<FlexInlineStyleItem />
<FlexInlineStyleItem />
<FlexInlineStyleItem yOffset={20} />
<FlexInlineStyleItem />
</FlexInlineStyle>
);
const Flex = (props: React.ComponentProps<typeof FlexPrimitive>) => (
<FlexPrimitive {...props} style={{ ...styles.root, ...props.style }} />
);
const FlexItem = (props: React.ComponentProps<typeof FlexPrimitive.Item>) => (
<FlexPrimitive.Item {...props} style={{ ...styles.item, ...props.style }} />
);
const FlexInlineStyle = (props: React.ComponentProps<typeof Flex>) => (
<Flex
{...props}
style={{
backgroundColor: 'ghostwhite',
flexWrap: 'wrap',
}}
/>
);
const FlexInlineStyleItem = (props: React.ComponentProps<typeof FlexItem>) => (
<FlexItem
{...props}
style={{
height: 50,
minWidth: 200,
borderWidth: 2,
borderStyle: 'solid',
borderColor: 'gainsboro',
}}
/>
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment