1.0.5 • Published 4 years ago

@greenberry/mq v1.0.5

Weekly downloads
1
License
-
Repository
github
Last release
4 years ago

Media Query Helper

This package is intended for a React development environment and makes it able to either style (using styles components) or listen to a resize event using a custom hook.

Styling

The mq() utiltiy will resolve styling using a similar syntax as used within styled components. It can output a @media query and follows a familiar syntax. The mq() utility will resolve any styling (and props) used inside a styled component. This means that any arguments will automatically be parsed and returned as a concatenated string.

Getting started

You can use the mq() utility as long as it's imported in your component. The syntax for styling is simple, options (arguments) will be discussed below. The following example shows a a simple styled component using Emotion.

import { mq } from '@greenberry/mq';

const Example = styled('div')`
  width: 50%;
  height: auto;
  display: block;

  ${mq('tablet')`
      width: 75%;
  `}

  ${mq(['phone', 'tablet-small'])`
      width: 100%;
  `}
`;

This will parse the component to have the following styling, class name will normally be generated by Emotion.

.example {
  width: 50%;
  height: auto;
  display: block;

  @media screen (min-width: 0rem) and (max-width: 60rem) {
    width: 75%;
  }

  @media screen (min-width: 27.5rem) and (max-width: 47.5rem) {
    width: 75%;
  }
}

Breakpoint configuration

The mq() util offers multiple options to resolve a media query. By default the utiltiy will use a default setup for breakpoints. This configuration can be overruled using the ViewProvider. You can also pass a value as a string or number. It's however best to use named values for consistancy.

const namesConfig = {
  desktopWide: 1440,
  desktop: 1280,
  tabletWide: 1080,
  tablet: 960,
  tabletSmall: 760,
  phablet: 640,
  phoneWide: 560,
  phone: 420,
};

The values in this config resemble a px unit, using the mq() function, numbers will be parsed to rem values. For instance (with a base font-size of 16px) the value 1280 will resolve as 80rem. The following table shows the rem values of the default configuration.

valuenumberrem
desktopWide144090rem
desktop128080rem
tabletWide108067.rem
tablet96060rem
tabletSmall76047.5rem
phablet64040rem
phoneWide56035rem
phone44027.5rem

Besides named values (default), there is also a shorthand version available. This can be configured using the ViewProvider.

const shorthandConfig = {
  xxl: 1440,
  xl: 1280,
  l: 1080,
  m: 880,
  s: 640,
  xs: 560,
  xxs: 420,
};

Overrule configuration

The default configuration can be overruled setting your own config as a prop on the ViewProvider. Inside your main App component set the provider so the mq() utility has access to your custom set of values.

import { ViewProvider } from '@greenberry/mq';

const customConfig = {
  xl: 1280,
  l: 1080,
  m: 780,
  s: 640,
  xs: 560,
};

return (
  <div className="app">
    <ViewProvider config={customConfig}>// your code</ViewProvider>
  </div>
);

If no config is provided the default will kick in, this will be named configuration (desktop, tablet etc.). You can set this to shorthand adding <ViewProvider shorthand></ViewProvider>. By default the type (min or max) will be set to max. You can manipulate this using <ViewProvider mobilefirst></ViewProvider>

Parsing values

The mq() helper will automatically parse values unless the value can be mapped on the given config. Meaning if you use named breakpoints (as shown above) the value will be parsed if the function recieves a number. So a custom configuration may also use strings, these strings will not be parsed. So instead of parsing 1280 (as a number) to 80rem, a value of 1280px will remain the same. Here is an example of using px units within your configuration.

const customConfig = {
  xl: '1280px',
  l: '1080px',
  m: '780px',
  s: '640px',
  xs: '560px',
};

Below is a simple example of the several options that can be passed as a first argument showing the parsing that will always resolve a string.

typevalueoutputparsedescription
string200px200pxNoWill use input
number16010remYesWill convert number to rem
stringtablet60remYesWill map on config and parse

Here is a simple example of the parsing happening in the background (based on a body font-size of 16px).

mq(200, 'min')`
    width: 100%;
`;

This will result in the following CSS (returned as a string).

@media screen and (min-width: 12.5rem) {
  width: 100%;
}

Passing a view and view type

The first argument of the mq() function will take either a number, a string or an array containing strings and/or numbers. Here are some examples of the output explaining what happens. It will try to automatically resolve the type either a min, max or a between. The type can be set as a second argument, this is not necassary when 2 values are passed in an array, this will automatically resolve a min and a max value (between).

