more docs, more fun– six docs!

This commit is contained in:
Ryan Haskell-Glatz 2019-12-11 16:44:19 -06:00
parent 114c286e08
commit 356ca9796b
13 changed files with 412 additions and 20 deletions

View File

@ -8,7 +8,7 @@
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.2",
"elm/core": "1.0.4",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
@ -28,8 +28,5 @@
"test-dependencies": {
"direct": {},
"indirect": {}
},
"elm-spa": {
"ui": "Element"
}
}

View File

@ -371,9 +371,9 @@
}
},
"elm-spa": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/elm-spa/-/elm-spa-2.0.4.tgz",
"integrity": "sha512-j9CkEEy5iYSuiy6/mgSWwI0eXIFk1ltRYgpKyrjZ1hvuNur9j3wKylXniFiODgWr2Va//6tzeQRjHnUJ0fCfKA==",
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/elm-spa/-/elm-spa-2.0.10.tgz",
"integrity": "sha512-UtOTP4NDnkSPaf3V5I2h9PGCtjF2/9NIrYK8ctdzE24knR1L8QImA723t57RYTodQAs/meDME3sSxNoCNJ10hA==",
"dev": true
},
"emoji-regex": {

View File

@ -19,6 +19,6 @@
"chokidar-cli": "2.1.0",
"elm": "0.19.1-3",
"elm-live": "4.0.1",
"elm-spa":"2.0.9"
"elm-spa": "2.0.10"
}
}

View File

