Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

toX, mapContextInX added and more #11

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This library wraps [`mdgriffith/elm-ui`](https://package.elm-lang.org/packages/m
A context is a global, *constant or mostly constant* object. It can be used to store those things that you will need *almost everywhere* in your `view` but don't change often, or at all.

Examples of things you could want to put in the context:
1. theme (dark/light/custom) - this is needed almost everwhere for colors, and styles, and changes very rarely;
1. theme (dark/light/custom) - this is needed almost everywhere for colors, and styles, and changes very rarely;
2. language - this is needed for every single label for localization, and changes rarely or never;
3. timezone - this is needed to display local times for the user, and mostly doesn't change;
4. responsive class (phone/tablet/desktop) - this doesn't usually change (unless the user dramatically resizes the window);
Expand Down Expand Up @@ -79,7 +79,7 @@ This has the advantage of keeping a nice API while making it (almost) impossible

Notice how `text` simply requires a context that includes a `language` field, so is very generic.

This tecnique can be adapted for image sources, title texts, and anything that needs localization.
This technique can be adapted for image sources, title texts, and anything that needs localization.

Strings with placeholders can be represented as `L10N (a -> b -> String)` and used by defining an `apply : L10N (a -> b) -> a -> L10N b`. Beware: different languages can have very different rules on plurals, genders, special cases, ...

Expand Down Expand Up @@ -107,13 +107,13 @@ fontColor theme =
someViewFunction =
el
[ Element.withAttribute
(\{theme} -> fontColor theme)
(\{ theme } -> fontColor theme)
Font.color
]
(text "Hello")
```

(`\{theme} -> fontColor theme` can be replaced by `.theme >> fontColor`, depending on taste).
(`\{ theme } -> fontColor theme` can be replaced by `.theme >> fontColor`, depending on taste.)

This also has the advantage that you can "force" a particular theme in places that need it, like the theme picker, by just doing `fontColor Light`.

Expand Down
219 changes: 207 additions & 12 deletions src/Element/WithContext.elm
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Element.WithContext exposing
( with, withAttribute, withDecoration, layout, layoutWith, element, attribute, attr
( with, withAttribute, withDecoration, layout, layoutWith, element, attribute, attr, toElement, toAttribute, toAttr, mapContextInElement, mapContextInAttr
, Element, none, text, el
, row, wrappedRow, column
, paragraph, textColumn
Expand Down Expand Up @@ -31,7 +31,10 @@ module Element.WithContext exposing

# `elm-ui-with-context` specific functions

@docs with, withAttribute, withDecoration, layout, layoutWith, element, attribute, attr
@docs with, withAttribute, withDecoration, layout, layoutWith, element, attribute, attr, toElement, toAttribute, toAttr, mapContextInElement, mapContextInAttr

Apart from some other functions in [`Element.WithContext.Input`](Element-WithContext-Input),
this is everything `elm-ui-with-context` adds to `elm-ui`.


# Basic Elements
Expand Down Expand Up @@ -208,17 +211,17 @@ You'll also need to retrieve the initial window size. You can either use [`Brows
@docs modular


## Mapping
# Mapping

@docs map, mapAttribute


## Compatibility
# Compatibility

@docs html, htmlAttribute


## Advanced
# Advanced

Sometimes it's more convenient to just access the whole context while building your view. This functions allow you do just that.

Expand All @@ -227,7 +230,7 @@ Sometimes it's more convenient to just access the whole context while building y
-}

import Element
import Element.WithContext.Internal as Internal exposing (Attr(..), Attribute, Element(..), attr, attribute, attributes, run, runAttr, wrapAttrs, wrapContainer)
import Element.WithContext.Internal as Internal exposing (Attr(..), Attribute, Element(..), attributes, run, runAttr, wrapAttrs, wrapContainer)
import Html exposing (Html)


Expand Down Expand Up @@ -349,6 +352,60 @@ htmlAttribute child =


{-| Embed an element from the original elm-ui library. This is useful for interop with existing code, like `lemol/ant-design-icons-elm-ui`.

`element` can also be used in combination with `with`
to supply arguments to an elm-ui element from the context.

For example

module SomePackage exposing (Context, view)

import Element exposing (Element)

view :
{ backgroundColor : Element.Color
, foregroundColor : Element.Color
}
-> Element msg

in your code

module YourCode exposing (main)

import Element.WithContext exposing (Element)

type alias Context =
{ theme : Theme }

type Theme
= Black
| White

themeToColors :
Theme
-> { background : Element.WithContext.Color
, foreground : Element.WithContext.Color
}

view : Element Context msg
view =
Element.WithContext.column
[]
[ ...
, Element.WithContext.with
(\{ theme } ->
let
themeColors =
theme |> themeToColors
in
SomePackage.view
{ backgroundColor = themeColors.background
, foregroundColor = themeColors.foreground
}
|> Element.WithContext.element
)
]

-}
element : Element.Element msg -> Element context msg
element elem =
Expand All @@ -369,6 +426,144 @@ attr elem =
Attribute <| \_ -> elem