usagevalueresult
mq(200) | number | @media screen and (min-width: 0rem) and (max-width: 12.5rem) {}
mq('700px') | string | @media screen and (min-width: 0rem) and (max-width: 700px) {}
mq('tablet') | string | @media screen and (min-width: 0rem) and (max-width: 60rem) {}
mq(200, 400)array@media screen and (min-width: 12.5rem) and (max-width: 25rem) {}
mq('760px', '1080px')array@media screen and (min-width: 760px) and (max-width: 1080px) {}
mq('phone, tablet')array@media screen and (min-width: 27.5rem) and (max-width: 60rem) {}
mq(200, tablet')array@media screen and (min-width: 12.5rem) and (max-width: 60rem) {}

Usually (for good practise) you wan to pass values in order, so first min and then max. However if you make a mistake mq([1080, 760]) the function will try to resolve the largest value as the max. If you need to change the direction of the breakpoint a second argument can be passed specifing the type.

For instance mq('tablet', 'min'), this will result in @media screen and (min-width: 60rem) {}.

Adding additional settings

If needed some extra settings can be passed optional to specify a specific media query. By default the screen setting will automatically be set using a default. This results in @media screen and, this can be overruled for instance to print. Using the settings can either be passed as the first or second argument. In case of an array the type will automatically (automagically) be set to between, meaning there is no need to be explicit about it. It prevents making a mistake.

mq('tablet', 'min', { ...settings })``;
mq(['phone', 'tablet'], { ...settings })``;

A list of settings is provided in the table below:

settingdefaultresultdescription
-screen@media screen andno setting required
{ screen: 'print' }-@media print andspecify key value pair
{ orientation: true }landscape(orientation: landscape)set to true will resolve "landscape"
{ orientation: 'portrait' }-(orientation: portrait)set string will resolve string
{ retina: true }-(-webkit-min-device-pixel-ratio: 2) and (min-resolution: 192dpi)will resolve ratio and dpi
{ mode: true }dark(prefers-color-scheme: dark)set to true will resolve "dark"
{ mode: 'light' }-(prefers-color-scheme: light)set string will resolve string

You can combine these settings, so for instance use dark mode on retina only. This could for instance be initiated as follows. You could either seperate the configuration (use it multiple times) or set it inline (see second example).

const settings = {
    mode: true
    retina: true,
}

mq(['phone', 'tablet'], { ...settings })``;

mq(['tablet', 'desktop'], { mode: 'light' })``;

Hook

Using the useView hook you are able to set values, nodes or other data based on a specific viewport. Behind the scenes the hook is using the same principles as the mq() utility in combintion with the window.matchMedia method. Doing so we are able to reuse the logic of the mq() util.

Instead of resolving passed styles the mq() util will only resolve the query if used as such: mq('tablet')('query') this will return a string only containing the exact query to be used within the matchMedia function. Meaning it would look something like this: (min-width: 0rem) and (max-width: 60rem. The hook can be used as follows:

const [config, view] = useView(null);

view.on(['phone', 'tablet'], { name: 'Sander' });

First we set a default value, in this case we set null. This will be the default rendering the component for the first time. Be aware that if you are trying to acces config.name this will be empty if the default is set to null. Using the useView hook will return two values, the first is the state. Meaning the initial null will be the default state for this instance.

The second value (in this example called view) will be resposible for adding an event listener to manipulate the state (called config in this example). This means that if the view is between phone and tablet the config will be changed to `{ name: 'Sander' }.

const [component, view] = useView((<div>Initial</div>));

view.on(['phone', 'tablet'], () => (<div>Example</div>)));

You could also use a callback function, this callback function makes it easy to add either logic or for instance a node/component to a specific view. In this case we call the first value to be returned component, this component will store the component that can be changed once the view is between phone and tablet.

Parsing values

Parsing values will be working the same as described in the mq() utility. To get a better understanding the following table shows some examples of the expected outcome.

usageoutput
view.on('tablet', {})Will set {} between 0rem and 60rem
view.on(200, {})Will set {} between 0rem and 12.5rem
view.on('tablet', 'desktop')Will set {} between 60rem and 80rem

The on method will manipulate the state once this specific view is true. If you want the hook to return either true or false this can be achieved not passing a second argument. This however limits the use of this hook since multiple on events will trigger the inView to be true. This can only be useful if one event is being use.

const [inView, view] = useView();

view.on(['phone', 'tablet']);