Open Mohammer5 opened 5 years ago
@cooper-joe mentioned a strategy that sounds good to me:
I think media queries/breakpoints work for the layout components, but the components inside of them should behave fluidly (e.g. no breakpoints, all widths fixed or percentage based). This, of course, won't work for all components, but from what I've seen so far in DHIS2 I think this makes the most sense.
Avoiding media queries in ui-core makes sense; it keeps ui-core simple and opinionated, it reduces the API surface area, and allows us to defer layout decisions to the applications, rather than carpet bomb everyone with what ui-core has decided (a concern first raised by @ismay).
Avoiding media queries in ui-core makes sense; it keeps ui-core simple and opinionated
Agree, ui-core
should be free of media queries, which means that components like the table component will be split into two separate components
This, of course, won't work for all components
So how should we handle the cases where this won't work?
ui-widgets
and/or en-(dis-?)abling automatic adjustments?ui-widgets
) could accept values for when to apply a certain layout (I have no idea if that works with styled-jsx
though)In my experience basing behavior on layout areas for widgets works quite well on a larger scale with the downside of not changing the behavior when needing customization.
This leads me to the next question:
Currently we have ui-core
and ui-widgets
.
How do we define where the different parts of the design system go, should it be defined by type (atoms, molecules, etc.) or on what it does (dumb -> ui-core, not-dumb -> ui-widgets)
It seems to me that in order for people to use our components, and not have to write custom ones too much (which will cause divergence in how our apps look and work) we have to find the right amount of abstraction.
I think the only place where media queries in a library component make sense to me, is if we're 100% sure of where the component will be used and what will be rendered in it. So say the headerbar. If I'm correct, we are basically in control of what gets rendered there, plus it should always be used full-width at the top. I think that using media queries there is safe.
With the table component we don't know that, so allowing users (devs) of it to choose for themselves when to render stacked vs normal is a nice compromise in my opinion. It's not the best solution performance-wise for some cases, but usability trumps performance in my opinion. That'll allow it to be used in most cases I can think of.
I think the layout components abstract things at a level that is a bit too high for me. Layouts are fairly trivial to make with grid and flexbox. I also suspect that there will be exceptions where we'll have to either complicate the layout component, or people will write their own. Maintenance-wise, handling responsive behaviour with the layout component also seems a lot more complicated to me than handling it in an app. As a dev I think I'd prefer the explicit approach with small, composable elements that are made responsive in the app, over a slightly magic layout component handling the logic for making things responsive. Given that the cost of creating a layout is not that high, but that the cost of maintaining a component like this seems fairly high to me, I'd vote against this approach.
I agree with you guys, that we shouldn't enforce a certain responsive behavior in our component libraries.
Then I'd like to talk about what options we have left:
We can create a library which is for the sole purpose of being used by us / in a dhis2 design-system layout context, similar to ui-widgets
and leverages on ui-core
.
I could imagine that components will look like this:
const useMatchMedia = condition => {
const mq = useMemo(() => window.matchMedia(condition)}, [condition])
const [ matches, setMatches ] = useState(mq.matches)
const onChange = useCallback(curMq => {
if (curMq.matches && !matches) { setMatches(true) }
else if (!curMq.matches && matches) { setMatches(false) }
}, [mq])
useEffect(() => {
mq.addListener(onChange)
return () => mq.removeListener(onChange)
}, [mq, onChange])
return matches
}
const Table = props => {
const isSmall = useMatchMedia('(max-width: 400px)')
if (isSmall) return <TableStacked { ...props } />
return <TableStatic { ...props } />
}
We could create common breakpoints that we'd like to use, but expose the useMatchMedia
hook as well for customization in apps
Of course apps could either implement the above or use css to hide/show the correct component (which will have the overhead of rendering all not-displayed components in the vdom)
I really want our apps to behave similar, both in terms of UX and UI. So I'd tend towards the library (which could include the layout components that I proposed as well). The advantage is that we don't mix JS and CSS in terms of responsive styles if we don't want to render all components in the vdom, so it's a clear SoC, it'd be centralized and identical in our apps.
What do you guys think? Do you have another approach that might be easier to use?
Just for reference:
Tested & works:
const useMatchMedia = condition => {
const mq = useMemo(() => window.matchMedia(condition), [condition])
const [ matches, setMatches ] = useState(mq.matches)
const onChange = useCallback(curMq => {
if (curMq.matches && !matches) { setMatches(true) }
else if (!curMq.matches && matches) { setMatches(false) }
}, [mq, matches, setMatches])
useEffect(() => {
mq.addListener(onChange)
return () => mq.removeListener(onChange)
}, [mq, onChange])
return matches
}
As a small utility function that seems usable to me. I can imagine that part of this is also difficult to plan in advance. Maybe we should also just keep an eye out for emerging patterns, and centralize when and where necessary. Trying to plan it too much in advance might also be difficult.
We could create common breakpoints that we'd like to use
I would prefer not to do that. Breakpoints should differ based on the content, so to me it's not that useful to set them in advance.
Maybe we should have one or two apps where we tackle this, and then based on that discuss the patterns that we'd like to abstract, etc.
I would prefer not to do that. Breakpoints should differ based on the content, so to me it's not that useful to set them in advance.
That's true, but I think there are certain things that we already know:
ui-core
)HeaderBar
)Then, you can add breakpoints depending on the layout and area an element is placed inside. Which means: If you need different breakpoints because the content is different, you probably also need a different layout (which could be the same grid with different proportions)
But I think it's alright to try to see emerging patterns. The maintenance app is the newest one we currently have, so we could start creating responsive components there. And if it turns out to work nicely and could be reused across apps, extract the responsive stuff to a library
Yeah, to me the headerbar also seems like a good candidate for including breakpoints. For the layout components I'm not sure personally: https://github.com/dhis2/notes/issues/59#issuecomment-519022367.
But I think it's alright to try to see emerging patterns. The maintenance app is the newest one we currently have, so we could start creating responsive components there. And if it turns out to work nicely and could be reused across apps, extract the responsive stuff to a library
Yeah to me that seems the most practical. Otherwise it feels a bit like an academic exercise in trying to predict what we'll need.
Should be interesting to @dhis2/front-end Would like to get other opinions on this as well
I'm not 100% sure about this, but I think we have some formally supported measure of minimum screen width. I believe >= 1024px is supported.
I remember reading about this a long time ago, but I can't really find any document that actually states this. At one point in time we we working on a Google doc, and someone added some text about the minimum width, which was removed later on because it didn't really belong in that document. So all I have is a link to a slack thread which includes a link to the Google docs comment about the deleted text. I don't think it would hold up in court....
I thought I'd mention this anyway, because it might be very relevant for this discussion.
I'm not 100% sure about this, but I think we have some formally supported measure of minimum screen width.
Yes we currently don't support smaller screens, but looking at the development of devices, there's a trend towards small, mobile devices is Asia (and I assume it's similar in Africa) and I think we should think a bit more progressively here
I have silently added breakpoints to my table proposal in https://github.com/dhis2/ui-core/pull/304, which has triggered a discussion about if/how we should handle responsive styles.
So I think it's time we start talking about what we want to do properly (let's see if our component & design system is mature enough so we can come to a conclusion here)
Requirements
There are some requirements that we'll have to take care of:
Different layouts of components can be needed regardless of viewport dimensions
This is most likely the case for components rendered in resizable containers, here's what @cooper-joe said:
This can't be solved with breakpoints!
Different component-layouts based on content/page-layout
Components will most likely render differently when placed in a sidebar instead of the main content area
This can't be solved with breakpoints!
Component-layouts that change for different viewport sizes
Many component layout won't make much sense on phones and tablets (desktop view of the org unit tree or group editor for example). These changes can't be determined by the page's layout area they're placed in
This can be solved with breakpoints only!
Custom breakpoints defined by apps
Some apps will have content that's unique for those apps.
This will conflict with hard-coded breakpoints in out component libraries!
Proposal
So in order to get a grip on this problem, I'll lay out my preferred solution to this. It's won't cover all possible cases, so obviously there will be some limitations.
Final goal
What I'd like to end up with is
ui-core
, likeui-widgets
. Once we're there, apps won't useui-core
anymore except for rare cases.Custom layouts / responsive styles
I'd like to reduce custom styles to a minimum for some reasons and base most of the behavior on the page's layout:
ui-core
, so if there's really a need for something custom it still can be doneDrawbacks
If we define behavior in our component libraries, we'll end up with some fixed (pixel based) values eventually, so we will have trouble with custom breakpoints, so this is something we'd need to avoid. With a proper/mature design system this should be do-able and even preferable
If we don't define behavior in our component libraries, we'll end up with different responsive styles in our apps and we'll have to determine component behavior with javascript because we can't use breakpoints in our component libraries.