/** * This is a level above csstips * - It wraps up the csstips primitives into components */ import * as csstips from 'csstips'; import * as typestyle from 'typestyle'; import * as React from "react"; /** * Defaults used in layout */ export let defaultValues = { spacing: 10, breakpoints: { phone: 480, } } export function setDefaults(defaults: typeof defaultValues) { defaultValues = defaults; } declare global { interface Function { displayName?: string; } } /******** * * Primitives * ********/ /** * For that time you just need a visual vertical seperation */ export const SmallVerticalSpace = (props: { space?: number }) => { return
; } SmallVerticalSpace.displayName = "SmallVerticalSpace"; /** * For that time you just need a visual horizontal seperation */ export const SmallHorizontalSpace = (props: { space?: number }) => { return
; } SmallVerticalSpace.displayName = "SmallHorizontalSpace"; export interface PrimitiveProps extends React.HTMLProps { }; namespace ClassNames { export const content = typestyle.style(csstips.content); export const flex = typestyle.style(csstips.pass, csstips.flex); export const flexScrollY = typestyle.style(csstips.pass, csstips.flex, csstips.vertical, { overflowY: 'auto' }); export const pass = typestyle.style(csstips.pass); export const contentVertical = typestyle.style(csstips.content, csstips.vertical); export const contentVerticalCentered = typestyle.style(csstips.content, csstips.vertical, csstips.center); export const flexVerticalCentered = typestyle.style(csstips.flex, csstips.vertical, csstips.center); export const contentHorizontal = typestyle.style(csstips.content, csstips.horizontal); export const contentHorizontalCentered = typestyle.style(csstips.content, csstips.horizontal, csstips.center); export const flexHorizontalCentered = typestyle.style(csstips.flex, csstips.horizontal, csstips.center); export const flexVertical = typestyle.style(csstips.flex, csstips.vertical, { maxWidth: '100%' /*normalizing browser bugs*/ }); export const flexHorizontal = typestyle.style(csstips.flex, csstips.horizontal); } /** * Generally prefer an inline block (as that will wrap). * Use this for critical `content` driven *vertical* height * * Takes as much space as it needs, no more, no less */ export const Content = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.content, props.className); return (
); }; Content.displayName = "Content"; /** * Takes as much space as it needs, no more, no less */ export const InlineBlock = (props: PrimitiveProps) => { const className = typestyle.classes(typestyle.style({ display: 'inline-block' }), props.className); return (
); }; InlineBlock.displayName = "InlineBlock"; /** * Takes up all the parent space, no more, no less */ export const Flex = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.flex, props.className); return (
); }; Flex.displayName = "Flex"; /** * Takes up all the parent space, no more, no less and scrolls the children in Y if needed */ export const FlexScrollY = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.flexScrollY, props.className); return (
); }; FlexScrollY.displayName = "FlexScrollY"; /** * When you need a general purpose container. Use this instead of a `div` */ export const Pass = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.pass, props.className); return (
); }; Pass.displayName = "Pass"; /** * Provides a Vertical Container. For the parent it behaves like content. */ export const ContentVertical = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.contentVertical, props.className); return (
); }; ContentVertical.displayName = "ContentVertical"; /** * Quite commonly need horizontally centered text */ export const ContentVerticalCentered = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.contentVerticalCentered, props.className); return (
); } ContentVerticalCentered.displayName = "ContentVerticalCentered"; /** * Quite commonly need horizontally centered text */ export const FlexVerticalCentered = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.flexVerticalCentered, props.className); return (
); } FlexVerticalCentered.displayName = "FlexVerticalCentered"; /** * Provides a Horizontal Container. For the parent it behaves like content. */ export const ContentHorizontal = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.contentHorizontal, props.className); return (
); }; ContentHorizontal.displayName = "ContentHorizontal"; /** * Provides a Horizontal Container and centers its children in the cross dimension */ export const ContentHorizontalCentered = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.contentHorizontalCentered, props.className); return (
); }; ContentHorizontalCentered.displayName = "ContentHorizontalCentered"; /** * Provides a Vertical Container. For the parent it behaves like flex. */ export const FlexVertical = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.flexVertical, props.className); return (
); }; FlexVertical.displayName = "FlexVertical"; /** * Provides a Horizontal Container. For the parent it behaves like flex. */ export const FlexHorizontal = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.flexHorizontal, props.className); return (
); }; FlexHorizontal.displayName = "FlexHorizontal"; /** * Provides a Horizontal Container and centers its children in the cross dimension */ export const FlexHorizontalCentered = (props: PrimitiveProps) => { const className = typestyle.classes(ClassNames.flexHorizontalCentered, props.className); return (
); }; ContentHorizontalCentered.displayName = "FlexHorizontalCentered"; /******** * * Grid System * ********/ export interface MarginedProps extends PrimitiveProps { margin?: number; } /** * Lays out the children horizontally with * - ThisComponent: gets the overall Height (by max) of the children * - Children: get the Width : equally distributed from the parent Width * - Children: get the Height : sized by content * - ThisComponent: Puts a horizontal margin between each item */ export const ContentHorizontalMargined = (props: MarginedProps & { horizontalAlign?: 'left' /** default */ | 'right' }) => { const { margin, horizontalAlign, ...otherProps } = props; const spacing = (margin == null ? defaultValues.spacing : margin); const className = typestyle.classes(props.className, typestyle.style(csstips.horizontallySpaced(spacing)), horizontalAlign == 'right' && typestyle.style(csstips.endJustified) ); return ( ); } ContentHorizontalMargined.displayName = "ContentHorizontalMargined"; export const ContentHorizontalMarginedCentered = (props: MarginedProps) => { const { margin, ...otherProps } = props; const spacing = (margin == null ? defaultValues.spacing : margin); const className = typestyle.classes(props.className, typestyle.style(csstips.center, csstips.horizontallySpaced(spacing))); return ( ); } ContentHorizontalMarginedCentered.displayName = "ContentHorizontalMarginedCentered"; /** * Lays out the children horizontally with * - Parent: gets to chose the Width * - ThisComponent: gets the overall Height (by max) of the children * - Children: get the Width : equally distributed from the parent Width * - Children: get the Height : sized by content * - ThisComponent: Puts a horizontal margin between each item */ export const FlexHorizontalMargined = (props: MarginedProps) => { const { margin, ...otherProps } = props; const spacing = (margin == null ? defaultValues.spacing : margin); const className = typestyle.classes(props.className, typestyle.style(csstips.horizontallySpaced(spacing))); return ( ); } FlexHorizontalMargined.displayName = "FlexHorizontalMargined"; export const FlexVerticalMargined = (props: MarginedProps) => { const { margin, ...otherProps } = props; const spacing = (margin == null ? defaultValues.spacing : margin); const className = typestyle.classes(props.className, typestyle.style(csstips.verticallySpaced(spacing))); return ( ); } FlexHorizontalMargined.displayName = "FlexVerticalMargined"; /** * Could be ContentVerticalMargined but also wraps each child in Content to auto fix IE vertical layout issues */ export const ContentVerticalContentMargined = (props: MarginedProps) => { const { margin, children, ...otherProps } = props; const spacing = (margin == null ? defaultValues.spacing : margin); const className = typestyle.classes(props.className, typestyle.style(csstips.verticallySpaced(spacing))); return ( { React.Children.toArray(children).filter(c => !!c).map((child, i) => {child}) } ); } ContentVerticalContentMargined.displayName = "ContentVerticalContentMargined"; /** * Lays out the children vertically with * - Parent: gets to chose the overall Width * - ThisComponent: gets the Height : (by sum) of the children * - Children: get the Width : sized by content * - Children: get the Height : sized by content * - ThisComponent: Puts a margin between each item. * - ThisComponent: Puts a negative margin on itself to offset the margins of the children (prevents them from leaking out) */ export const GridMargined = (props: MarginedProps) => { const { margin, children, ...otherProps } = props; const spacing = (margin == null ? defaultValues.spacing : margin) + 'px'; const className = typestyle.style(csstips.wrap, { marginTop: '-' + spacing, marginLeft: '-' + spacing }, props.style || {} as any); return ( { React.Children.toArray(children).filter(c => !!c).map((child, i) => {child}) } ); } GridMargined.displayName = "GridMargined"; /** * Responsive Containers */ interface ResponsiveMarginedProps extends PrimitiveProps { margin?: number; breakpoint?: number; } export interface ResponsiveGridParentProps extends PrimitiveProps { margin?: number; breakpoint?: number; /** Alignment in horizontal mode */ horizontalAlign?: 'top' /** Default */ | 'center'; } export const ResponsiveGridParent = (props: ResponsiveGridParentProps) => { const { margin, breakpoint, horizontalAlign, ...otherProps } = props; const spacing = (margin == null ? defaultValues.spacing : margin); const breakpointNum = (breakpoint || defaultValues.breakpoints.phone) const breakpointPx = (num) => num + 'px'; const children = React.Children.toArray(props.children).filter(c => !!c); const alignment = horizontalAlign === 'center' ? [csstips.flexRoot, csstips.center] : [csstips.flexRoot, csstips.start]; const parentClassName = typestyle.classes( props.className, typestyle.style( csstips.layerParent, /** Lower than breakpoint: Vertical Margined */ typestyle.media({ minWidth: 0, maxWidth: breakpointNum }, csstips.verticallySpaced(spacing)), /** Bigger than breakpoint: Horizontal Margined */ typestyle.media({ minWidth: breakpointNum + 1 }, csstips.horizontallySpaced(spacing), ...alignment), ) ); const childClassName = (className) => typestyle.classes( className, typestyle.style( csstips.inlineBlock, /** Lower than breakpoint: full sized */ typestyle.media({ minWidth: 0, maxWidth: breakpointNum }, { width: '100%' }), /** Bigger than breakpoint: percent sized */ typestyle.media({ minWidth: breakpointNum + 1 }, { width: `calc(${(1 / children.length) * 100}% - ${spacing - spacing / children.length}px)` }), ) ); return (
{ children.map((child, i) =>
{child}
) }
); } ResponsiveGridParent.displayName = "ResponsiveGridParent"; /** * Just a display:block with vertical spacing between each child */ export const VerticalMargined = (props: MarginedProps) => { const { margin, ...otherProps } = props; const spacing = (margin == null ? defaultValues.spacing : margin); const className = typestyle.classes(props.className, typestyle.style(csstips.verticallySpaced(spacing))); return (
); } VerticalMargined.displayName = "VerticalMargined";