Skip to content

Commit

Permalink
Replace enquire.js with matchmedia.js for staticMatch compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
Rendez committed Jul 25, 2016
1 parent b5183a7 commit c389845
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 57 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
- 0.1.4 Replace enquire.js with matchmedia.js for staticMatch compatibility.
- 0.1.3 Added initial tests for responsive HOC.
- 0.1.2 Added tests for ResponsiveProvider.
- 0.1.1 Added option `only` to the responsive wrapper.
- 0.1.0 Initial release.
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-media-responsive",
"version": "0.1.3",
"version": "0.1.4",
"description": "Provider and media query wrapper for your React components",
"main": "./lib/index.js",
"scripts": {
Expand Down Expand Up @@ -61,7 +61,6 @@
"eslint-config-rackt": "^1.1.1",
"eslint-plugin-react": "^5.2.2",
"expect": "^1.20.2",
"hyphenate-style-name": "^1.0.0",
"jsdom": "~5.4.3",
"mocha": "^2.5.3",
"react": "^15.2.1",
Expand All @@ -71,7 +70,6 @@
},
"dependencies": {
"can-use-dom": "^0.1.0",
"enquire.js": "^2.1.1",
"hoist-non-react-statics": "^1.2.0",
"hyphenate-style-name": "^1.0.1",
"invariant": "^2.0.0",
Expand Down
55 changes: 36 additions & 19 deletions src/components/ResponsiveProvider.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Component, PropTypes, Children } from 'react'
import mediaShape from '../utils/mediaShape'
import matchMedia from 'matchmedia'
import hyphenate from 'hyphenate-style-name'
import mediaQuery from '../utils/mediaQuery'
import storeShape from '../utils/storeShape'
import toQuery from '../utils/toQuery'
import canUseDOM from 'can-use-dom'
import enquire from 'enquire.js'

