mirror of
https://github.com/ryan-haskell/elm-spa.git
synced 2024-09-11 12:35:44 +03:00
more docs, more fun– six docs!
This commit is contained in:
parent
114c286e08
commit
356ca9796b
@ -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"
|
||||
}
|
||||
}
|
||||
|
6
examples/docs/package-lock.json
generated
6
examples/docs/package-lock.json
generated
@ -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": {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
282
examples/docs/public/content/docs/components.md
Normal file
282
examples/docs/public/content/docs/components.md
Normal 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 }
|
||||
```
|
26
examples/docs/public/content/docs/deploying.md
Normal file
26
examples/docs/public/content/docs/deploying.md
Normal 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`
|
||||
|
15
examples/docs/public/content/docs/faqs.md
Normal file
15
examples/docs/public/content/docs/faqs.md
Normal 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.
|
66
examples/docs/public/content/docs/pages/component.md
Normal file
66
examples/docs/public/content/docs/pages/component.md
Normal 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"
|
||||
)
|
||||
```
|
@ -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;
|
||||
}
|
@ -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 =
|
||||
|
@ -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")
|
||||
|
@ -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" }
|
||||
]
|
||||
|
@ -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!"
|
||||
|
@ -37,7 +37,7 @@ view =
|
||||
, links = [ { label = "learn more", url = "/docs" } ]
|
||||
}
|
||||
, el
|
||||
[ width (fill |> maximum 480)
|
||||
[ width (fill |> maximum 512)
|
||||
, centerX
|
||||
]
|
||||
<|
|
||||
|
Loading…
Reference in New Issue
Block a user