kettanaito / atomic-layout

Build declarative, responsive layouts in React using CSS Grid.
https://redd.gitbook.io/atomic-layout
MIT License
1.13k stars 33 forks source link

How can I define grid template area for portrait mode? [Help Required] #240

Open noushad-pp opened 5 years ago

noushad-pp commented 5 years ago

I want to define a template-area for a component for portrait mode. Creating an issue as I dont know where to ask for help. I can't understand how to define the template area for this kind of breakpoint from the documentation. I tried both areasPortrait and areasOrientationPortrait Is it possible this way of do I have to manage it programmatically? eg:

const areas = `
media text
`;

const areasPortrait=`
media
text
`;

<Composition areas={areas} areasPortrait={areasPortrait} areasOrientationPortrait={areasPortrait}>
  {({ Media, Text }) => (
    <>
      <Media />
      <Text />
    </>
  )}
<Composition/>
kettanaito commented 5 years ago

Hi, @noushad-pp. That's the right place to ask.

By default, responsive props in Atomic Layout use Bootstrap 4 breakpoints (xs, sm, md, lg, xl). As you may already know, those are described using media queries targeting a device's width. This means that by default there is no behavior to target a device's orientation.

Basically, out of the box the library can handle abc, abcSm, abcMd, abcLg and abcXl responsive props suffixes in accordance to Bootstrap breakpoints. What happens when you write abcPortrait is that the library doesn't know what is Portrait and what breakpoint it represents. You need to explicitly describe the Portrait breakpoint.

In order to assign responsive props based on device's orientation, you would have to create such breakpoint manually using Layout.configure() and its breakpoints property:

// src/index.js
import Layout, { defaultOptions } from 'atomic-layout'

// Make sure to call this once, ideally on the root level of your app.
// This can be called before "ReactDOM.render()", for example.
Layout.configure({
  breakpoints: {
    // Include the default breakpoints to still apply
    ...defaultOptions.breakpoints,

    // A custom breakpoint name.
    // Naming it "portrait" to be intuitive, but can be about any string.
    portrait: {
      // Media query definition. In our case just device's orientation.
      orientation: 'portrait'
    }
  }
})

See the running Codesandbox example.

By doing so, you create your custom responsive props suffix called portrait. Calling Layout.configure() makes all Atomic Layout's components aware of your suffix automatically.

Whenever you suffix a prop name with that suffix a prop value will be applied only when the media query is met (in our case when orientation is portrait):

// src/MyComponent.jsx
import React from 'react'
import { Composition } from 'atomic-layout'

const MyComponent = () => (
  <Composition areas="left right" areasPortrait="left center right">
    {(Areas) => (
      <React.Fragment>
        <Areas.Left>I am rendered always.</Areas.Left>
        <Areas.Center>I should be rendered only on portrait orientation.</Areas.Center>
        <Areas.Right>I am rendered always as well.</Areas.Right>
      </React.Fragment>
    )}
  </Composition>
)

I hope this explanation helps. Let me know if you still have any questions.


Please update to atomic-layout@0.9.9 to be able to import defaultOptions from the package. This would dramatically simplify the declaration of custom breakpoints. Thanks.

noushad-pp commented 5 years ago

Awesome. Thanks for your time and effort into this detailed explanation. This was the solution that I used before: 😓

const isPortrait = window.screen.orientation.type.includes('portrait') || window.innerHeight > window.innerWidth;
const areasLandscape = `
  media text
  `;
  const areasPortrait = `
  media
  text
  `;
const areas = isPortrait ? areasPortrait : areasLandscape;
noushad-pp commented 5 years ago

Defining portrait breakpoint as mentioned above is causing areas not to render unless I have to specify landscape as a breakpoint and render areasLandscape instead of areas. Even the code sandbox example is behaving the same way.

This doesnt work in landscape mode

const MyComponent = () => (
  <Composition areas="left right" areasPortrait="left center right">
    {(Areas) => (
      <React.Fragment>
        <Areas.Left>I am rendered always.</Areas.Left>
        <Areas.Center>I should be rendered only on portrait orientation.</Areas.Center>
        <Areas.Right>I am rendered always as well.</Areas.Right>
      </React.Fragment>
    )}
  </Composition>
)