/**
* Creates a Responsive store that holds the state tree.
Expand All @@ -12,21 +13,27 @@ import enquire from 'enquire.js'
*
* There should only be a single store in your app.
*
* @param {Object} queries An object that is used to subscribe to matching
* @param {Object} [queries] An object that is used to subscribe to matching
* queries, and that will in turn modify the current state of responsiveness.
*
* @param {Object} [initialState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* @param {Object} [values] The initial values. You may optionally specify it
* to hydrate the state from the server in universal apps.
*
* @returns {Store} A Responsive store that lets you read the state, set media
* queries and subscribe to changes.
*/
function createStore(queries, initialState = {}) {
function createStore(queries, values) {
const listeners = []
let currentState = initialState
let currentState = {}
let timeoutID = 0

if (values) {
values = Object.keys(values).reduce(function(result, key) {
result[hyphenate(key)] = values[key]
return result
}, {});
}

/**
* Reads the state tree managed by the store.
*
Expand Down Expand Up @@ -66,19 +73,19 @@ function createStore(queries, initialState = {}) {
}

/**
* Registers a media query object using enquire.js. Everytime a match or
* unmatch is trigered, the current state changes to reflect it with a boolean
* value. Then every registered callback in listeners is executed.
* Registers a media query object using addListener(). Everytime a match
* is trigered, the current state changes to reflect it with a boolean
* value. Then every registered callback in `listeners` is executed.
*
* @param {Object} obj key-value pairs with media queries to match
* @param {String} name of media match that will be flagged with a boolean
* @returns {void}
*/
function media(obj, name) {
enquire.register(toQuery(obj), {
match: handler(name, true),
unmatch: handler(name, false)
})
const mql = matchMedia(toQuery(obj), values)
const handlerFn = handler(name, mql);
mql.addListener(handlerFn)
handlerFn()
}

/**
Expand All @@ -90,10 +97,14 @@ function createStore(queries, initialState = {}) {
* @param {Boolean} value representing the state of the matched media query
* @returns {Function} returns a wrapper function triggering callbacks
*/
function handler(name, value) {
function handler(name, mql) {
return function () {
if (mql.matches === currentState[name]) {
return
}

const newState = {}
newState[name] = value
newState[name] = mql.matches

currentState = { ...currentState, ...newState }

Expand Down Expand Up @@ -135,9 +146,15 @@ export default class ResponsiveProvider extends Component {
}
}

function getPropTypeMatchers() {
const matchers = { ...mediaQuery.matchers }
delete matchers.type
return PropTypes.shape(matchers)
}

ResponsiveProvider.propTypes = {
media: mediaShape.isRequired,
values: PropTypes.objectOf(PropTypes.bool),
media: PropTypes.shape(mediaQuery.all).isRequired,
values: getPropTypeMatchers(),
children: PropTypes.element.isRequired
}

Expand Down
98 changes: 98 additions & 0 deletions src/utils/mediaQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Full credit to react-responsive for the mediaShape declaration:
* https://github.com/contra/react-responsive/blob/master/src/mediaQuery.js
*/
import { PropTypes } from 'react'

const { number, string, bool, oneOf, oneOfType } = PropTypes
const stringOrNumber = oneOfType([ string, number ])

// properties that match media queries
const matchers = {
orientation: oneOf([
'portrait',
'landscape'
]),

scan: oneOf([
'progressive',
'interlace'
]),

aspectRatio: string,
deviceAspectRatio: string,

height: stringOrNumber,
deviceHeight: stringOrNumber,

width: stringOrNumber,
deviceWidth: stringOrNumber,

color: bool,

colorIndex: bool,

monochrome: bool,
resolution: stringOrNumber
}

// media features
const features = {
minAspectRatio: string,
maxAspectRatio: string,
minDeviceAspectRatio: string,
maxDeviceAspectRatio: string,

minHeight: stringOrNumber,
maxHeight: stringOrNumber,
minDeviceHeight: stringOrNumber,
maxDeviceHeight: stringOrNumber,

minWidth: stringOrNumber,
maxWidth: stringOrNumber,
minDeviceWidth: stringOrNumber,
maxDeviceWidth: stringOrNumber,

minColor: number,
maxColor: number,

minColorIndex: number,
maxColorIndex: number,

minMonochrome: number,
maxMonochrome: number,

minResolution: stringOrNumber,
maxResolution: stringOrNumber
}

Object.assign(features, matchers)

// media types
const types = {
all: bool,
grid: bool,
aural: bool,
braille: bool,
handheld: bool,
print: bool,
projection: bool,
screen: bool,
tty: bool,
tv: bool,
embossed: bool
}

const all = {}
Object.assign(all, types)
Object.assign(all, features)

// add the type property
Object.assign(matchers, { type: Object.keys(types) })

module.exports = {
all: all,
types: types,
matchers: matchers,
features: features
}
3 changes: 0 additions & 3 deletions src/utils/mediaShape.js

This file was deleted.

57 changes: 32 additions & 25 deletions src/utils/toQuery.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
/**
* Full credit to react-responsive for the mediaShape declaration:
* https://github.com/contra/react-responsive/blob/master/src/toQuery.js
*/
import hyphenate from 'hyphenate-style-name'
import mq from './mediaQuery'

const re = /[height|width]$/
function negate(cond) {
return 'not ' + cond
}

function isDimension(feature) {
return re.test(feature)
function keyVal(k, v) {
// px shorthand
if (typeof v === 'number') {
v = `${v}px`
}
if (v === true) {
return k
}
if (v === false) {
return negate(k)
}

const realKey = hyphenate(k)
return `(${realKey}: ${v})`
}
function negate(cond) {
return `not ${cond}`

function join(conds) {
return conds.join(' and ')
}

export default function toQuery(obj) {
let mq = ''
const features = Object.keys(obj)
export default function(obj) {
const rules = []

features.forEach(function (feature, index) {
let value = obj[feature]
feature = hyphenate(feature)
// Add px to dimension features
if (isDimension(feature) && typeof value === 'number') {
value = `${value}px`
}
if (value === true) {
mq += feature
} else if (value === false) {
mq += negate(feature)
} else {
mq += `(${feature}: ${value})`
}
if (index < features.length-1) {
mq += ' and '
Object.keys(mq.all).forEach(function(k) {
var v = obj[k]
if (v != null) {
rules.push(keyVal(k, v))
}
})

return mq
return join(rules)
}
2 changes: 1 addition & 1 deletion test/components/ResponsiveProvider.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import expect from 'expect'
import React, { Component } from 'react'
import React, { PropTypes, Component } from 'react'
import TestUtils from 'react-addons-test-utils'
import storeShape from '../../src/utils/storeShape'
import { ResponsiveProvider as Provider } from '../../src/index'
Expand Down
11 changes: 8 additions & 3 deletions test/components/responsive.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ describe('React', () => {

it('should pass state and props to the given component', () => {
const media = {
fromTablet: {}
fromTablet: {
minWidth: 768
}
}
const values = {
width: 1024
}

@responsive()
Expand All @@ -71,7 +76,7 @@ describe('React', () => {
}

const container = TestUtils.renderIntoDocument(
<Provider media={media}>
<Provider media={media} values={values}>
<Container pass="through" responsive={{ fromTablet: false, untilTablet: true }} />
</Provider>
)
Expand All @@ -83,7 +88,7 @@ describe('React', () => {
})

it('should subscribe class components to the store changes')

it('should subscribe pure function components to the store changes')

it('should unsubscribe before unmounting')
Expand Down
3 changes: 0 additions & 3 deletions test/setup.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { jsdom } from 'jsdom'
import matchMedia from 'matchmedia'

global.document = jsdom('<!doctype html><html><body><div id="app"></div></body></html>')
global.window = document.defaultView
global.navigator = global.window.navigator

global.window.matchMedia = matchMedia

0 comments on commit c389845

Please sign in to comment.