@ -0,0 +1,282 @@
---
{ "title": "components"
, "description": "reusing ui in your app."
}
---
<iframe></iframe>
### starting simple
Not every component in Elm needs to have it's own `Model`, `Msg`, `init`,
`update`, `view` defined. In fact, a lot of things can just be a function!
Let's look at an examples of using creating a reusable button in Elm:
```elm
module Pages.Top exposing ( Model, Msg, page )
import Element
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
type Msg = SignIn | SignOut
view : Element Msg
view =
Input.button
[ Font.size 14
, Font.semiBold
, Border.solid
, Border.width 2
, Border.rounded 4
, Element.paddingXY 24 8
, Font.color colors.coral
, Border.color colors.coral
, Background.color colors.white
, Element.pointer
]
{ label = Element.text "SignIn"
, onPress = Just SignIn
}
```
Here, our homepage (at `src/Pages/Top.elm`) defines a __bunch__ of button styles.
If we wanted to reuse those styles, we can make a function like this:
```elm
module Pages.Top exposing ( Model, Msg, page )
import Element
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
viewButton : { label : String, onPress : msg } -> Element msg
viewButton options =
Input.button
[ Font.size 14
, Font.semiBold
, Border.solid
, Border.width 2
, Border.rounded 4
, Element.paddingXY 24 8
, Font.color colors.coral
, Border.color colors.coral
, Background.color colors.white
, Element.pointer
]
{ label = Element.text options.label
, onPress = Just options.onPress
}
type Msg = SignIn | SignOut
view : Element Msg
view =
Element.column []
[ viewButton
{ label = "Sign in"
, onPress = SignIn
}
, viewButton
{ label = "Sign out"
, onPress = SignOut
}
]
```
By creating that `viewButton` function, we prevent the need to duplicate our code,
and reuse those styles again for the "Sign out" button!
### sharing between pages
So we love our button so much that we want to reuse it on the "Share" page
(over at `src/Pages/Share.elm`). The only problem is that all the code we wrote
is in the `src/Pages/Top.elm` file.
__So what should we do?__
Let's create a module called `Ui.elm` that has our `viewButton` function in it:
```elm
module Ui exposing ( viewButton )
import Element
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
viewButton : { label : String, onPress : msg } -> Element msg
viewButton options =
Input.button
[ Font.size 14
, Font.semiBold
, Border.solid
, Border.width 2
, Border.rounded 4
, Element.paddingXY 24 8
, Font.color colors.coral
, Border.color colors.coral
, Background.color colors.white
, Element.pointer
]
{ label = Element.text options.label
, onPress = Just options.onPress
}
```
And update `src/Pages/Top.elm`:
```elm
module Pages.Top exposing ( Model, Msg, page )
import Element
import Ui
type Msg = SignIn | SignOut
view : Element Msg
view =
Element.column []
[ Ui.viewButton
{ label = "Sign in"
, onPress = SignIn
}
, Ui.viewButton
{ label = "Sign out"
, onPress = SignOut
}
]
```
That makes our page a lot shorter, and using `Ui.viewButton` let's readers know
where that function is coming from!
We can now reuse it on `src/Pages/Share.elm` easily!
```elm
module Pages.Share exposing ( Model, Msg, page )
import Element
import Ui
type Msg = ShareOnTwitter
view : Element Msg
view =
Ui.viewButton
{ label = "Share"
, onPress = ShareOnTwitter
}
```
### when to create a new module
In Elm, we _usually_ make a module around data structures. The creator of the language,
Evan Czaplicki, has a [really great talk](https://www.youtube.com/watch?v=XpDsk374LDE)
about that idea here.
For this site, I made the navbar into it's own file (at `src/Components/Navbar.elm`),
but I could have just as easily made a function in `src/Ui.elm` that exposed `viewNavbar`.
Directly mapping ideas from JS frameworks like React may lead you down a frustrating path.
What makes sense for scaling a JavaScript app might not translate in Elm!
If you find yourself creating components like this:
```elm
module Components.Example exposing
( Model
, Msg
, init
, update
, view
)
-- code
```
You'll end up creating a verbosity problem for components (the same one
that __elm-spa__ was designed to fix for pages!)
```elm
module Pages.Example exposing (Model, Msg, page)
import Components.Foo as Foo
import Components.Bar as Bar
import Components.Baz as Baz
type alias Model =
{ foo : Foo.Model
, bar : Bar.Model
, baz : Baz.Model
}
type Msg
= FromFoo Foo.Msg
| FromBar Bar.Msg
| FromBaz Baz.Msg
view : Model -> Element Msg
view model =
Element.column []
[ Element.map FromFoo (Foo.view model.foo)
, Element.map FromBar (Bar.view model.bar)
, Element.map FromBaz (Baz.view model.baz)
]
update : Msg -> Model -> Model
update msg model =
case msg of
FromFoo msg_ ->
{ model | foo = Foo.update msg_ model.foo }
FromBar msg_ ->
{ model | bar = Bar.update msg_ model.bar }
FromBaz msg_ ->
{ model | baz = Baz.update msg_ model.baz }
```
There's nothing _wrong_ with the code in the example above! Maybe `Foo` needs to
be complex!
But start with the simplest strategy first. Maybe `Bar` and `Baz` don't need to
follow that pattern:
```elm
module Pages.Example exposing (Model, Msg, page)
import Components.Foo as Foo
import Ui
type alias Model =
{ user : Maybe String
, foo : Foo.Model
}
type Msg
= FromFoo Foo.Msg
| SignOut
view : Model -> Element Msg
view model =
Element.column []
[ Element.map FromFoo (Foo.view model.foo)
, Ui.viewBar model.username
, Ui.viewBaz { onClick = SignOut }
]
update : Msg -> Model -> Model
update msg model =
case msg of
FromFoo msg_ ->
{ model | foo = Foo.update msg_ model.foo }
SignOut ->
{ model | user = Nothing }
```

View File

