Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand generic type names to be more descriptive #155

Merged
merged 1 commit into from Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
75 changes: 40 additions & 35 deletions src/Breakpoints.ts
Expand Up @@ -4,17 +4,20 @@ import { createRuleSet, createClassName } from "./Utils"
/**
* A union of possible breakpoint props.
*/
export type MediaBreakpointKey = keyof MediaBreakpointProps
export type BreakpointConstraintKey = keyof MediaBreakpointProps

type ValueBreakpointPropsTuple<T, B> = [T, MediaBreakpointProps<B>]
type ValueBreakpointPropsTuple<SizeValue, BreakpointKey> = [
SizeValue,
MediaBreakpointProps<BreakpointKey>
]

type Tuple = [string, string]

function breakpointKey(breakpoint: string | Tuple) {
return Array.isArray(breakpoint) ? breakpoint.join("-") : breakpoint
}

export enum BreakpointKey {
export enum BreakpointConstraint {
Comment on lines -17 to +20
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is technically a breaking change for this module's api, it should only affect the internal API.

The external API is preserved via this change.

at = "at",
lessThan = "lessThan",
greaterThan = "greaterThan",
Expand All @@ -26,20 +29,20 @@ export enum BreakpointKey {
* Encapsulates all breakpoint data needed by the Media component. The data is
* generated on initialization so no further runtime work is necessary.
*/
export class Breakpoints<B extends string> {
export class Breakpoints<BreakpointKey extends string> {
static validKeys() {
return [
BreakpointKey.at,
BreakpointKey.lessThan,
BreakpointKey.greaterThan,
BreakpointKey.greaterThanOrEqual,
BreakpointKey.between,
BreakpointConstraint.at,
BreakpointConstraint.lessThan,
BreakpointConstraint.greaterThan,
BreakpointConstraint.greaterThanOrEqual,
BreakpointConstraint.between,
]
}

private _sortedBreakpoints: ReadonlyArray<string>
private _breakpoints: Record<string, number>
private _mediaQueries: Record<BreakpointKey, Map<string, string>>
private _mediaQueries: Record<BreakpointConstraint, Map<string, string>>

constructor(breakpoints: { [key: string]: number }) {
this._breakpoints = breakpoints
Expand All @@ -61,38 +64,37 @@ export class Breakpoints<B extends string> {
)

this._mediaQueries = {
[BreakpointKey.at]: this._createBreakpointQueries(
BreakpointKey.at,
[BreakpointConstraint.at]: this._createBreakpointQueries(
BreakpointConstraint.at,
this._sortedBreakpoints
),
[BreakpointKey.lessThan]: this._createBreakpointQueries(
BreakpointKey.lessThan,
[BreakpointConstraint.lessThan]: this._createBreakpointQueries(
BreakpointConstraint.lessThan,
this._sortedBreakpoints.slice(1)
),
[BreakpointKey.greaterThan]: this._createBreakpointQueries(
BreakpointKey.greaterThan,
[BreakpointConstraint.greaterThan]: this._createBreakpointQueries(
BreakpointConstraint.greaterThan,
this._sortedBreakpoints.slice(0, -1)
),
[BreakpointKey.greaterThanOrEqual]: this._createBreakpointQueries(
BreakpointKey.greaterThanOrEqual,
[BreakpointConstraint.greaterThanOrEqual]: this._createBreakpointQueries(
BreakpointConstraint.greaterThanOrEqual,
this._sortedBreakpoints
),
[BreakpointKey.between]: this._createBreakpointQueries(
BreakpointKey.between,
[BreakpointConstraint.between]: this._createBreakpointQueries(
BreakpointConstraint.between,
betweenCombinations
),
}
}

public get sortedBreakpoints() {
return this._sortedBreakpoints as B[]
return this._sortedBreakpoints as BreakpointKey[]
}

public get dynamicResponsiveMediaQueries() {
return Array.from(this._mediaQueries[BreakpointKey.at].entries()).reduce(
(acc, [k, v]) => ({ ...acc, [k]: v }),
{}
)
return Array.from(
this._mediaQueries[BreakpointConstraint.at].entries()
).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
}

public get largestBreakpoint() {
Expand All @@ -109,12 +111,12 @@ export class Breakpoints<B extends string> {
}
const throughBreakpoint = this.findBreakpointAtWidth(throughWidth)
if (!throughBreakpoint || fromBreakpoint === throughBreakpoint) {
return [fromBreakpoint] as B[]
return [fromBreakpoint] as BreakpointKey[]
} else {
return this._sortedBreakpoints.slice(
this._sortedBreakpoints.indexOf(fromBreakpoint),
this._sortedBreakpoints.indexOf(throughBreakpoint) + 1
) as B[]
) as BreakpointKey[]
}
}

Expand All @@ -129,7 +131,7 @@ export class Breakpoints<B extends string> {
} else {
return width >= this._breakpoints[breakpoint]
}
}) as B | undefined
}) as BreakpointKey | undefined
}

public toRuleSets(keys = Breakpoints.validKeys()) {
Expand All @@ -138,7 +140,7 @@ export class Breakpoints<B extends string> {
mediaQueries[query] = this._mediaQueries[query]
return mediaQueries
},
{} as Record<BreakpointKey, Map<string, string>>
{} as Record<BreakpointConstraint, Map<string, string>>
)

return Object.entries(selectedMediaQueries).reduce(
Expand Down Expand Up @@ -201,10 +203,10 @@ export class Breakpoints<B extends string> {
return false
}

public valuesWithBreakpointProps = <T>(
values: T[]
): Array<ValueBreakpointPropsTuple<T, B>> => {
type ValueBreakpoints = [T, string[]]
public valuesWithBreakpointProps = <SizeValue>(
values: SizeValue[]
): Array<ValueBreakpointPropsTuple<SizeValue, BreakpointKey>> => {
type ValueBreakpoints = [SizeValue, string[]]
const max = values.length
const valueBreakpoints: ValueBreakpoints[] = []
let lastTuple: ValueBreakpoints
Expand All @@ -229,7 +231,10 @@ export class Breakpoints<B extends string> {
// prop, which unlike `between` is inclusive.
props.between = [breakpoints[0], valueBreakpoints[i + 1][1][0]]
}
return [value, props] as ValueBreakpointPropsTuple<T, B>
return [value, props] as ValueBreakpointPropsTuple<
SizeValue,
BreakpointKey
>
})
}

Expand Down Expand Up @@ -275,7 +280,7 @@ export class Breakpoints<B extends string> {
}

private _createBreakpointQueries(
key: MediaBreakpointKey,
key: BreakpointConstraintKey,
forBreakpoints: ReadonlyArray<string | [string, string]>
) {
return forBreakpoints.reduce<Map<string, string>>((map, breakpoint) => {
Expand Down
65 changes: 36 additions & 29 deletions src/Media.tsx
Expand Up @@ -4,7 +4,7 @@ import React from "react"
import { createResponsiveComponents } from "./DynamicResponsive"
import { MediaQueries } from "./MediaQueries"
import { intersection, propKey, createClassName } from "./Utils"
import { BreakpointKey } from "./Breakpoints"
import { BreakpointConstraint } from "./Breakpoints"

/**
* A render prop that can be used to render a different container element than
Expand All @@ -19,7 +19,7 @@ export type RenderProp = (

// TODO: All of these props should be mutually exclusive. Using a union should
// probably be made possible by https://github.com/Microsoft/TypeScript/pull/27408.
export interface MediaBreakpointProps<B = string> {
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
Expand All @@ -43,7 +43,7 @@ export interface MediaBreakpointProps<B = string> {
```
*
*/
at?: B
at?: BreakpointKey

/**
* Children will only be shown if the viewport is smaller than the specified
Expand All @@ -63,7 +63,7 @@ export interface MediaBreakpointProps<B = string> {
```
*
*/
lessThan?: B
lessThan?: BreakpointKey

/**
* Children will only be shown if the viewport is greater than the specified
Expand All @@ -83,7 +83,7 @@ export interface MediaBreakpointProps<B = string> {
```
*
*/
greaterThan?: B
greaterThan?: BreakpointKey

/**
* Children will only be shown if the viewport is greater or equal to the
Expand All @@ -106,7 +106,7 @@ export interface MediaBreakpointProps<B = string> {
```
*
*/
greaterThanOrEqual?: B
greaterThanOrEqual?: BreakpointKey

/**
* Children will only be shown if the viewport is between the specified
Expand All @@ -127,10 +127,11 @@ export interface MediaBreakpointProps<B = string> {
```
*
*/
between?: [B, B]
between?: [BreakpointKey, BreakpointKey]
}

export interface MediaProps<B, I> extends MediaBreakpointProps<B> {
export interface MediaProps<BreakpointKey, Interaction>
extends MediaBreakpointProps<BreakpointKey> {
/**
* Children will only be shown if the interaction query matches.
*
Expand All @@ -144,7 +145,7 @@ export interface MediaProps<B, I> extends MediaBreakpointProps<B> {
<Media interaction="hover">ohai</Media>
```
*/
interaction?: I
interaction?: Interaction

/**
* The component(s) that should conditionally be shown, depending on the media
Expand Down Expand Up @@ -221,32 +222,34 @@ export interface CreateMediaConfig {
interactions?: { [key: string]: string }
}

export interface CreateMediaResults<B, I> {
export interface CreateMediaResults<BreakpointKey, Interactions> {
/**
* The React component that you use throughout your application.
*
* @see {@link MediaBreakpointProps}
*/
Media: React.ComponentType<MediaProps<B, I>>
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<B | I>>
MediaContextProvider: React.ComponentType<
MediaContextProviderProps<BreakpointKey | Interactions>
>

/**
* 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?: BreakpointKey[]): string
createMediaStyle(breakpointKeys?: BreakpointConstraint[]): string

/**
* A list of your application’s breakpoints sorted from small to large.
*/
SortedBreakpoints: B[]
SortedBreakpoints: BreakpointKey[]

/**
* Creates a list of your application’s breakpoints that support the given
Expand All @@ -255,12 +258,12 @@ export interface CreateMediaResults<B, I> {
findBreakpointsForWidths(
fromWidth: number,
throughWidth: number
): B[] | undefined
): BreakpointKey[] | undefined

/**
* Finds the breakpoint that matches the given width.
*/
findBreakpointAtWidth(width: number): B | undefined
findBreakpointAtWidth(width: number): BreakpointKey | undefined

/**
* Maps a list of values for various breakpoints to props that can be used
Expand All @@ -270,7 +273,9 @@ export interface CreateMediaResults<B, I> {
* less values are specified than the number of breakpoints your application
* has, the last value will be applied to all subsequent breakpoints.
*/
valuesWithBreakpointProps<T>(values: T[]): Array<[T, MediaBreakpointProps<B>]>
valuesWithBreakpointProps<SizeValue>(
values: SizeValue[]
): Array<[SizeValue, MediaBreakpointProps<BreakpointKey>]>
}

/**
Expand Down Expand Up @@ -304,25 +309,25 @@ export interface CreateMediaResults<B, I> {
*
*/
export function createMedia<
C extends CreateMediaConfig,
B extends keyof C["breakpoints"],
I extends keyof C["interactions"]
>(config: C): CreateMediaResults<B, I> {
const mediaQueries = new MediaQueries<B>(
MediaConfig extends CreateMediaConfig,
BreakpointKey extends keyof MediaConfig["breakpoints"],
Interaction extends keyof MediaConfig["interactions"]
>(config: MediaConfig): CreateMediaResults<BreakpointKey, Interaction> {
const mediaQueries = new MediaQueries<BreakpointKey>(
config.breakpoints,
config.interactions || {}
)

const DynamicResponsive = createResponsiveComponents()

const MediaContext = React.createContext<MediaContextProviderProps<B | I>>({})
const MediaContext = React.createContext<
MediaContextProviderProps<BreakpointKey | Interaction>
>({})
MediaContext.displayName = "Media.Context"

const MediaContextProvider: React.SFC<MediaContextProviderProps<B | I>> = ({
disableDynamicMediaQueries,
onlyMatch,
children,
}) => {
const MediaContextProvider: React.SFC<
MediaContextProviderProps<BreakpointKey | Interaction>
> = ({ disableDynamicMediaQueries, onlyMatch, children }) => {
if (disableDynamicMediaQueries) {
return (
<MediaContext.Provider
Expand Down Expand Up @@ -363,7 +368,9 @@ export function createMedia<
}
}

const Media = class extends React.Component<MediaProps<B, I>> {
const Media = class extends React.Component<
MediaProps<BreakpointKey, Interaction>
> {
constructor(props) {
super(props)
validateProps(props)
Expand Down
4 changes: 2 additions & 2 deletions src/MediaQueries.ts
@@ -1,4 +1,4 @@
import { Breakpoints, BreakpointKey } from "./Breakpoints"
import { Breakpoints, BreakpointConstraint } from "./Breakpoints"
import { Interactions } from "./Interactions"
import { intersection } from "./Utils"
import { MediaBreakpointProps } from "./Media"
Expand Down Expand Up @@ -28,7 +28,7 @@ export class MediaQueries<B extends string> {
return this._breakpoints
}

public toStyle = (breakpointKeys?: BreakpointKey[]) => {
public toStyle = (breakpointKeys?: BreakpointConstraint[]) => {
return [
// Don’t add any size to the layout
".fresnel-container{margin:0;padding:0;}",
Expand Down
4 changes: 2 additions & 2 deletions src/Utils.ts
@@ -1,11 +1,11 @@
import { MediaBreakpointProps } from "./Media"
import { MediaBreakpointKey } from "./Breakpoints"
import { BreakpointConstraintKey } from "./Breakpoints"

/**
* Extracts the single breakpoint prop from the props object.
*/
export function propKey(breakpointProps: MediaBreakpointProps) {
return Object.keys(breakpointProps)[0] as MediaBreakpointKey
return Object.keys(breakpointProps)[0] as BreakpointConstraintKey
}

/**
Expand Down