This works

import Layout, { defaultOptions } from 'atomic-layout'

Layout.configure({
  breakpoints: {
    ...defaultOptions.breakpoints,
    portrait: {
      orientation: 'portrait'
    },
   landscape: {
      orientation: 'landscape'
    }
  }
})

const MyComponent = () => (
  <Composition areasLandscape="left right" areasPortrait="left center right">
    {(Areas) => (
      <React.Fragment>
        <Areas.Left>I am rendered always.</Areas.Left>
        <Areas.Center>I should be rendered only on portrait orientation.</Areas.Center>
        <Areas.Right>I am rendered always as well.</Areas.Right>
      </React.Fragment>
    )}
  </Composition>
)
ruhaise commented 5 years ago

@noushad-pp if you dont specify the suffix the default breakpoint name is used which is xs explained in this section https://redd.gitbook.io/atomic-layout/fundamentals/breakpoints#default-breakpoint-name

kettanaito commented 5 years ago

Yes, thanks for mentioning that, @ruhaise.

@noushad-pp you are right, it appears to be an issue in the way Atomic Layout opens breakpoints. Let me elaborate below.

As Ruhaise has mentioned earlier, areas (without a suffix) would use the default breakpoint name - xs. So, if we extend the props assignment it would look as follows:

areasXs="left right"
areasPortrait="left center right"

As the next step, Atomic Layout determines when to render each of the areas. To do so, it first analyzes if two sibling responsive props declarations operate on the same breakpoint kind. For example, areasMd and areasLg operate on the same breakpoint kind, as their breakpoint is based on the viewport's dimensions. areasXs and areasPortrait, however, have different breakpoint kind:

Since the breakpoint kind between two declarations is different, the library decides to wrap an area component (i.e. left) in a Placeholder. That placeholder provides conditional rendering of the area only when the media query predicate is met.

Now to the issue. It looks that the areasXs declaration is not opened properly:

Screenshot 2019-10-29 at 10 34 34

Expected behavior would be the Left area rendered unconditionally, not having the Placeholder wrapper. In other words, areas left and right must be rendered always. Since internally only the last breakpoint in a breakpoints set can be opened, areas doesn't get opened being the first in a set (followed by areasPortrait). This results into its Placeholder expecting maxWidth: 576 to get the left and right areas rendered. That's why you don't see anything rendered at some point. When such viewport width kick in, it already satisfies the portrait orientation as well, so you can't tell the two.

I'd say we need to define how breakpoints should behave in such case.

kettanaito commented 5 years ago

Having though about this problem, I'm yet not sure what would be the expected behavior.

To illustrate, let's replace areas with areasLg.

<Composition areasLg="left" />

In isolation that means "render the left area only on large devices and up". So it won't get rendered on xs, sm, and md screen sizes. Now let's add areasPortrait to the mix:

<Composition areasLg="left" areasPortrait="left right" />

areasPortrait imply that left and right areas will get rendered only on portrait orientation of the device. Which, in most cases, would be a smaller screen size than lg breakpoint describes. This makes two responsive props declaring contradictory behavior toward the same area.

Current solution

@noushad-pp since this behavior needs to be carefully designed, and I don't wish to block your development, I can suggest another way to tackle portrait orientation. Instead of declaring the areasPortrait prop, use the useResponsiveValue hook:

import { useResponsiveValue, Composition } from 'atomic-layout'

const MyComponent = () => {
  const shouldRenderWidget = useResponsiveValue({
    portrait: true // whenever your custom "portrait" breakpoint is met, return the value (true)
  }, false)

  return (
    <Composition areas="left right">
      {Areas => (
        <React.Fragment>
          <Areas.Left>I should render always</Areas.Left>
          <Areas.Right>I should render always</Areas.Right>
          {shouldRenderWidget && <div>Visible on portrait orientation</div>}
        </React.Fragment>
      )}
    </Composition>
  )
}

That means to leave out the portrait from the areas declaration and treat it as an extra scenario during rendering. I understand this is not ideal, as you may not be able to target the conditional widget with CSS Grid properties in its fullest extent.

Also, please, feel free to voice what your expectations would be, so we could shape the behavior together. Thanks!