{-| Construct an element from the original elm-ui supplying a complete context.

This can be used as the final step before
embedding sub-elements that use elm-ui-with-context
in a bigger elm-ui element
without elm-ui-with-context becoming "infectious"
and forcing higher-level elements to adopt context.

For example

module SomePackage exposing (Context, view)

import Element.WithContext exposing (Element)

type alias Context =
{ backgroundColor : Element.WithContext.Color
, foregroundColor : Element.WithContext.Color
}

view : Element Context msg

in your code

module YourCode exposing (main)

import Element exposing (Element)
import Element.WithContext

view : Element msg
view =
Element.WithContext.column
[]
[ ...
, SomePackage.view
-- app currently only supports a black theme
-- but `SomePackage`'s theme must be configured ↓
|> Element.WithContext.toElement
{ backgroundColor = Element.WithContext.rgb 0 0 0
, foregroundColor = Element.WithContext.rgb 1 1 1
}
]

-}
toElement : context -> Element context msg -> Element.Element msg
toElement context (Element f) =
f context


{-| Construct an attribute for the original elm-ui supplying a complete context.
[`toElement`](#toElement) documents use-cases.
-}
toAttribute : context -> Attribute context msg -> Element.Attribute msg
toAttribute context (Attribute f) =
f context


{-| Construct an attribute from the original elm-ui by supplying a complete context.
[`toElement`](#toElement) documents use-cases.
-}
toAttr : context -> Attr context decorative msg -> Element.Attr decorative msg
toAttr context (Attribute f) =
f context


{-| Change how the context looks for a given element.

This is used to embed elm-ui-with-context
from another origin (like a package)
with another context type.

This is quite similar to [`map`](#map)
but instead of transforming the inner msg type to match the outer type,
it transforms the outer context type to match the inner type.

For example

module SomePackage exposing (Context, view)

import Element.WithContext exposing (Element)

type alias Context =
{ backgroundColor : Element.WithContext.Color
, foregroundColor : Element.WithContext.Color
}

view : Element Context msg

in your code

module YourCode exposing (main)

import Element.WithContext exposing (Element)

type alias Context =
{ theme : Theme }

type Theme
= Black
| White

themeToColors :
Theme
-> { background : Element.WithContext.Color
, foreground : Element.WithContext.Color
}

view : Element Context msg
view =
Element.WithContext.column
[]
[ ...
, Element.WithContext.mapContextInElement
(\{ theme } ->
let
themeColors =
theme |> themeToColors
in
{ backgroundColor = themeColors.background
, foregroundColor = themeColors.foreground
}
)
SomePackage.view
]

-}
mapContextInElement : (outerContext -> innerContext) -> Element innerContext msg -> Element outerContext msg
mapContextInElement outerToInnerContext (Element f) =
Element (outerToInnerContext >> f)


{-| Change how the context looks for a given attribute.
[`mapContextInElement`](#mapContextInElement) documents use-cases.
-}
mapContextInAttr : (outerContext -> innerContext) -> Attr innerContext decorative msg -> Attr outerContext decorative msg
mapContextInAttr outerToInnerContext (Attribute f) =
Attribute (outerToInnerContext >> f)


{-| -}
map : (msg -> msg1) -> Element context msg -> Element context msg1
map f (Element g) =
Expand Down Expand Up @@ -497,14 +692,14 @@ fillPortion =
{-| This is your top level node where you can turn `Element` into `Html`.
-}
layout : context -> List (Attribute context msg) -> Element context msg -> Html msg
layout context attrs (Element f) =
Element.layout (attributes context attrs) (f context)
layout context attrs elem =
Element.layout (attributes context attrs) (elem |> toElement context)


{-| -}
layoutWith : context -> { options : List Option } -> List (Attribute context msg) -> Element context msg -> Html msg
layoutWith context options attrs (Element f) =
Element.layoutWith options (attributes context attrs) (f context)
layoutWith context options attrs elem =
Element.layoutWith options (attributes context attrs) (elem |> toElement context)


{-| -}
Expand Down Expand Up @@ -917,8 +1112,8 @@ downloadAs =


createNearby : (Element.Element msg -> Element.Attribute msg) -> Element context msg -> Attribute context msg
createNearby toAttr (Element f) =
Attribute (f >> toAttr)
createNearby elementToAttr (Element f) =
Attribute (f >> elementToAttr)


{-| -}
Expand Down
Loading