Skip to content

Commit

Permalink
Merge pull request #140 from Autarc/feat/allow-style-rules-subset
Browse files Browse the repository at this point in the history
Allow style rules subsetting
  • Loading branch information
damassi committed Jul 5, 2020
2 parents d937372 + 20a863c commit 89e6efc
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 30 deletions.
7 changes: 4 additions & 3 deletions README.md
Expand Up @@ -426,8 +426,9 @@ export const HomePage = () => {
Besides the `Media` and `MediaContextProvider` components, there's a
`createMediaStyle` function that produces the CSS styling for all possible media
queries that the `Media` instance can make use of while markup is being passed
from the server to the client during hydration. Be sure to insert this within a
`<style>` tag
from the server to the client during hydration. If only a subset of breakpoint
keys is used those can be optional specified as a parameter to minimize the
output. Be sure to insert this within a `<style>` tag
[in your document’s `<head>`](https://github.com/artsy/fresnel/blob/master/examples/ssr-rendering/src/server.tsx#L28).

It’s advisable to do this setup in its own module so that it can be easily
Expand All @@ -446,7 +447,7 @@ const ExampleAppMedia = createMedia({
})

// Generate CSS to be injected into the head
export const mediaStyle = ExampleAppMedia.createMediaStyle()
export const mediaStyle = ExampleAppMedia.createMediaStyle() // optional: .createMediaStyle(['at'])
export const { Media, MediaContextProvider } = ExampleAppMedia
```

Expand Down
62 changes: 42 additions & 20 deletions src/Breakpoints.ts
Expand Up @@ -14,24 +14,32 @@ function breakpointKey(breakpoint: string | Tuple) {
return Array.isArray(breakpoint) ? breakpoint.join("-") : breakpoint
}

export enum BreakpointKey {
at = "at",
lessThan = "lessThan",
greaterThan = "greaterThan",
greaterThanOrEqual = "greaterThanOrEqual",
between = "between",
}

/**
* 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> {
static validKeys() {
return ["at", "lessThan", "greaterThan", "greaterThanOrEqual", "between"]
return [
BreakpointKey.at,
BreakpointKey.lessThan,
BreakpointKey.greaterThan,
BreakpointKey.greaterThanOrEqual,
BreakpointKey.between,
]
}

private _sortedBreakpoints: ReadonlyArray<string>
private _breakpoints: { [key: string]: number }
private _mediaQueries: {
at: Map<string, string>
lessThan: Map<string, string>
greaterThan: Map<string, string>
greaterThanOrEqual: Map<string, string>
between: Map<string, string>
}
private _breakpoints: Record<string, number>
private _mediaQueries: Record<BreakpointKey, Map<string, string>>

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

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

Expand All @@ -75,7 +89,7 @@ export class Breakpoints<B extends string> {
}

public get dynamicResponsiveMediaQueries() {
return Array.from(this._mediaQueries.at.entries()).reduce(
return Array.from(this._mediaQueries[BreakpointKey.at].entries()).reduce(
(acc, [k, v]) => ({ ...acc, [k]: v }),
{}
)
Expand Down Expand Up @@ -118,8 +132,16 @@ export class Breakpoints<B extends string> {
}) as B | undefined
}

public toRuleSets() {
return Object.entries(this._mediaQueries).reduce(
public toRuleSets(keys = Breakpoints.validKeys()) {
const selectedMediaQueries = keys.reduce(
(mediaQueries, query) => {
mediaQueries[query] = this._mediaQueries[query]
return mediaQueries
},
{} as Record<BreakpointKey, Map<string, string>>
)

return Object.entries(selectedMediaQueries).reduce(
(acc: string[], [type, queries]) => {
queries.forEach((query, breakpoint) => {
// We need to invert the query, such that it matches when we want the
Expand Down
11 changes: 9 additions & 2 deletions src/Interactions.ts
@@ -1,12 +1,16 @@
import { createClassName, createRuleSet } from "./Utils"

export enum InteractionKey {
interaction = "interaction",
}

/**
* Encapsulates all interaction data needed by the Media component. The data is
* generated on initialization so no further runtime work is necessary.
*/
export class Interactions {
static validKeys() {
return ["interaction"]
return [InteractionKey.interaction]
}

private _interactions: { [key: string]: string }
Expand All @@ -20,7 +24,10 @@ export class Interactions {
(acc: string[], [name, query]) => {
return [
...acc,
createRuleSet(createClassName("interaction", name), query),
createRuleSet(
createClassName(InteractionKey.interaction, name),
query
),
]
},
[]
Expand Down
5 changes: 3 additions & 2 deletions src/Media.tsx
Expand Up @@ -4,6 +4,7 @@ import React from "react"
import { createResponsiveComponents } from "./DynamicResponsive"
import { MediaQueries } from "./MediaQueries"
import { intersection, propKey, createClassName } from "./Utils"
import { BreakpointKey } from "./Breakpoints"

/**
* A render prop that can be used to render a different container element than
Expand Down Expand Up @@ -240,7 +241,7 @@ export interface CreateMediaResults<B, I> {
* 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(): string
createMediaStyle(breakpointKeys?: BreakpointKey[]): string

/**
* A list of your application’s breakpoints sorted from small to large.
Expand Down Expand Up @@ -458,7 +459,7 @@ export function createMedia<
}
}

const MutuallyExclusiveProps = MediaQueries.validKeys()
const MutuallyExclusiveProps: string[] = MediaQueries.validKeys()

function validateProps(props) {
const selectedProps = Object.keys(props).filter(prop =>
Expand Down
6 changes: 3 additions & 3 deletions src/MediaQueries.ts
@@ -1,4 +1,4 @@
import { Breakpoints } from "./Breakpoints"
import { Breakpoints, BreakpointKey } from "./Breakpoints"
import { Interactions } from "./Interactions"
import { intersection } from "./Utils"
import { MediaBreakpointProps } from "./Media"
Expand Down Expand Up @@ -28,11 +28,11 @@ export class MediaQueries<B extends string> {
return this._breakpoints
}

public toStyle = () => {
public toStyle = (breakpointKeys?: BreakpointKey[]) => {
return [
// Don’t add any size to the layout
".fresnel-container{margin:0;padding:0;}",
...this._breakpoints.toRuleSets(),
...this._breakpoints.toRuleSets(breakpointKeys),
...this._interactions.toRuleSets(),
].join("\n")
}
Expand Down
13 changes: 13 additions & 0 deletions src/__test__/Media.test.tsx
Expand Up @@ -4,6 +4,7 @@ import React from "react"
import renderer, { ReactTestRendererJSON } from "react-test-renderer"
import { injectGlobal } from "styled-components"
import { createMedia } from "../Media"
import { BreakpointKey } from "../Breakpoints"
import { MediaQueries } from "../MediaQueries"
import ReactDOMServer from "react-dom/server"
import ReactDOM from "react-dom"
Expand Down Expand Up @@ -145,6 +146,18 @@ describe("Media", () => {
.toJSON()
expect(query.props.className).toContain("foo")
})

it("includes only style rules for specified breakpoint keys", () => {
const defaultMediaStyles = createMediaStyle()
expect(defaultMediaStyles).toContain(".fresnel-between-small-large")

const subsetMediaStyles = createMediaStyle([
BreakpointKey.at,
BreakpointKey.greaterThan,
])
expect(subsetMediaStyles).not.toContain(".fresnel-between-small-large")
expect(subsetMediaStyles).toContain(".fresnel-at-extra-small")
})
})

describe("concerning breakpoints", () => {
Expand Down
1 change: 1 addition & 0 deletions src/index.tsx
@@ -1 +1,2 @@
export { createMedia } from "./Media"
export { BreakpointKey } from "./Breakpoints"

0 comments on commit 89e6efc

Please sign in to comment.