diff --git a/html/Shpadoinkle/Html/Event.hs b/html/Shpadoinkle/Html/Event.hs
index d748e45cb18880f14a52d59565c85c6941fb641c..79a1c07225a9b6ab67e1143ae7295e1ce271835c 100644
--- a/html/Shpadoinkle/Html/Event.hs
+++ b/html/Shpadoinkle/Html/Event.hs
@@ -89,6 +89,10 @@ preventDefault :: RawEvent -> JSM ()
preventDefault e = void $ valToObject e # ("preventDefault" :: String) $ ([] :: [()])
+stopPropagation :: RawEvent -> JSM ()
+stopPropagation e = void $ valToObject e # ("stopPropagation" :: String) $ ([] :: [()])
+
+
onSubmitC :: Continuation m a -> (Text, Prop m a)
onSubmitC m = listenRaw "submit" $ \_ e -> preventDefault e >> return m
$(mkEventVariants "submit")
diff --git a/website/Shpadoinkle/Website/Page/Documentation.hs b/website/Shpadoinkle/Website/Page/Documentation.hs
index 57418d9bdbd512e126e47b403ea12da802b2d730..cb1f36a374d8dbb21acc279a51679bb21e9d5168 100644
--- a/website/Shpadoinkle/Website/Page/Documentation.hs
+++ b/website/Shpadoinkle/Website/Page/Documentation.hs
@@ -64,9 +64,9 @@ top
:: MonadJSM m
=> Route -> Route -> Text -> Maybe (Html m a, Html m a) -> Html m a
top cur r t mext = div [ class' side_nav__expand ] $
- [ a [ class' $ side_nav__expand__button <> asClass (side_nav__bold, r == cur)
- , goTo r
- ] $ [ text t ] <> maybeToList (fst <$> mext)
+ [ a ( class' (side_nav__expand__button <> asClass (side_nav__bold, r == cur))
+ : goTo r
+ ) $ [ text t ] <> maybeToList (fst <$> mext)
] <> maybeToList (snd <$> mext)
@@ -93,8 +93,9 @@ expandable cur toR t = let topr = toR $ minBound @r in top cur topr t $ Just
sideNavLink :: MonadJSM m => Humanize r => Route -> (r -> Route) -> r -> Html m a
sideNavLink cur toR r = li_
- [ a [ class' $ side_nav__link <> asClass (side_nav__bold, cur == toR r)
- , goTo (toR r) ] [ text $ humanize r ]
+ [ a ( class' (side_nav__link <> asClass (side_nav__bold, cur == toR r))
+ : goTo (toR r)
+ ) [ text $ humanize r ]
]
diff --git a/website/Shpadoinkle/Website/Page/Home.hs b/website/Shpadoinkle/Website/Page/Home.hs
index 1d74efde6bbbc995a6747b63f28fd03a87d7ded5..c98b9515dea1abe7e1f96f57b3925ab9599254c2 100644
--- a/website/Shpadoinkle/Website/Page/Home.hs
+++ b/website/Shpadoinkle/Website/Page/Home.hs
@@ -22,19 +22,19 @@ import Shpadoinkle.Html as H hiding
import Shpadoinkle.Html.TH.AssetLink (assetLink)
import Shpadoinkle.Lens (onRecord,
onRecordEndo)
-import Shpadoinkle.Router (MonadJSM,
- navigate)
+import Shpadoinkle.Router (MonadJSM)
import Shpadoinkle.Website.Component.LiveExample as Live (example)
import Shpadoinkle.Website.Style as Style
import Shpadoinkle.Website.Types.Effects.Example (ExampleEffects)
import Shpadoinkle.Website.Types.Example (Example)
import Shpadoinkle.Website.Types.Home (ExampleLens,
Examples, Home)
-import Shpadoinkle.Website.Types.Nav (Nav (NGettingStarted),
+import Shpadoinkle.Website.Types.Nav (Nav (NGettingStarted, NConcept),
toRoute)
-import Shpadoinkle.Website.Types.Route (Route (RGettingStarted))
-import Shpadoinkle.Website.Types.Route.GettingStarted (Route (RGSIndex))
-import Shpadoinkle.Website.Types.SPA (SPA, goTo)
+import Shpadoinkle.Website.Types.Route as Route
+import Shpadoinkle.Website.Types.Route.Packages (Route (RPLens, RPBackends, RPRouter))
+import Shpadoinkle.Website.Types.Route.Tutorial (Route (RTCalculator))
+import Shpadoinkle.Website.Types.SPA (goTo)
view :: (MonadJSM m, ExampleEffects m) => Home -> Html m Home
@@ -55,7 +55,7 @@ hero' = div_
]
, div [ class' hero_button ]
[ div [ class' $ split_button <> hero_button__content ]
- [ button [ class' split_button__button, type' "button", goTo $ toRoute NGettingStarted ]
+ [ a (class' split_button__button : goTo (toRoute NGettingStarted))
[ span [ class' $ split_button__split1 <> hero_button__split ]
[ "Get Started" ]
, span [ class' $ split_button__split2 <> hero_button__split ]
@@ -70,14 +70,15 @@ hero' = div_
]
-data FeatureCard = FeatureCard
+data FeatureCard m a = FeatureCard
{ card'icon :: Text
, card'title :: Text
- , card'description :: Text
+ , card'description :: [Html m a]
+ , card'link :: Route.Route
}
-featureCard :: FeatureCard -> Html m a
+featureCard :: MonadJSM m => FeatureCard m a -> Html m a
featureCard fc = div [ class' feature__card ]
[ div [ class' feature__card__content ]
[ div [ class' feature__card__heading ]
@@ -86,37 +87,73 @@ featureCard fc = div [ class' feature__card ]
[ text $ card'title fc ]
]
, p [ class' feature__card__description ]
- [ text $ card'description fc ]
+ (card'description fc)
+ , a (class' feature__card__link : goTo (card'link fc))
+ [ text "Learn More ➢" ]
]
]
-features :: [FeatureCard]
+features :: MonadJSM m => [FeatureCard m a]
features =
[ FeatureCard
{ card'icon = $(assetLink "/assets/fast_icon.svg")
, card'title = "Fast"
- , card'description = "Shpadoinkle does little work. The renderer is modular, so you can always benefit from the latest advances in virtual DOM rendering."
+ , card'description =
+ [ text "Shpadoinkle does "
+ , em_ [text "minimal work"]
+ , text ". The renderer is modular, so you can always benefit from the "
+ , strong_ [text "latest advances"]
+ , text " in virtual DOM rendering."]
+ , card'link = RPackages RPBackends
}
, FeatureCard
{ card'icon = $(assetLink "/assets/declarative_icon.svg")
, card'title = "Declarative"
- , card'description = "Your Shpadoinkle code is high-level. You need not worry about low-level details, causality, or when DOM nodes get replaced."
+ , card'description =
+ [ text "Your Shpadoinkle code is "
+ , strong_ [text "high-level"]
+ , text ". You don't need to worry about low-level details, causality, or when DOM nodes get replaced."
+ ]
+ , card'link = toRoute NConcept
}
, FeatureCard
{ card'icon = $(assetLink "/assets/composable_icon.svg")
, card'title = "Composable"
- , card'description = "Shpadoinkle avoids elaborate passing of messages and payloads. Components are highly composable with Lenses"
+ , card'description =
+ [ text "Shpadoinkle avoids elaborate message passing and payloads. Components are "
+ , em_ [text "highly composable"]
+ , text " with "
+ , a (goTo $ RPackages RPLens) [text "Lenses"]
+ , text "."
+ ]
+ , card'link = RTutorial RTCalculator
}
, FeatureCard
{ card'icon = $(assetLink "/assets/reliable_icon.svg")
, card'title = "Reliable"
- , card'description = "Shpadoinkle UIs are composed of components with no side-effects. Runtime errors are exceedingly rare. Code is easy to test because model updates are pure functions."
+ , card'description =
+ [ text "Shpadoinkle UIs are composed of components with "
+ , strong_ [text "no side-effects"]
+ , text ". Runtime errors are exceedingly rare. Code is easy to test because model updates are "
+ , em_ [text "pure functions"]
+ , text "."
+ ]
+ , card'link = RFourOhFour
}
, FeatureCard
{ card'icon = $(assetLink "/assets/lorem_ipsum_logo.svg")
, card'title = "Type Safe"
- , card'description = "Shpadoinkle facilitates type safe UI code. Everything from client server communication with Servant, to compile time checked asset paths, is designed with types in mind."
+ , card'description =
+ [ text "Shpadoinkle facilitates type safe UI code. "
+ , strong_ [text "Everything"]
+ , text ", from client / server communication with "
+ , a (goTo $ RPackages RPRouter) [text "Servant"]
+ , text ", to compile-time verified asset paths, is designed with "
+ , em_ [text "types"]
+ , text " in mind."
+ ]
+ , card'link = RFourOhFour
}
]
@@ -130,15 +167,19 @@ featureSection = div [ class' feature__section ]
]
, div [ class' feature_button ]
[ div [ class' $ split_button <> feature_button__content ]
- [ button [ class' split_button__button, type' "button", onClickM_ . navigate @(SPA m) $ RGettingStarted RGSIndex ]
+ [ a (class' split_button__button : goTo (toRoute NConcept))
[ span [ class' $ split_button__split1 <> feature_button__split ]
- [ "Checkout the getting started guide" ]
+ [ "Learn Shpadoinkle" ]
, span [ class' $ split_button__split2 <> feature_button__split ]
[ img' [ alt "Right arrow", src $(assetLink "/assets/right-arrow.svg") ]
]
]
]
]
+ , p_
+ [ text "Or, "
+ , a (goTo (toRoute NGettingStarted)) [text "Get Started"]
+ ]
, img' [ alt "background transition", class' transition, src $(assetLink "/assets/orange_purple_transition.svg") ]
]
@@ -167,7 +208,7 @@ componentsSection :: (MonadJSM m, ExampleEffects m) => Examples Example -> Html
componentsSection exs = div [ class' components__section ]
[ componentExample #helloWorld exs $ ComponentExample
{ title' = "Hello World"
- , description = "Shpadoinkle components are expressed as functions, no classes, inheritance, or JSX style new syntax. Purity of view logic is guaranteed by the type system."
+ , description = "Shpadoinkle components are expressed as functions — no classes, inheritance, or JSX style new syntax. Purity of view logic is guaranteed by the type system."
}
, componentExample #counter exs $ ComponentExample
{ title' = "State is easy"
@@ -175,7 +216,7 @@ componentsSection exs = div [ class' components__section ]
}
, componentExample #todo exs $ ComponentExample
{ title' = "An Application"
- , description = "Using event handlers, and pure functions we can compose applications without any further abstraction."
+ , description = "Using event handlers and pure functions, we can compose applications without any further abstraction."
}
, img' [ alt "background transition", className "transition", src $(assetLink "/assets/purple_black_transition.svg") ]
]
diff --git a/website/Shpadoinkle/Website/Partials/Footer.hs b/website/Shpadoinkle/Website/Partials/Footer.hs
index ff9be7078c08b16bd90728c354b163e5e4f22510..e2844f2f280521e378f8a703f8813ecda8ebe0a0 100644
--- a/website/Shpadoinkle/Website/Partials/Footer.hs
+++ b/website/Shpadoinkle/Website/Partials/Footer.hs
@@ -23,7 +23,7 @@ import Shpadoinkle.Widgets.Types (humanize)
footerLink :: MonadJSM m => Nav -> Html m a
footerLink n = li_
- [ a [ goTo $ toRoute n ]
+ [ a (goTo $ toRoute n)
[ text $ humanize n ]
]
@@ -32,7 +32,7 @@ view :: MonadJSM m => CurrentYear -> Html m a
view cy =
footer_
[ div [ class' footer__wrapper ]
- [ a [ goTo RHome ]
+ [ a (goTo RHome)
[ img' [ alt "shpadoinkle logo", src $(assetLink "/assets/try_shpadoinkle_footer_logo.svg") ]
]
, div [ class' $ flex <> flex_col <> justify_between <> w_full <> gap_8 <> pt_12 <> leading_8 <> "md:flex-row" <> "md:w-2/5" <> "md:gap-0" ]
diff --git a/website/Shpadoinkle/Website/Partials/Nav.hs b/website/Shpadoinkle/Website/Partials/Nav.hs
index 11515e40e485d594435d17ee8d7022d1f538d6f3..f61c18ac891325ff84c66178dd53d36dc10e11aa 100644
--- a/website/Shpadoinkle/Website/Partials/Nav.hs
+++ b/website/Shpadoinkle/Website/Partials/Nav.hs
@@ -29,19 +29,19 @@ toActive = \case
navLinkDesktop :: MonadJSM m => Route -> Nav -> Html m a
navLinkDesktop r n = li [ class' nav__link ] $
- a [ goTo $ toRoute n ] [ text $ humanize n ]
+ a (goTo $ toRoute n) [ text $ humanize n ]
: [ img' [ class' $ if r == RHome then nav__link_underline else nav__link__underline_docs
, src $(assetLink "/assets/nav_line.svg") ] | toActive r == Just n ]
navLinkMobile :: MonadJSM m => Nav -> Html m a
-navLinkMobile n = a [ class' nav_mobile__link, goTo $ toRoute n ] [ text $ humanize n ]
+navLinkMobile n = a (class' nav_mobile__link : goTo (toRoute n)) [ text $ humanize n ]
view :: MonadJSM m => Toggle -> Route -> [Html m Toggle]
view t r = pure $ div [ class' started_header ] $
[ H.nav [ class' Style.nav ]
- [ a [ goTo RHome ]
+ [ a (goTo RHome)
[ img' [ class' nav__logo, src $(assetLink "/assets/landing_logo.svg") ]
]
, img' [ class' nav__menu_icon, onClick toggle, src $ case t of
diff --git a/website/Shpadoinkle/Website/Types/SPA.hs b/website/Shpadoinkle/Website/Types/SPA.hs
index d5e0b46c04b4e3e19409b9cabfa0b439cb24e269..9fdbb8cb23bbcfeb54fc32c0d11e6e59a6ebc650 100644
--- a/website/Shpadoinkle/Website/Types/SPA.hs
+++ b/website/Shpadoinkle/Website/Types/SPA.hs
@@ -20,9 +20,14 @@ import Data.Proxy (Proxy (Proxy))
import Data.Text (Text, pack)
import Servant.API (type (:<|>) (..),
type (:>))
-import Shpadoinkle (MonadJSM, Prop)
+import Shpadoinkle (MonadJSM, Prop,
+ liftJSM,
+ listenRaw)
+import Shpadoinkle.Continuation (done)
import Shpadoinkle.Html (href,
- onClickM_)
+ onClickM_,
+ preventDefault,
+ stopPropagation)
import Shpadoinkle.Router (Redirect (..),
Routed (..),
View, getURI,
@@ -128,9 +133,19 @@ instance Routed (SPA m) Route where
Redirect (Proxy @("404" :> View' m)) id
-goTo :: forall m a. MonadJSM m => Route -> (Text, Prop m a)
+goTo :: forall m a. MonadJSM m => Route -> [(Text, Prop m a)]
#ifndef ghcjs_HOST_OS
-goTo = href . ("/" <>) . pack . show . getURI @(SPA m)
+goTo r = [href . ("/" <>) . pack . show $ getURI @(SPA m) r]
#else
-goTo = onClickM_ . navigate @(SPA m)
+goTo r =
+ [ listenRaw "click" (const handleEvent)
+ , href . ("/" <>) . pack . show $ getURI @(SPA m) r
+ ]
+ where
+ handleEvent e = do
+ liftJSM $ do
+ preventDefault e
+ stopPropagation e
+ navigate @(SPA m) r
+ pure done
#endif
diff --git a/website/assets/style.css b/website/assets/style.css
index 3d4e07b8afcf7d8dd339c74f272246c62f525fb1..c49586a5062db9311ebfa0822956c427280cd3c4 100644
--- a/website/assets/style.css
+++ b/website/assets/style.css
@@ -1081,6 +1081,10 @@ body {
.feature-button {
display: none;
justify-content: center;
+ margin-bottom: 1rem;
+}
+.feature-button + p {
+ text-align: center;
margin-bottom: 3rem;
}
@media (min-width: 768px) {
@@ -1091,10 +1095,9 @@ body {
.feature-button__content {
--tw-border-opacity: 1;
border-color: rgba(196, 91, 40, var(--tw-border-opacity));
- width: 24rem;
+ width: 17rem;
}
.feature-button__split {
- margin-top: 10px;
--tw-bg-opacity: 1;
background-color: rgba(241, 226, 85, var(--tw-bg-opacity));
--tw-text-opacity: 1;
@@ -1222,6 +1225,20 @@ body {
width: 100%;
margin-bottom: 3.5rem;
}
+.feature__section a {
+ color: rgba(241, 226, 85);
+ text-decoration: none;
+ -moz-transition: all .2s ease-in;
+ -o-transition: all .2s ease-in;
+ -webkit-transition: all .2s ease-in;
+ transition: all .2s ease-in;
+}
+.feature__section a:hover {
+ color: #D9CE5F;
+}
+.feature__section em, .feature__section strong {
+ color: #EDCFC0;
+}
@media (min-width: 768px) {
.feature__card {
margin-bottom: 3.5rem;
@@ -1242,6 +1259,8 @@ body {
padding-left: 2rem;
padding-right: 2rem;
width: 100%;
+ display: flex;
+ flex-direction: column;
}
@media (min-width: 768px) {
.feature__card__content {
@@ -1264,6 +1283,10 @@ body {
.feature__card__description {
font-size: 1.125rem;
line-height: 1.75rem;
+ flex-grow: 1;
+}
+.feature__card__link {
+ align-self: flex-end;
}
.components__section {
--tw-bg-opacity: 1;
diff --git a/website/docs/concepts/other-paradigms.adoc b/website/docs/concepts/other-paradigms.adoc
new file mode 100644
index 0000000000000000000000000000000000000000..61aacbb8d9b752a607e7b044e172244f1c29dfd6
--- /dev/null
+++ b/website/docs/concepts/other-paradigms.adoc
@@ -0,0 +1,3 @@
+:icons: font
+
+Issues with traditional models