Skip to content

Instantly share code, notes, and snippets.

@mdgriffith
Created October 20, 2021 13:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdgriffith/c2abad030640dd20e0aa3bc3519211e4 to your computer and use it in GitHub Desktop.
Save mdgriffith/c2abad030640dd20e0aa3bc3519211e4 to your computer and use it in GitHub Desktop.
{- A sketch for a potential responsive API for elm-ui
First, the API. Following will be some example code!
-}
-- defining a set of global window-width breakpoints
breakpoints : label -> List (Breakpoint label) -> Breakpoints label
breakAt : Int -> label -> Breakpoint label
{-| Define a layout that is a row when the page in in the specified breakpoints.
Otherwise, it'll render as a `column`.
-}
rowWhen : Breakpoints label -> List label -> List (Attribute msg) -> List (Element msg)
-- an exact value
value : Int -> Value
-- a value that will scale from one value to another depending
-- on it's position in the current window-width range
-- See fluid typography example below
fluid : Int -> Int -> Value
-- Properties that can vary based on the given window size
fontSize : Breakpoints label -> (label -> Value) -> Attribute msg
padding : Breakpoints label -> (label -> Value) -> Attribute msg
height : Breakpoints label -> (label -> Value) -> Attribute msg
heightMin : Breakpoints label -> (label -> Value) -> Attribute msg
heightMax : Breakpoints label -> (label -> Value) -> Attribute msg
width : Breakpoints label -> (label -> Value) -> Attribute msg
widthMin : Breakpoints label -> (label -> Value) -> Attribute msg
widthMax : Breakpoints label -> (label -> Value) -> Attribute msg
{- EXAMPLES -}
{-|
Defined statically. Needs to be provided to Ui.layout so it can render media queries.
Also needs to be provided to all elements that need responsiveness. But since it's top-level, no need to thread it through every view fn.
-}
breakpoints : Ui.Responsive.Breakpoints Breakpoints
breakpoints =
Ui.Responsive.breakpoints ExtraLarge
[ Ui.Responsive.breakAt 2400 Large
, Ui.Responsive.breakAt 1400 Medium
, Ui.Responsive.breakAt 800 Small
]
el
[ Ui.Responsive.visible breakpoints
[ Medium ]
]
(text "only visible at medium")
el
[ Ui.Responsive.fontSize breakpoints
(\breakpoint ->
case breakpoint of
ExtraLarge ->
Ui.Responsive.value 35
Large ->
Ui.Responsive.value 35
Medium ->
-- scales from 16 to 35 when the window is in the `Medium` range
Ui.Responsive.fluid 16 35
Small ->
Ui.Responsive.value 16
)
]
(text "Fluid typography")
-- padding
el
[ Ui.Responsive.padding breakpoints
(\breakpoint ->
case breakpoint of
ExtraLarge ->
Ui.Responsive.value 35
Large ->
Ui.Responsive.value 35
Medium ->
-- scales from 16 to 35 when the window is in the `Medium` range
Ui.Responsive.fluid 16 35
Small ->
Ui.Responsive.value 16
)
]
(text "Responsive padding!")
@gampleman
Copy link

I have a couple of comments:

  1. Is it necessary to pass it to every attribute, if the layout already knows about it? Could you not use some sort of “context” technique to pass the breakpoints down? The current API provides the possibility of providing different breakpoints to the layout and different ones to the elements… Is that desirable? Would it work?

  2. This relies very much on a global understanding of breakpoints, which is just one conceptualisation of responsive design. I personally am not a huge fan, because to me it feels like it isn't composable - how would I publish a library that provides responsive Elements? So for that I think having responsiveness built at the parent-child boundary makes more sense - i.e. if a parent wants an element to take > 300px of vertical space, I can render as a row, otherwise I can render as a column can compose in a way where it's independent of what else is participating in the layout.

@mdgriffith
Copy link
Author

  1. Yeah, it is necessary unless you want to add a breakpoint type variable to every Element and Attribute, or ditch the custom type there.

  2. Ultimately, yeah, that's an approach I'd like to move towards. If I'm following you, In CSS land, these are called container queries and a lot of people want them! But they don't exist in CSS yet.

Alternatively this would be much easier if elm-ui did it's own layout calculations, which is something I'd love to explore! However it'd be quite involved to introduce.

@gampleman
Copy link

Yeah, it is necessary unless you want to add a breakpoint type variable to every Element and Attribute

Wouldn't that be preferable to having it passed into every responsive value?

or ditch the custom type there

I suppose for the case of composition, having it as a custom type could be tricky. There could be some sort of mapBreakpoint, but how that would work in practice...

On the other hand providing some sort of fixed vocabulary seems really tricky.

Alternatively this would be much easier if elm-ui did it's own layout calculations, which is something I'd love to explore! However it'd be quite involved to introduce.

At work we have a custom UI system that's kind of inspired by elm-ui and it does this (in our case only for widths since in practice branching on height isn't very common and it's harder to get right). We basically get a subscription for the window height, then thread the height throughout the layout tree, computing paddings, widths and spacings as appropriate. The width is always a Maybe, since it's impractical to measure text, so if the width depends on text content, we can't provide it. In practice this is actually rare (although our rows and columns default to the equivalent of width fill, so perhaps that makes it easier); furthermore it's even rarer for a particular element to change between Just x and Nothing - typically the width is either knowable or not.

Also the calculations are accurate with some error, which seems to be due to the fact that different OSs reserve space for scrollbars in different ways, which ocassionally eats up a few pixels of space. I haven't yet figured out a way to reliably take this into account, but I suspect that for most responsive use cases a ±5px accuracy wouldn't be too much of a problem.

We actually don't use this for responsive design at the moment (we don't really support mobile browsers at all... ¯_(ツ)_/¯ ), but use it for composable SVG drawing that needs to be responsive. And it works really well.

@mdgriffith
Copy link
Author

Wouldn't that be preferable to having it passed into every responsive value?

I personally don't think so - the additional cognitive complexity for type signatures would be pretty big because this type is everywhere and would need to be explained to anyone starting to use the library. Easy for people who are very familiar with Elm, but I want elm-ui to be a gateway project, so I'm maybe more wary of cognitive overhead than usual.

I don't think mapping breakpoints would actually be useful because you're never reading them beyond essentially doing a ==

Also not many elements actually need to be explicitly responsive like this. It's usually a handful compared to the vast majority of times when width fill is plenty of responsiveness.

Also, ideally you'd have only a few prepackaged responsive properties like this. e.g. Theme.padding.responsive.medium.

At work we have a custom UI system that's kind of inspired by elm-ui and it does this

Cool! Yeah, measuring text is the main blocker. I have a few ideas on how to do that-happy to share once I'm focusing on that directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment