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