@ -0,0 +1,26 @@
---
{ "title": "deploying"
, "description": "sharing your app with the world!"
}
---
<iframe></iframe>
### using netlify
Netlify is a free way to publish your app.
When you run [elm-spa init](/docs/elm-spa/init), a file is automatically created
in your project named `netlify.toml`.
Additionally, commands like `npm run build` have already been implemented to
make sharing your app easy!
After you push your code up to Github, and create a free [Netlify account](https://netlify.com),
you should provide these details in your project's deploy settings:
Setting | Value
:-- | :--
__Build command__ | `npm run build`
__Publish directory__ | `public`

View File

@ -0,0 +1,15 @@
---
{ "title": "faqs"
, "description": "common questions from the community!"
}
---
### how do i submit an faq?
If you think a question is common enough to make it in here, you can submit it
to the `#elm-spa` channel in [the official Elm slack](https://elmlang.herokuapp.com/).
If your question/answer combo gets 10 👍 emojis, it's officially considered "frequently asked"
and we'd be happy to add it in!
It's a ridiculous system, and I'm very excited to see if it actually works.

View File

@ -0,0 +1,66 @@
---
{ "title" : "Page.component"
, "description": "pages that make global updates."
}
---
<iframe></iframe>
### pages that make global updates.
A "component" page is just a [Page.element](./element) that can update the global state
by sending messages!
Both `init` and `update` now return something like this:
```elm
( Model, Cmd Msg, Cmd Global.Msg )
```
We can use `elm-spa add` to create a component page like this:
```bash
npx elm-spa add component SignIn
```
A sign in page is a good example of when we would reach for a component instead
of an element.
We can have the update function send out a `Global` message to update the logged
in user.
It's also very common to omit the `always` to give our functions access
to the `Global.Model` from the page context.
```elm
Page.component
{ title = always title
, init = init -- *removed always
, update = update -- *removed always
, view = view -- *removed always
, subscriptions = always subscriptions
}
```
Maybe your `init` does something like this:
```elm
import Global
type alias Model =
{ user : Maybe User
}
type Msg = NoOp
init :
PageContext
-> Params.SignIn
-> ( Model, Cmd Msg, Cmd Global.Msg )
init context _ =
( { user = context.global.user }
, Cmd.none
, Global.SignIn "ryan@elm-spa.dev"
)
```

View File

@ -6,7 +6,7 @@ html, body {
}
.markdown {
max-width: 60ch;
max-width: 40em;
}
.markdown > * {
@ -103,8 +103,8 @@ html, body {
.markdown iframe {
width: 100%;
height: calc(61.25vw - 2rem);
max-width: 512px;
max-height: 288px;
max-width: 560px;
max-height: 315px;
margin-top: 2em;
background: #eee;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
@ -116,6 +116,7 @@ html, body {
display: block;
max-width: 100%;
overflow-x: auto;
margin: 1rem 0;
}
.markdown thead {
@ -123,6 +124,10 @@ html, body {
font-weight: 600;
}
.markdown th {
padding-bottom: 4px;
}
.markdown td {
padding-right: 1em;
}

View File

@ -1,11 +1,9 @@
module Components.Hero exposing (Options, view)
import Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Region as Region
import Ui exposing (colors, styles)
import Ui exposing (styles)
type alias Options =

View File

@ -28,6 +28,10 @@ links =
, Heading "layouts"
, Link ( "overview", routes.docs_dynamic "layouts" )
, Link ( "transitions", routes.docs_dynamic_dynamic "layouts" "transitions" )
, Heading "other things"
, Link ( "components", routes.docs_dynamic "components" )
, Link ( "deploying", routes.docs_dynamic "deploying" )
, Link ( "faqs", routes.docs_dynamic "faqs" )
]
@ -101,7 +105,7 @@ view activeRoute =
column
[ alignTop
, spacing 16
, width (px 180)
, width (px 200)
, paddingEach { top = 84, left = 0, right = 0, bottom = 0 }
]
[ el [ Font.size 24, Font.semiBold ] (text "docs")

View File

@ -36,7 +36,7 @@ view =
, subtitle = "(coming soon)"
, links = []
}
, el [ centerX, width (fill |> maximum 480) ] <|
, el [ centerX, width (fill |> maximum 512) ] <|
Ui.markdown """
### what can i build with elm-spa?
@ -44,7 +44,6 @@ __This entire site!__ And in this guide we'll build it together, from scratch.
(Step-by-step, with short videos)
<iframe></iframe>
Until that's ready checkout the [docs](/docs)!
"""
, link ([ centerX ] ++ Ui.styles.button) { label = text "let's gooo", url = "/docs" }
]

View File

@ -33,7 +33,7 @@ page =
view : Element Msg
view =
Hero.view
{ title = "page not found?"
{ title = "that's a 404"
, subtitle = "it's not you, it's me."
, links =
[ { label = "but this link works!"

View File

@ -37,7 +37,7 @@ view =
, links = [ { label = "learn more", url = "/docs" } ]
}
, el
[ width (fill |> maximum 480)
[ width (fill |> maximum 512)
, centerX
]
<|