UNPKG

@artsy/fresnel

Version:

An SSR compatible approach to CSS media query based responsive layouts for React.

283 lines (262 loc) 9.4 kB
import React, { CSSProperties } from "react"; import { BreakpointConstraint } from "./Breakpoints"; /** * A render prop that can be used to render a different container element than * the default `div`. * * @see {@link MediaProps.children}. */ export declare type RenderProp = (className: string, renderChildren: boolean) => React.ReactNode; export interface MediaBreakpointProps<BreakpointKey = string> { /** * Children will only be shown if the viewport matches the specified * breakpoint. That is, a viewport width that’s higher than the configured * breakpoint value, but lower than the value of the next breakpoint, if any * larger breakpoints exist at all. * * @example ```tsx // With breakpoints defined like these { xs: 0, sm: 768, md: 1024 } // Matches a viewport that has a width between 0 and 768 <Media at="xs">ohai</Media> // Matches a viewport that has a width between 768 and 1024 <Media at="sm">ohai</Media> // Matches a viewport that has a width over 1024 <Media at="md">ohai</Media> ``` * */ at?: BreakpointKey; /** * Children will only be shown if the viewport is smaller than the specified * breakpoint. * * @example ```tsx // With breakpoints defined like these { xs: 0, sm: 768, md: 1024 } // Matches a viewport that has a width from 0 to 767 <Media lessThan="sm">ohai</Media> // Matches a viewport that has a width from 0 to 1023 <Media lessThan="md">ohai</Media> ``` * */ lessThan?: BreakpointKey; /** * Children will only be shown if the viewport is greater than the specified * breakpoint. * * @example ```tsx // With breakpoints defined like these { xs: 0, sm: 768, md: 1024 } // Matches a viewport that has a width from 768 to infinity <Media greaterThan="xs">ohai</Media> // Matches a viewport that has a width from 1024 to infinity <Media greaterThan="sm">ohai</Media> ``` * */ greaterThan?: BreakpointKey; /** * Children will only be shown if the viewport is greater or equal to the * specified breakpoint. * * @example ```tsx // With breakpoints defined like these { xs: 0, sm: 768, md: 1024 } // Matches a viewport that has a width from 0 to infinity <Media greaterThanOrEqual="xs">ohai</Media> // Matches a viewport that has a width from 768 to infinity <Media greaterThanOrEqual="sm">ohai</Media> // Matches a viewport that has a width from 1024 to infinity <Media greaterThanOrEqual="md">ohai</Media> ``` * */ greaterThanOrEqual?: BreakpointKey; /** * Children will only be shown if the viewport is between the specified * breakpoints. That is, a viewport width that’s higher than or equal to the * small breakpoint value, but lower than the value of the large breakpoint. * * @example ```tsx // With breakpoints defined like these { xs: 0, sm: 768, md: 1024 } // Matches a viewport that has a width from 0 to 767 <Media between={["xs", "sm"]}>ohai</Media> // Matches a viewport that has a width from 0 to 1023 <Media between={["xs", "md"]}>ohai</Media> ``` * */ between?: [BreakpointKey, BreakpointKey]; } export interface MediaProps<BreakpointKey, Interaction> extends MediaBreakpointProps<BreakpointKey> { /** * Children will only be shown if the interaction query matches. * * @example ```tsx // With interactions defined like these { hover: "(hover: hover)" } // Matches an input device that is capable of hovering <Media interaction="hover">ohai</Media> ``` */ interaction?: Interaction; /** * The component(s) that should conditionally be shown, depending on the media * query matching. * * In case a different element is preferred, a render prop can be provided * that receives the class-name it should use to have the media query styling * applied. * * Additionally, the render prop receives a boolean that indicates wether or * not its children should be rendered, which will be `false` if the media * query is not included in the `onlyMatch` list. Use this flag if your * component’s children may be expensive to render and you want to avoid any * unnecessary work. * (@see {@link MediaContextProviderProps.onlyMatch} for details) * * @example * ```tsx const Component = () => ( <Media greaterThan="xs"> {(className, renderChildren) => ( <span className={className}> {renderChildren && "ohai"} </span> )} </Media> ) ``` * */ children: React.ReactNode | RenderProp; /** * Additional classNames to passed down and applied to Media container */ className?: string; /** * Additional styles to passed down and applied to Media container */ style?: CSSProperties; } export interface MediaContextProviderProps<M> { /** * This list of breakpoints and interactions can be used to limit the rendered * output to these. * * For instance, when a server knows for some user-agents that certain * breakpoints will never apply, omitting them altogether will lower the * rendered byte size. */ onlyMatch?: M[]; /** * Disables usage of browser MediaQuery API to only render at the current * breakpoint. * * Use this with caution, as disabling this means React components for all * breakpoints will be mounted client-side and all associated life-cycle hooks * will be triggered, which could lead to unintended side-effects. */ disableDynamicMediaQueries?: boolean; } export interface CreateMediaConfig { /** * The breakpoint definitions for your application. Width definitions should * start at 0. * * @see {@link createMedia} */ breakpoints: { [key: string]: number | string; }; /** * The interaction definitions for your application. */ interactions?: { [key: string]: string; }; } export interface CreateMediaResults<BreakpointKey, Interactions> { /** * The React component that you use throughout your application. * * @see {@link MediaBreakpointProps} */ Media: React.ComponentType<MediaProps<BreakpointKey, Interactions>>; /** * The React Context provider component that you use to constrain rendering of * breakpoints to a set list and to enable client-side dynamic constraining. * * @see {@link MediaContextProviderProps} */ MediaContextProvider: React.ComponentType<MediaContextProviderProps<BreakpointKey | Interactions> & { children: React.ReactNode; }>; /** * Generates a set of CSS rules that you should include in your application’s * styling to enable the hiding behaviour of your `Media` component uses. */ createMediaStyle(breakpointKeys?: BreakpointConstraint[]): string; /** * A list of your application’s breakpoints sorted from small to large. */ SortedBreakpoints: BreakpointKey[]; /** * Creates a list of your application’s breakpoints that support the given * widths and everything in between. */ findBreakpointsForWidths(fromWidth: number, throughWidth: number): BreakpointKey[] | undefined; /** * Finds the breakpoint that matches the given width. */ findBreakpointAtWidth(width: number): BreakpointKey | undefined; /** * Maps a list of values for various breakpoints to props that can be used * with the `Media` component. * * The values map to corresponding indices in the sorted breakpoints array. If * less values are specified than the number of breakpoints your application * has, the last value will be applied to all subsequent breakpoints. */ valuesWithBreakpointProps<SizeValue>(values: SizeValue[]): [SizeValue, MediaBreakpointProps<BreakpointKey>][]; } /** * This is used to generate a Media component, its context provider, and CSS * rules based on your application’s breakpoints and interactions. * * Note that the interaction queries are entirely up to you to define and they * should be written in such a way that they match when you want the element to * be hidden. * * @example * ```tsx const MyAppMedia = createMedia({ breakpoints: { xs: 0, sm: 768, md: 900 lg: 1024, xl: 1192, }, interactions: { hover: `not all and (hover:hover)` }, }) export const Media = MyAppMedia.Media export const MediaContextProvider = MyAppMedia.MediaContextProvider export const createMediaStyle = MyAppMedia.createMediaStyle ``` * */ export declare function createMedia<MediaConfig extends CreateMediaConfig, BreakpointKey extends keyof MediaConfig["breakpoints"], Interaction extends keyof MediaConfig["interactions"]>(config: MediaConfig): CreateMediaResults<BreakpointKey, Interaction>;