├── src
├── main
│ ├── clj-kondo.exports
│ │ └── dv
│ │ │ └── cljs-emotion
│ │ │ └── config.edn
│ └── dv
│ │ ├── emotion_helix_dom.cljc
│ │ ├── emotion_css.cljc
│ │ ├── cljs_emotion_reagent.cljc
│ │ └── cljs_emotion.cljc
└── dev
│ └── dv
│ └── cljs_emotion
│ ├── fulcro_cards.cljs
│ ├── reagent_debug.cljs
│ ├── debug.cljs
│ ├── helix_cards.cljs
│ ├── target_styled.cljs
│ ├── reagent_cards.cljs
│ └── devcards.cljs
├── test
└── dv
│ └── cljs_emotion_test.clj
├── .gitignore
├── resources
└── public
│ └── devcards.html
├── package.json
├── Makefile
├── shadow-cljs.edn
├── LICENSE
├── music.md
├── deps.edn
├── pom.xml
├── README.md
└── yarn.lock
/src/main/clj-kondo.exports/dv/cljs-emotion/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as
2 | {dv.cljs-emotion/defstyled clojure.core/def
3 | dv.cljs-emotion-reagent/defstyled clojure.core/def}}
4 |
--------------------------------------------------------------------------------
/test/dv/cljs_emotion_test.clj:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion-test
2 | (:require [clojure.test :refer :all]
3 | [dv.cljs-emotion :refer :all]))
4 |
5 | (deftest a-test
6 | (testing "FIXME, I fail."
7 | (is (= 0 1))))
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | *.jar
5 | *.class
6 | /.cpcache
7 | /.lein-*
8 | /.nrepl-history
9 | /.nrepl-port
10 | *.iml
11 | *.idea
12 | *.shadow-cljs
13 | node_modules
14 | yarn-error.log
15 | resources/public/js
16 |
--------------------------------------------------------------------------------
/resources/public/devcards.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | dv.cljs-emotion devcards
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dv.cljs-emotion",
3 | "version": "1.2.3",
4 | "repository": "git@github.com:dvingo/cljs-emotion.git",
5 | "author": "Daniel Vingo ",
6 | "license": "MIT",
7 | "devDependencies": {
8 | "@emotion/react": "^11.10.5",
9 | "@emotion/styled": "^11.10.5",
10 | "create-react-class": "^15.7.0",
11 | "highlight.js": "^10.4.1",
12 | "marked": "^1.1.0",
13 | "polished": "^4.1.3",
14 | "react": "^17.0.2",
15 | "react-dom": "^17.0.2",
16 | "shadow-cljs": "^2.16.10"
17 | },
18 | "dependencies": {
19 | "react-grid-layout": "^0.16.6"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := bash
2 | .ONESHELL:
3 | .SHELLFLAGS := -eu -o pipefail -c
4 | .DELETE_ON_ERROR:
5 | MAKEFLAGS += --warn-undefined-variables
6 | MAKEFLAGS += --no-builtin-rules
7 |
8 | ifeq ($(origin .RECIPEPREFIX), undefined)
9 | $(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)
10 | endif
11 | .RECIPEPREFIX = >
12 |
13 | jar:
14 | > clojure -Spom
15 | > clojure -X:jar
16 |
17 | dev:
18 | > yarn
19 | > yarn shadow-cljs watch devcards
20 |
21 | # To deploy to gh-pages:
22 |
23 | # Make commits on master
24 | # gco gh-pages
25 | # git merge master
26 | # make gh-pages
27 | # gc -m
28 | # gpoh -f
29 | gh-pages:
30 | > yarn shadow-cljs release devcards
31 | > mv resources/public/js/devcards/devcards.js docs/
32 |
--------------------------------------------------------------------------------
/shadow-cljs.edn:
--------------------------------------------------------------------------------
1 | {:deps
2 | {:aliases [:dev]}
3 |
4 | :nrepl
5 | {:port 3434}
6 |
7 | :builds
8 | {:devcards {:target :browser
9 | :output-dir "resources/public/js/devcards"
10 | :asset-path "/js/devcards"
11 |
12 | :compiler-options {:devcards true
13 | :closure-defines {dv.cljs-emotion/ADD_CLASSNAMES true
14 | dv.cljs-emotion-reagent/ADD_CLASSNAMES true}}
15 |
16 | :modules {:devcards {:entries [dv.cljs-emotion.devcards]
17 | :init-fn dv.cljs-emotion.devcards/main}}
18 |
19 | :devtools {:http-port 4001
20 | :http-root "resources/public"
21 | :push-state/index "devcards.html"
22 | :after-load dv.cljs-emotion.devcards/main}}}}
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Daniel Vingo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/music.md:
--------------------------------------------------------------------------------
1 |
2 | This file contains links to music listened to while creating this software.
3 |
4 | Bonobo - Essential Mix 1481 (Live at Glastonbury) - 02 July 2022 | BBC Radio 1
5 |
6 | https://www.youtube.com/watch?v=PwbYUFY6Q3A
7 |
8 | Maya Jane Coles Boiler Room & Ballantine's True Music Russia DJ Set
9 |
10 | https://www.youtube.com/watch?v=DZuXhbpuRRY
11 |
12 | Maya Jane Coles Boiler Room & Ballantine's Stay True Scotland DJ Set
13 |
14 | https://www.youtube.com/watch?v=7m0v5uOQ0XQ
15 |
16 | Waterkant Souvenirs Podcast Mira 2012-12-14
17 |
18 | https://soundcloud.com/mira_kater/waterkant_podcast?in=serjin/sets/mira-chris
19 |
20 |
21 | Mira | mach mal langsam... | www.klangextase.de
22 |
23 | https://soundcloud.com/mira_kater/mira-mach-mal-langsam-klangextase?in=serjin/sets/mira-chris
24 |
25 |
26 | Tara Brooks
27 |
28 | https://soundcloud.com/deep-house-amsterdam/tara-brooks-dha-mix-318
29 |
30 |
31 | nicolás jaar 2 hour mix april 16 19:00 gmt
32 |
33 | https://soundcloud.com/prcvl/nicolas-jaar-april161900gmt
34 |
35 |
36 | massive attack essential mix bbc radio 1 1994-11-12
37 | https://www.youtube.com/watch?v=qhmdED0Nq4c
38 |
39 |
40 | Nina Kraviz - Billie Eilish - Hans Zimmer ◆ You Can't Stop Me (Electro Junkiee Mix)
41 |
42 | https://www.youtube.com/watch?v=CR0fDrij2E8
43 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/main" "resources"]
2 | :deps {org.clojure/clojure {:mvn/version "1.10.3"}
3 | camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"}
4 | borkdude/dynaload {:mvn/version "0.3.5"}}
5 | :aliases
6 | {:dev {:extra-paths ["src/dev"]
7 | :extra-deps {devcards/devcards {:mvn/version "0.2.7"}
8 | reagent/reagent {:mvn/version "1.1.1" :exclusions [cljsjs/react]}
9 | com.fulcrologic/fulcro {:mvn/version "3.5.29"}
10 | lilactown/helix {:mvn/version "0.1.9"}
11 | sablono/sablono {:mvn/version "0.8.6"}
12 | binaryage/devtools {:mvn/version "1.0.6"}
13 | dv/clj-utils {:mvn/version "2021-10-31.0.0"}
14 | thheller/shadow-cljs {:mvn/version "2.16.10"}}}
15 | :test {:extra-paths ["test"]
16 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}}}
17 | :runner
18 | {:extra-deps {com.cognitect/test-runner
19 | {:git/url "https://github.com/cognitect-labs/test-runner"
20 | :sha "dd6da11611eeb87f08780a30ac8ea6012d4c05ce"}}
21 | :main-opts ["-m" "cognitect.test-runner" "-d" "test"]}
22 |
23 | :jar {:replace-deps {seancorfield/depstar {:mvn/version "2.0.165"}}
24 | :exec-fn hf.depstar/jar
25 | :exec-args {:jar "dv.cljs-emotion.jar"}}
26 |
27 | :install {:extra-deps {deps-deploy/deps-deploy {:mvn/version "0.0.12"}}
28 | :main-opts ["-m" "deps-deploy.deps-deploy" "install" "dv.cljs-emotion.jar"]}
29 |
30 | :deploy {:extra-deps {deps-deploy/deps-deploy {:mvn/version "0.0.12"}}
31 | :main-opts ["-m" "deps-deploy.deps-deploy" "deploy" "dv.cljs-emotion.jar"]}}}
32 |
--------------------------------------------------------------------------------
/src/dev/dv/cljs_emotion/fulcro_cards.cljs:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion.fulcro-cards
2 | (:require
3 | [com.fulcrologic.fulcro.rendering.multiple-roots-renderer :as fr]
4 | [devcards.core :as dc :refer (defcard)]
5 | [dv.cljs-emotion.target-styled]
6 | [dv.cljs-emotion.reagent-cards]
7 | [dv.devcards-fulcro3 :as f3]
8 | [com.fulcrologic.fulcro.dom :as dom]
9 | [com.fulcrologic.fulcro.components :as fc :refer [defsc]]
10 | [sablono.core :as sab :refer [html]]
11 | ["polished" :as p :refer [darken]]
12 | [dv.cljs-emotion :as em :refer [defstyled keyframes global-style theme-provider]]))
13 |
14 | (defsc Button [this {:keys [className text] :as props}]
15 | {:query [:className :text]
16 | :ident (fn [_] [::id :button])
17 | :initial-state (fn [_] {:text "Hello world"})}
18 | (dom/div
19 | (dom/button {:className className} text)))
20 |
21 | (def ui-button (fc/computed-factory Button))
22 |
23 | (defonce app-id (random-uuid))
24 |
25 | (def fulcro-app
26 | (f3/upsert-app
27 | {::f3/root Button
28 | ::f3/root-state {}
29 | ::f3/wrap-root? true
30 | ::f3/persistence-key app-id
31 | ::f3/app {}
32 | :fulcro.inspect.core/app-id app-id}))
33 |
34 | (comment
35 | (println "HI")
36 |
37 | (fr/with-app-context
38 | fulcro-app
39 | (ui-button {:className "TEST CLS"})))
40 |
41 | (defcard dv-test-fulcro-styled-button
42 | (dc/dom-node
43 | (fn [_ dom-node]
44 | (f3/mount-at fulcro-app
45 | {::f3/root Button
46 | ::f3/wrap-root? true
47 | ::f3/persistence-key app-id}
48 | dom-node))))
49 |
50 | ;(f3/make-card Button)
51 |
52 | ;; the thought is to do this:
53 | ;(defstyled ui-fulcro-btn Button {:color "red"})
54 |
55 | ; and then figure out the best way to pass the className to the returned react-element before rendering -
56 | ;; probably in render middleware?
57 | ; and have this pass the className to fulcro
58 |
--------------------------------------------------------------------------------
/src/dev/dv/cljs_emotion/reagent_debug.cljs:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion.reagent-debug
2 | (:require
3 | [devcards.core :as dc :refer [defcard-rg defcard]]
4 | ["polished" :as p :refer [darken]]
5 | [dv.cljs-emotion-reagent :as em :refer [defstyled keyframes global-style theme-provider]]))
6 |
7 | (defstyled full-height :div
8 | {:height "100%"
9 | :background "pink"}
10 | (fn [props]
11 | (.log js/console "PROPS: " props)
12 | (:styles props)))
13 |
14 | (defn nested-fn []
15 | [:div
16 | (full-height
17 | {:styles {:width "70%" "@media (min-width: 700px)" {:background "skyBLUE" :width "100%"}}}
18 | [:p "hello body"])])
19 |
20 | (dc/defcard-doc "Cljs properties should be passed to callbacks untouched." (dc/mkdn-pprint-source full-height))
21 |
22 | (defcard breakpoint-debug (dc/mkdn-pprint-source nested-fn))
23 |
24 | (defcard (dc/reagent [nested-fn]))
25 |
26 | (defcard
27 | "Testing 1-arity test."
28 | (dc/reagent
29 | [:div
30 | [full-height
31 | {:styles {":before"
32 | {:content "'HELLO'"
33 | "@media (min-width: 700px)" {:content "'LARGE SCREEN'"}}
34 | :width "70%" "@media (min-width: 700px)" {:background "skyBLUE" :width "100%"}}}]]))
35 |
36 | (defstyled switch-case :div
37 | (fn [{:keys [status] :as args}]
38 | ;(.log js/console "ARGS: " args)
39 | {:background
40 | (case status
41 | :active "seagreen"
42 | :offline "grey"
43 | :away "blue"
44 | "white")}))
45 |
46 | (defcard "Test keywords - you should get keywords as values when passed as keywords."
47 | (dc/reagent
48 | [:div
49 | [:div "Active" [switch-case {:status :active} "Should be green"]]
50 | [:div "Offline" [switch-case {:status :offline} "Should be grey"]]
51 | [:div "Away" [switch-case {:status :away} "Should be blue"]]
52 | [:div "Missing" [switch-case "Should be white"]]]))
53 |
54 | (defstyled hover-hand :div {":hover" {:cursor "pointer"}})
55 | (defcard "Test js-props - hover should show pointer"
56 | (dc/reagent [:div [hover-hand #js{:key "login-label"} "Login"] ]))
57 |
--------------------------------------------------------------------------------
/src/dev/dv/cljs_emotion/debug.cljs:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion.debug
2 | (:require
3 | [devcards.core :as dc :refer (defcard)]
4 | [sablono.core :as sab :refer [html]]
5 | ["polished" :as p :refer [darken]]
6 | [dv.cljs-emotion :as em :refer [defstyled keyframes global-style theme-provider]]))
7 |
8 | (defstyled full-height :div
9 | {:height "100%"
10 | :background "pink"}
11 | (fn [props]
12 | (.log js/console "PROPS: " props)
13 | (:styles props)))
14 |
15 | (defn nested-fn []
16 | (html [:div
17 | (full-height
18 | {:styles {:width "70%" "@media (min-width: 700px)" {:background "skyBLUE" :width "100%"}}}
19 | (html [:p "hello body"]))]))
20 |
21 | (defcard "Cljs properties should be passed to callbacks untouched." (dc/mkdn-pprint-source full-height))
22 |
23 | (defcard breakpoint-debug (dc/mkdn-pprint-source nested-fn))
24 |
25 | (defcard (nested-fn))
26 |
27 | (defcard
28 | "Testing 1-arity test."
29 | (html [:div
30 | (full-height
31 | {:styles {":before"
32 | {:content "'HELLO'"
33 | "@media (min-width: 700px)" {:content "'LARGE SCREEN'"}}
34 | :width "70%" "@media (min-width: 700px)" {:background "skyBLUE" :width "100%"}}})]))
35 |
36 | (defstyled switch-case :div
37 | (fn [{:keys [status] :as args}]
38 | ;(.log js/console "ARGS: " args)
39 | {:background
40 | (case status
41 | :active "seagreen"
42 | :offline "grey"
43 | :away "blue"
44 | "white")}))
45 |
46 | (defcard
47 | "# Test keywords
48 | You should get keywords as values in a style fn when passed as keywords."
49 | (html
50 | [:div
51 | [:div "Active" (switch-case {:status :active} "Should be green")]
52 | [:div "Offline" (switch-case {:status :offline} "Should be grey")]
53 | [:div "Away" (switch-case {:status :away} "Should be blue")]
54 | [:div "Missing" (switch-case "Should be white")]]))
55 |
56 |
57 | (defstyled hover-hand :div {":hover" {:cursor "pointer"}})
58 | (defcard "Test js-props - hover should show pointer"
59 | (html [:div (hover-hand #js{:key "login-label"} "Login") ]))
60 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | dv
5 | cljs-emotion
6 | 2023-07-16.0.0
7 | cljs-emotion
8 | ClojureScript wrapper of emotion css-in-js library
9 | https://github.com/dvingo/dv.cljs-emotion
10 |
11 |
12 | MIT
13 | https://opensource.org/licenses/MIT
14 |
15 |
16 |
17 |
18 | Daniel Vingo
19 |
20 |
21 |
22 | https://github.com/dvingo/dv.cljs-emotion
23 | scm:git:git://github.com/dvingo/cljs-emotion.git
24 | scm:git:ssh://git@github.com/dvingo/cljs-emotion.git
25 | HEAD
26 |
27 |
28 |
29 | org.clojure
30 | clojure
31 | 1.10.3
32 |
33 |
34 | com.fulcrologic
35 | guardrails
36 | 1.1.9
37 |
38 |
39 | camel-snake-kebab
40 | camel-snake-kebab
41 | 0.4.2
42 |
43 |
44 | borkdude
45 | dynaload
46 | 0.2.2
47 |
48 |
49 |
50 | src/main
51 |
52 |
53 |
54 | clojars
55 | https://repo.clojars.org/
56 |
57 |
58 |
59 |
60 | clojars
61 | Clojars repository
62 | https://clojars.org/repo
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/dev/dv/cljs_emotion/helix_cards.cljs:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion.helix-cards
2 | (:require
3 | [devcards.core :as dc :refer (defcard)]
4 | [dv.cljs-emotion.fulcro-cards]
5 | [dv.cljs-emotion.reagent-cards]
6 | [dv.cljs-emotion.reagent-debug]
7 | [dv.cljs-emotion.debug]
8 | [dv.cljs-emotion.target-styled]
9 | [dv.emotion-helix-dom :as d]
10 | [dv.cljs-emotion :as em]
11 | [helix.core :refer [$ defnc]]
12 | [dv.cljs-emotion :as em :refer [defstyled keyframes global-style theme-provider]]))
13 |
14 | (defcard "# Helix dom support for :css prop.
15 |
16 | The namespace `dv.emotion-helix-dom` is a copy of the `helix.dom` namespace with added support for emotion's `:css` property.
17 |
18 |
19 | If a component does not include the `:css` property and it does not include the dynamic property symbols `:&` and `&`,
20 | then helix's `dom-props` macro is expanded on the props, so there is no performance cost on top of helix for components
21 | that don't use styles. For any components using dynamic props the code is the same as that in helix with the addition
22 | of the `:css` property being wrapped to deal with cljs->js datatype conversion.")
23 |
24 | (defnc a-component []
25 | (d/div {:css (fn [{:keys [bg]}]
26 | {:color "yellow"
27 | :background-color (or bg "black")})}
28 | "Here is some text in a div"))
29 |
30 | (defnc a-table []
31 | (let [dyn-props {:class "TESTING"
32 | :style {:background-color "red"}}]
33 | (d/table {:css {:background-color "blue"
34 | "tbody > tr:nth-of-type(odd)" {:background-color "purple"}
35 | :border "2px solid yellow"}
36 | :& dyn-props}
37 | (d/tbody
38 | (d/tr (d/td "hello"))
39 | (d/tr (d/td "hello1"))
40 | (d/tr (d/td "hello2"))
41 | (d/tr (d/td "hello2"))
42 | (d/tr (d/td "hello2"))
43 | (d/tr (d/td "hello2"))
44 | (d/tr (d/td "hello2"))
45 | (d/tr (d/td "hello2"))
46 | (d/tr (d/td "hello2"))))))
47 |
48 | (defnc vector-of-styles []
49 | (d/div {:css [{:color "black"} {:background-color "#efefef"} #js {:borderRadius "4px"} {:border "1px solid"}]}
50 | "This text should be black on #efefef"))
51 |
52 | (defcard helix-css
53 | (d/div
54 | (d/h1 "This is a helix table with the :css prop targeting the children rows")
55 | ($ a-table)
56 | ($ vector-of-styles)))
57 |
58 | (dc/defcard-doc "In the above example we're using nested selectors with both static props and dynamic
59 | (you can inspect this page to see the `TESTING` css class is applied).
60 |
61 | In the second example we're using a
62 | vector of styles which are merged together by emotion."
63 | (dc/mkdn-pprint-source a-table)
64 | (dc/mkdn-pprint-source vector-of-styles))
65 |
66 | (defnc theme-component []
67 | (d/div
68 | (em/theme-provider {:theme {:bg "cadetblue"}}
69 | (d/h2 "With theme, bg should be cadetblue")
70 | ($ a-component))
71 | (d/h2 "With no theme, bg should be black")
72 | ($ a-component)))
73 |
74 | (defcard theme-support
75 | ($ theme-component))
76 |
77 | (dc/defcard-doc
78 | "This example demonstrates using a theme and function as the value for `:css` to read the theme data."
79 | (dc/mkdn-pprint-source theme-component)
80 | (dc/mkdn-pprint-source a-component))
81 |
82 | (def some-css
83 | {:self (em/css #js {:backgroundColor "#aefefe"
84 | :border "2px solid #eee"
85 | :borderRadius "0.5rem"
86 | :padding "1rem"})
87 | :title (em/css {:font-size "1.25rem"
88 | :color "red"}
89 | #js{:border "1px dashed"
90 | "> div > p" {:color "blue"}})
91 | :more (em/css {:border "2px solid"})})
92 |
93 | (def merged-css
94 | (em/css
95 | {:background-color "black"}
96 | (:title some-css)
97 | (:more some-css)))
98 |
99 | (defn object-styles []
100 | (d/div {:css (:self some-css)}
101 | (d/h2 {:css (:title some-css)} "here we have a title")
102 | (d/h2 {:css merged-css} "here we have a title")
103 | (d/p {:css (em/css merged-css {:color "white"})}
104 | "And overwrite inline")))
105 |
106 | (defcard object-styles-card
107 | (object-styles))
108 |
109 | (dc/defcard-doc
110 | "We can use object styles as described here [https://emotion.sh/docs/object-styles](https://emotion.sh/docs/object-styles)
111 | and here [https://emotion.sh/docs/composition](https://emotion.sh/docs/composition)
112 |
113 | We have the `css` function which lets us mix cljs datatypes with javascript ones.
114 |
115 | This is very flexible and allows creating a library of reusable styles which can be combined per component as needed."
116 | (dc/mkdn-pprint-source some-css)
117 | (dc/mkdn-pprint-source merged-css)
118 | (dc/mkdn-pprint-source object-styles))
119 |
120 |
121 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ClojureScript wrapper for emotion
2 |
3 | [](https://clojars.org/dv/cljs-emotion)
4 |
5 | [](https://cljdoc.org/d/dv/cljs-emotion)
6 |
7 |
8 | Documentation and example usage are hosted in devcards here:
9 |
10 | https://dvingo.github.io/cljs-emotion/#!/dv.cljs_emotion.devcards
11 |
12 | https://dvingo.github.io/cljs-emotion
13 |
14 | For info about emotion itself, see:
15 |
16 | https://emotion.sh/docs/introduction
17 |
18 | # To use this library
19 |
20 | Include it in your clojure project dependency list (see most recent version on Clojars).
21 |
22 | And install emotion:
23 |
24 | ```bash
25 | npm install @emotion/react @emotion/styled
26 | ```
27 | The main namespaces are:
28 |
29 | `dv.cljs-emotion` - Plain React components/elements
30 |
31 | With API: `defstyled`, `jsx`, `css`, `global-style`, `theme-provider`, `keyframes`
32 |
33 | `dv.cljs-emotion-reagent` - Reagent components
34 |
35 | With API: `defstyled`, `jsx`, `css`, `global-style`, `theme-provider`, `keyframes`
36 |
37 | `dv.emotion-helix-dom`
38 |
39 | DOM elements for use with helix that support the `:css` prop, with the same API as the components in the `helix.dom` namespace.
40 |
41 | ## Use with plain react components
42 |
43 | ```clojure
44 | (require [dv.cljs-emotion :refer [jsx css defstyled keyframes global-style theme-provider]])
45 |
46 | (defstyled sample1 :div
47 | {:background-color "RebeccaPurple"})
48 |
49 | (react/createElement sample1 nil "hello")
50 | ```
51 |
52 | See the devcards for lots more details
53 |
54 | https://dvingo.github.io/cljs-emotion/#!/dv.cljs_emotion.devcards
55 |
56 | ## Use with helix
57 |
58 | https://github.com/lilactown/helix
59 |
60 | The namespace `dv.emotion-helix-dom` is a drop in replacement for `helix.dom`, and supports all the props that helix
61 | supports in addition to supporting the `:css` property of emotion. Here is an example:
62 |
63 | ```clojure
64 | (:require
65 | [dv.emotion-helix-dom :as d]
66 | [helix.core :refer [$ defnc]]
67 | [dv.cljs-emotion :as em :refer [css defstyled keyframes global-style theme-provider]]
68 |
69 | (def some-css
70 | {:self (em/css #js {:backgroundColor "#aefefe"
71 | :border "2px solid #eee"
72 | :borderRadius "0.5rem"
73 | :padding "1rem"})
74 | :title (em/css {:font-size "1.25rem"
75 | :color "red"}
76 | #js{:border "1px dashed"
77 | "> div > p" {:color "blue"}})
78 | :more (em/css {:border "2px solid"})})
79 |
80 | (def merged-css
81 | (em/css
82 | {:background-color "black"}
83 | (:title some-css)
84 | (:more some-css)))
85 |
86 | (defn object-styles []
87 | (d/div {:css (:self some-css)}
88 | (d/h2 {:css (:title some-css)} "here we have a title")
89 | (d/h2 {:css merged-css} "here we have a title")
90 | (d/p {:css (em/css merged-css {:color "white"})}
91 | "And overwrite inline")))
92 | ```
93 |
94 | You can of course use the `defstyled` API as well if you wish as that outputs React components.
95 |
96 | More examples in the devcards:
97 |
98 | https://dvingo.github.io/cljs-emotion/#!/dv.cljs_emotion.helix_cards
99 |
100 | ## Use with reagent
101 |
102 | There is a separate namespace for reagent support:
103 |
104 | ```clojure
105 | (:require
106 | [dv.cljs-emotion-reagent :refer [jsx css defstyled keyframes global-style theme-provider]])
107 |
108 | (defstyled a-child :div {:color "deepSKYBlue"})
109 |
110 | (defstyled a-parent2 :div
111 | (fn [{:keys [color]}]
112 | {:color "red"
113 | a-child {:color (or color "darkorchid")}
114 | "@media (min-width: 1024px)"
115 | {a-child {:color "black"}}}))
116 |
117 | (def black-bg {:background-color "black"})
118 |
119 | ;; in your component's render function:
120 |
121 | [:div
122 | [a-parent2 "parent should be red"]
123 | (jsx {:css (em/css black-bg {:color "white"})})
124 | [a-parent2 {:color "steelblue"}
125 | [a-child "nested child should be darkorchid"]]]
126 | ```
127 |
128 | The component output by `defstyled` supports passing reagent vectors as children and will convert them to React Elements
129 | for you.
130 |
131 | If you want to use the `:css` property you have to use the `jsx` helper or create a component with `defstyled`
132 |
133 | ```clojure
134 | (em/defstyled div :div {})
135 |
136 | ;; in your render function:
137 | [div {:css [{:background "blue"} {:color "#eff"}]} "Some text"]
138 | ```
139 |
140 | A cool future development would be to plug into the Reagent compiler and add support for `:css` on all elements.
141 |
142 | Pull requests welcome if that interests you!
143 |
144 | More examples in the devcards:
145 |
146 | https://dvingo.github.io/cljs-emotion/#!/dv.cljs_emotion.reagent_cards
147 |
148 | # Development
149 |
150 | ```bash
151 | make dev
152 | ```
153 |
154 | This starts the shadow-cljs server and watch process for the devcards build.
155 |
156 | Open the shadow-cljs devtools webpage and then click on the webserver that hosts the devcards.
157 |
158 | ## Deploy
159 |
160 | Notes for deploy:
161 |
162 | - update pom.xml version
163 | - commit
164 |
165 | Build a deployable jar of this library:
166 |
167 | clojure -X:jar
168 |
169 | Install it locally:
170 |
171 | clojure -M:install
172 |
173 | Deploy it to Clojars -- needs `CLOJARS_USERNAME` and `CLOJARS_PASSWORD` environment variables:
174 |
175 | clojure -M:deploy
176 |
177 | ## Publish github pages
178 |
179 | See `Makefile`
180 |
181 | ```bash
182 | git checkout gh-pages
183 | git merge master
184 | make gh-pages
185 | git add -A
186 | git commit -m
187 | git push github HEAD -f
188 | ```
189 |
--------------------------------------------------------------------------------
/src/main/dv/emotion_helix_dom.cljc:
--------------------------------------------------------------------------------
1 | ;; copied from https://github.com/lilactown/helix/blob/ddfa2de50804f84bb6e8100ed278cbe05cff128e/src/helix/dom.cljc
2 | (ns dv.emotion-helix-dom
3 | #?(:cljs (:require-macros [dv.emotion-helix-dom]))
4 | (:refer-clojure :exclude [map meta time])
5 | (:require
6 | #?(:cljs ["@emotion/react" :as em.react])
7 | [borkdude.dynaload :refer [dynaload]]
8 | [clojure.walk :as walk]
9 | [dv.emotion-css :as dv.em]))
10 |
11 | #?(:cljs (def jsx em.react/jsx))
12 | #?(:cljs (def css dv.em/css))
13 |
14 | (declare
15 | input textarea option select a abbr address area article aside audio b base bdi
16 | bdo big blockquote body br button canvas caption cite code col colgroup data datalist
17 | dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form
18 | h1 h2 h3 h4 h5 h6 head header hr html i iframe img ins kbd keygen label legend li link
19 | main map mark menu menuitem meta meter nav noscript object ol optgroup output p param
20 | picture pre progress q rp rt ruby s samp script section small source span strong style
21 | sub summary sup table tbody td tfoot th thead time title tr track u ul var video wbr
22 | circle clipPath ellipse g line mask path pattern polyline rect svg text defs
23 | linearGradient polygon radialGradient stop tspan)
24 |
25 | (def tags
26 | '[input textarea option select
27 | a
28 | abbr
29 | address
30 | area
31 | article
32 | aside
33 | audio
34 | b
35 | base
36 | bdi
37 | bdo
38 | big
39 | blockquote
40 | body
41 | br
42 | button
43 | canvas
44 | caption
45 | cite
46 | code
47 | col
48 | colgroup
49 | data
50 | datalist
51 | dd
52 | del
53 | details
54 | dfn
55 | dialog
56 | div
57 | dl
58 | dt
59 | em
60 | embed
61 | fieldset
62 | figcaption
63 | figure
64 | footer
65 | form
66 | h1
67 | h2
68 | h3
69 | h4
70 | h5
71 | h6
72 | head
73 | header
74 | hr
75 | html
76 | i
77 | iframe
78 | img
79 | ins
80 | kbd
81 | keygen
82 | label
83 | legend
84 | li
85 | link
86 | main
87 | map
88 | mark
89 | menu
90 | menuitem
91 | meta
92 | meter
93 | nav
94 | noscript
95 | object
96 | ol
97 | optgroup
98 | output
99 | p
100 | param
101 | picture
102 | pre
103 | progress
104 | q
105 | rp
106 | rt
107 | ruby
108 | s
109 | samp
110 | script
111 | section
112 | small
113 | source
114 | span
115 | strong
116 | style
117 | sub
118 | summary
119 | sup
120 | table
121 | tbody
122 | td
123 | tfoot
124 | th
125 | thead
126 | time
127 | title
128 | tr
129 | track
130 | u
131 | ul
132 | var
133 | video
134 | wbr
135 |
136 | ;; svg
137 | circle
138 | clipPath
139 | ellipse
140 | g
141 | line
142 | marker
143 | mask
144 | path
145 | pattern
146 | polyline
147 | rect
148 | svg
149 | text
150 | defs
151 | linearGradient
152 | polygon
153 | radialGradient
154 | stop
155 | tspan])
156 |
157 | (def hx-merge-obj (dynaload 'helix.impl.props/merge-obj))
158 | (def hx-primitive-obj (dynaload 'helix.impl.props/primitive-obj))
159 | (def hx-set-obj (dynaload 'helix.impl.props/set-obj))
160 | (def hx-kw->str (dynaload 'helix.impl.props/kw->str))
161 | (def hx-camel-case (dynaload 'helix.impl.props/camel-case))
162 | (def hx-or-undefined (dynaload 'helix.impl.props/or-undefined))
163 | (def hx-normalize-class (dynaload 'helix.impl.props/normalize-class))
164 | (def hx-dom-style (dynaload 'helix.impl.props/dom-style))
165 |
166 | (defn -dom-props
167 | ([m] #?(:clj (if-let [spread-sym (cond
168 | (contains? m '&) '&
169 | (contains? m :&) :&)]
170 | `(@hx-merge-obj ~(-dom-props (dissoc m spread-sym) (@hx-primitive-obj))
171 | (-dom-props ~(get m spread-sym)))
172 | (-dom-props m (@hx-primitive-obj)))
173 | :cljs (if (map? m)
174 | (-dom-props m (@hx-primitive-obj))
175 | ;; assume JS obj
176 | m)))
177 | ([m o]
178 | (if (seq m)
179 | (recur (rest m)
180 | (let [entry (first m)
181 | k (key entry)
182 | v (val entry)]
183 | (case k
184 | :css (@hx-set-obj o "css" #?(:clj `(dv.em/convert-css ~v) :cljs (dv.em/convert-css v)))
185 | :class (@hx-set-obj o "className" (@hx-normalize-class v))
186 | :for (@hx-set-obj o "htmlFor" v)
187 | :style (@hx-set-obj o "style" (@hx-dom-style v))
188 | :value (@hx-set-obj o "value" #?(:clj `(@hx-or-undefined ~v) :cljs (@hx-or-undefined v)))
189 | (@hx-set-obj o (@hx-camel-case (@hx-kw->str k)) v))))
190 | #?(:clj (list* o)
191 | :cljs o))))
192 |
193 | (defmacro dom-props [m] (-dom-props m))
194 |
195 | (defmacro $d
196 | "Creates a new React DOM element. \"type\" ought to be a string like \"span\",
197 | \"div\",etc.
198 |
199 | When a map of props are passed as the second argument, will statically convert
200 | to a JS object, specially handling things like converting kebab-case props to
201 | camelCase and other transformations.
202 |
203 | Use the special & or :& prop to merge dynamic props in."
204 | [type & args]
205 | (if (map? (first args))
206 | `^js/React.Element (jsx ~type (dom-props ~(first args)) ~@(rest args))
207 | `^js/React.Element (jsx ~type nil ~@args)))
208 |
209 | #?(:clj (defn gen-tag [tag] `(defmacro ~tag [& args#] `($d ~(str '~tag) ~@args#))))
210 | #?(:clj (defmacro gen-tags [] `(do ~@(for [tag tags] (gen-tag tag)))))
211 | #?(:clj (gen-tags))
212 |
--------------------------------------------------------------------------------
/src/dev/dv/cljs_emotion/target_styled.cljs:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion.target-styled
2 | (:require
3 | [devcards.core :as dc :refer (defcard)]
4 | [sablono.core :refer [html]]
5 | ["polished" :as p :refer [darken lighten]]
6 | ["react" :as react]
7 | ["react-dom" :as react-dom]
8 | [dv.cljs-emotion :as em :refer [defstyled keyframes global-style]]))
9 |
10 | (defcard
11 | "These examples show some more emotion API examples.")
12 |
13 | (defstyled prop-fn :div {:padding 20 :outline "1px solid"})
14 |
15 | (defstyled prop-fn2 :div
16 | {:padding 20
17 | :outline "1px solid"
18 | "& > a" {:color "hotpink"}})
19 |
20 | (defcard
21 | "## Using :as
22 | You can change the DOM element at render time by passing `:as`.
23 | ```clojure
24 | (defstyled prop-fn :div {:padding 20 :outline \"1px solid\"})
25 | (prop-fn {:as \"button\"} \"HERE\")\n
26 | ```
27 | "
28 | (prop-fn {:as "button"} "HERE"))
29 |
30 | (defcard prop-fn
31 | "
32 | An & will be replaced by the current styled-component's class name - so you can
33 | use it how you wish - override styles by repeating it \"&&&\" for example, or in this case target
34 | a child element:
35 | ```clojure
36 | (defstyled prop-fn2 :div\n {:padding 20 \n :outline \"1px solid\"\n \"& > a\" {:color \"hotpink\"} } )\n
37 |
38 | (prop-fn2 (html [:a {:href \"localhost:9001\"} \"hi here\"]))\n
39 | ```
40 | "
41 | (prop-fn2 (html [:a {:href "localhost:9001"} "hi here"]))
42 | )
43 |
44 | (defstyled hover-example prop-fn
45 | {":hover,:focus" {:background "hotpink"}})
46 |
47 | (defcard
48 | "You can combine multiple selectors using the CSS comma operator.
49 |
50 | ```clojure
51 | (defstyled hover-example prop-fn\n {\":hover,:focus\" {:background \"hotpink\"}})\n
52 | ```"
53 | (hover-example "HELLLO")
54 | )
55 |
56 | (defstyled a-child :div
57 | {:color "deepSKYBlue"})
58 |
59 | (defstyled a-parent :div
60 | {:color "red"
61 | a-child {:color "darkorchid"}})
62 |
63 | (defn nested-child-ex []
64 | (html
65 | [:div
66 | (a-child "child should be deepSkyBlue")
67 | (a-parent "parent should be red")
68 | (a-parent
69 | (a-child "nested child should be darkorchid"))]))
70 |
71 | (dc/defcard-doc
72 | "# Target another defstyled component
73 | If you use a component created with `defstyled` in the key position of a styles map
74 | the generated CSS uses a class selector in its place that is a hash of its fully qualified
75 | symbol name.
76 |
77 | This works inside media queries and functions (see the next example)."
78 | (dc/mkdn-pprint-source a-child)
79 | (dc/mkdn-pprint-source a-parent)
80 | (dc/mkdn-pprint-source nested-child-ex))
81 |
82 | (defn use-current-width []
83 | (let [use-width (react/useState js/innerWidth)
84 | width (aget use-width 0)
85 | set-width (aget use-width 1)]
86 | (react/useEffect
87 | (fn []
88 | (let [on-resize
89 | (fn [timeout-id]
90 | (js/clearTimeout timeout-id)
91 | (js/setTimeout (fn [] (set-width js/innerWidth)) 150))]
92 | (js/addEventListener "resize" on-resize)
93 | (fn [] (js/removeEventListener "resize" on-resize)))))
94 | width))
95 |
96 | (defstyled a-parent2 :div
97 | (fn [{:keys [color]}]
98 | {:color "red"
99 | a-child {:color (or color "darkorchid")}
100 | "@media (min-width: 1024px)"
101 | {a-child {:color "black"}}}))
102 |
103 | (defn my-component []
104 | (let [width (use-current-width)]
105 | (html
106 | [:div
107 | (a-parent2 {:color "blue"} "parent should be red")
108 | (a-child "child should be deepSkyBlue")
109 | (a-parent2 {:color "steelblue"}
110 | (a-child
111 | (str "nested child should be "
112 | (if (>= width 1024) "black" "darkorchid"))))])))
113 |
114 | (dc/defcard-doc
115 | "# Target another defstyled component in nested position
116 | Here we change the child element conditionally based on a media query -
117 | resize the page over and under 1024 pixels to see the effect."
118 | (dc/mkdn-pprint-source a-parent2)
119 | (dc/mkdn-pprint-source my-component))
120 |
121 | (defcard
122 | (dc/dom-node
123 | (fn [data-atom node]
124 | (react-dom/render (react/createElement my-component) node))))
125 |
126 |
127 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
128 | ;; styled component in selector string
129 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
130 |
131 | (defstyled a-parent3 :div
132 | (fn [_]
133 | {:color "red"
134 | (str a-child " + " a-child)
135 | {:color "#eee"
136 | :background-color "hsl(0, 0%, 48%)"
137 | :padding "1em"
138 | :border-top "1px solid"}
139 | a-child {:color "darkorchid"
140 | :background-color "paleVIOLETRed"}
141 | "@media (min-width: 1024px)"
142 | {a-child {:color "black"}
143 | (str a-child " + " a-child)
144 | {:background-color (lighten 0.2 "hsl(0, 0%, 48%)")}}}))
145 |
146 | (dc/defcard-doc
147 | "# Target another defstyled component in a combinator selector"
148 | (dc/mkdn-pprint-source a-parent3)
149 | "Here we are using a styled component as part of a larger CSS Selector expression.
150 | This works by implementing `toString` for styled components, returning their class selector."
151 | "
152 | ```clojure
153 | (a-parent3
154 | \"HELLLO\"
155 | (a-child \"first\")
156 | (a-child \"second\")
157 | (a-child \"third\")
158 | (a-child \"fourth\")
159 | (a-child \"fifth\"))
160 | ```")
161 |
162 | (defcard
163 | (a-parent3
164 | "HELLLO"
165 | (a-child "first")
166 | (a-child "second")
167 | (a-child "third")
168 | (a-child "fourth")
169 | (a-child "fifth")))
170 |
171 | (defstyled inside-text :span
172 | {:color "hsl(200, 40, 69)"})
173 |
174 | (defn anon-target []
175 | (em/jsx :div
176 | {:css
177 | {:color "skyblue"
178 | inside-text {:color "grey"}} }
179 | (html
180 | [:div
181 | [:p "outside text"]
182 | (inside-text "inside text")])))
183 |
--------------------------------------------------------------------------------
/src/dev/dv/cljs_emotion/reagent_cards.cljs:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion.reagent-cards
2 | (:require
3 | ["polished" :as p :refer [darken lighten]]
4 | [devcards.core :as dc :refer (defcard defcard-rg)]
5 | [reagent.core :as r]
6 | [dv.cljs-emotion-reagent :refer [jsx css defstyled keyframes global-style theme-provider]]))
7 |
8 | (defcard
9 | "These examples demonstrate reagent use.")
10 |
11 | (defstyled basic :div
12 | {:background "palevioletred"})
13 |
14 | (defcard basic-card
15 | "
16 | ```clojure
17 | (defstyled basic :div\n {:background \"palevioletred\"})\n
18 | [basic \"Simple usage example\"]) \n
19 | ```
20 | "
21 | (dc/reagent [basic "Simple usage example"]))
22 |
23 | (defcard basic-card2
24 | "
25 | ```clojure
26 | (defstyled basic :div
27 | {:background \"palevioletred\"})
28 |
29 | [basic [:h2 \"Child \"]])
30 |
31 | ```
32 | "
33 | (dc/reagent [basic [:h2 "Child "]]))
34 |
35 | (defcard basic-card3
36 | "With props
37 |
38 | ```clojure
39 | [basic {:onClick #(js/console.log \"CLICK\")} [:h2 \"Child \"]]
40 | ```
41 | "
42 | (dc/reagent [basic {:onClick #(js/console.log "CLICK")} [:h2 "Child "]]))
43 | (defcard basic-card4
44 | "With props and multiple children
45 |
46 | ```clojure
47 | [basic {:onClick #(js/console.log \"CLICK\")}\n [:h2 \"Child \"]\n [:h3 \"another child\"]]
48 | ```
49 | "
50 | (dc/reagent [basic {:onClick #(js/console.log "CLICK")}
51 | [:h2 "Child "]
52 | [:h3 "another child"]]))
53 |
54 | (defstyled test-theme :div
55 | (fn [{:keys [theme]}]
56 | {:background (or (:bg theme) "blue")}))
57 |
58 | (defcard theme-provider-card
59 | "Theme use is straight forward.
60 |
61 | It wraps emotion's ThemeProvider calling clj->js first on the props.
62 | Wrap your application with `theme-provider` passing it
63 | a map or JS object of theme data.
64 |
65 | ```clojure
66 | (defstyled test-theme :div\n (fn [{:keys [theme]}]\n {:background (or (:bg theme) \"blue\")}))
67 |
68 | [:div\n (theme-provider {:theme {:bg \"yellow\"}}\n [test-theme \"Hello there theme\"])\n [test-theme \"no theme\"]]
69 | ```
70 | "
71 | (dc/reagent
72 | [:div
73 | (theme-provider {:theme {:bg "yellow"}}
74 | [test-theme "Hello there theme"])
75 | [test-theme "no theme"]]))
76 |
77 | (defstyled test-fn :div
78 | (fn [{:keys [text-case background-color] :as props}]
79 | (js/console.log "in defstyled fn props: " props)
80 | {:text-transform text-case
81 | :background-color background-color}))
82 |
83 | (defcard test-props-card
84 | "Testing the camel case logic"
85 | (dc/reagent
86 | [test-fn {:text-case "uppercase"
87 | :background-color "chartreuse"} "hi"]))
88 |
89 | (defstyled a-child :div
90 | {:color "deepSKYBlue"})
91 |
92 | (defstyled a-parent :div
93 | {:color "red"
94 | a-child {:color "darkorchid"}})
95 |
96 | (defcard-rg a-thing
97 | "# Target another defstyled component
98 | If you use a `defstyled` in key position in the styles map a CSS classname is used in its place that is a hash of its fully qualified
99 | symbol name.
100 |
101 | This works inside media queries and functions (see the next example).
102 |
103 | ```clojure
104 | (defstyled a-child :div
105 | {:color \"deepSKYBlue\"})
106 |
107 | (defstyled a-parent :div
108 | {:color \"red\"
109 | a-child {:color \"darkorchid\"}})
110 |
111 | [:div
112 | [a-child \"child should be deepSkyBlue\"]
113 | [a-parent \"parent should be red\"]
114 | [a-parent
115 | [a-child \"nested child should be darkorchid\"]]]
116 | ```
117 | "
118 | [:div
119 | [a-child "child should be deepSkyBlue"]
120 | [a-parent "parent should be red"]
121 | [a-parent
122 | [a-child "nested child should be darkorchid"]]])
123 |
124 | (defstyled a-parent2 :div
125 | (fn [{:keys [color]}]
126 | {:color "red"
127 | a-child {:color (or color "darkorchid")}
128 | "@media (min-width: 1024px)"
129 | {a-child {:color "black"}}}))
130 |
131 | (defcard-rg a-thing2
132 | "# Target another defstyled component (continued)
133 | ```clojure
134 | (defstyled a-child :div
135 | {:color \"deepSKYBlue\"})
136 |
137 | (defstyled a-parent2 :div
138 | (fn [{:keys [color]}]
139 | {:color \"red\"
140 | a-child {:color (or color \"darkorchid\")}
141 | \"@media (min-width: 1024px)\"
142 | {a-child {:color \"black\"}}}))
143 |
144 | [:div
145 | [a-child \"child should be deepSkyBlue\"]
146 | [a-parent \"parent should be red\"]
147 | [a-parent
148 | [a-child \"nested child should be darkorchid, or black if window is >= 1024px\"]]]
149 | ```
150 | "
151 | [:div
152 | [a-child "child should be deepSkyBlue"]
153 | [a-parent2 "parent should be red"]
154 | [a-parent2 {:color "steelblue"}
155 | [a-child "nested child should be darkorchid"]]])
156 |
157 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
158 | ;; styled component in selector string
159 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
160 |
161 | (defstyled a-parent3 :div
162 | (fn [_]
163 | {:color "red"
164 | (str a-child " + " a-child)
165 | {:color "#eee"
166 | :background-color "hsl(0, 0%, 48%)"
167 | :padding "1em"
168 | :border-top "1px solid"}
169 | a-child {:color "darkorchid"
170 | :background-color "paleVIOLETRed"}
171 | "@media (min-width: 1024px)"
172 | {a-child {:color "black"}
173 | (str a-child " + " a-child)
174 | {:background-color (lighten 0.2 "hsl(0, 0%, 48%)")}}}))
175 |
176 | (dc/defcard-doc
177 | "# Target another defstyled component in a combinator selector"
178 | (dc/mkdn-pprint-source a-parent3)
179 | "Here we are using a styled component as part of a larger CSS Selector expression.
180 | This works by implementing `toString` for styled components, returning their class selector."
181 | "
182 | ```clojure
183 | [:div
184 | [a-parent3
185 | \"HELLLO\"
186 | [a-child \"first\"]
187 | [a-child \"second\"]
188 | [a-child \"third\"]
189 | [a-child \"fourth\"]
190 | [a-child \"fifth\"]]]
191 | ```")
192 |
193 | (defcard
194 | (dc/reagent
195 | [:div
196 | [a-parent3
197 | "HELLLO"
198 | [a-child "first"]
199 | [a-child "second"]
200 | [a-child "third"]
201 | [a-child "fourth"]
202 | [a-child "fifth"]]]))
203 |
204 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
205 | ;; Animation
206 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
207 |
208 | (defstyled with-anim :div
209 | (fn animation-styles [{:keys [time]}]
210 | {:animation (str
211 | (keyframes {:from {:background-color "transparent"}
212 | :to {:background-color "grey"}})
213 | " " time "s ease-in-out infinite")}))
214 |
215 | (defn animation-card [a _]
216 | [:div
217 | [:p "animation time: " (:time @a) " seconds"]
218 | [:button {:on-click #(swap! a update :time inc)} "inc"]
219 | [:button {:on-click #(swap! a update :time dec)} "dec"]
220 | (with-anim {:time (:time @a)} "Some text here")])
221 |
222 | (dc/defcard-doc
223 | "Keyframe animations are supported - this is built into emotion. "
224 | (dc/mkdn-pprint-source with-anim)
225 | (dc/mkdn-pprint-source animation-card))
226 |
227 | (def animation-state (r/atom {:time 1}))
228 |
229 | (defcard-rg keyframes
230 | [animation-card animation-state]
231 | animation-state)
232 |
233 | (defn anon-styles []
234 | (r/as-element
235 | [:div
236 | (jsx :div {:css {:background "lightgrey"}}
237 | [:p "Some text on a lightgrey background."])
238 | (theme-provider {:theme {:bg "salmon"}}
239 | (jsx :div {:css
240 | [{:color "white"}
241 | #js{:border "1px solid"}
242 | (fn [t]
243 | (.log js/console "THEME : " t)
244 | {:background (or (:bg t) "lightgrey")})]}
245 | [:p "Some text on a salmon background."]))]))
246 |
247 | (dc/defcard-doc
248 | "# Anonymous inline styles support.
249 | You can use the `jsx` helper to style a react element inline without needing to create a component."
250 | (dc/mkdn-pprint-source anon-styles))
251 |
252 | (defcard (anon-styles))
253 |
254 |
255 | ;; Test for nested breakpoints in callback functions.
256 | ;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
257 |
258 | (defstyled full-height :div
259 | {:height "100%"
260 | :background "pink"}
261 | (fn [props]
262 | (.log js/console "PROPS: " props)
263 | (:styles props)))
264 |
265 | (defn nested-fn []
266 | [:div
267 | [full-height
268 | {:styles {:width "70%" "@media (min-width: 700px)" {:background "skyBLUE" :width "100%"}}}
269 | [:p "hello body"]]])
270 |
271 | (defcard "Cljs properties should be passed to callbacks untouched." (dc/mkdn-pprint-source full-height))
272 |
273 | ;(defcard breakpoint-debug (dc/mkdn-pprint-source nested-fn))
274 |
275 | ;(defcard-rg (nested-fn))
276 |
277 | ;(defcard-rg
278 | ; "Testing 1-arity test."
279 | ; [:div
280 | ; [full-height
281 | ; {:styles {":before"
282 | ; {:content "'HELLO'"
283 | ; "@media (min-width: 700px)" {:content "'LARGE SCREEN'"}}
284 | ; :width "70%" "@media (min-width: 700px)" {:background "skyBLUE" :width "100%"}}}]])
285 |
--------------------------------------------------------------------------------
/src/main/dv/emotion_css.cljc:
--------------------------------------------------------------------------------
1 | (ns dv.emotion-css
2 | (:require
3 | #?@(:cljs [["react" :as react]
4 | ["@emotion/hash" :as emotion-hash*]
5 | ["@emotion/react" :as styled-core :refer [Global ThemeProvider]]
6 | [goog.object :as g]])
7 | [clojure.string :as str]
8 | [clojure.walk :as walk])
9 | #?(:cljs (:require-macros [dv.cljs-emotion :refer [defstyled]])))
10 |
11 | ;; Support plain cljs compiler and shadow.
12 | #?(:cljs (def emotion-hash (g/get emotion-hash* "default")))
13 | #?(:cljs (def jsx* styled-core/jsx))
14 |
15 | ;; Used to prevent generated code from needing to require goog.object
16 | (defn obj-set [o k v]
17 | #?(:cljs (g/set o k v)
18 | :clj nil))
19 |
20 | (defn obj-get [o k]
21 | #?(:cljs (g/get o k)
22 | :clj nil))
23 |
24 | ;; from fulcro
25 | #?(:cljs
26 | (defn force-children
27 | "Utility function that will force a lazy sequence of children (recursively) into realized
28 | vectors (React cannot deal with lazy seqs in production mode)"
29 | [x]
30 | (if (seq? x)
31 | (to-array (mapv force-children x))
32 | x)))
33 |
34 | #?(:cljs
35 | (defn kebab->camel
36 | [prop]
37 | (if (str/starts-with? prop ".")
38 | prop
39 |
40 | (if (str/includes? prop "-")
41 | (let [words (->> (re-seq #"[a-zA-Z]+" prop)
42 | (mapv str/capitalize))]
43 | (-> words
44 | (update 0 str/lower-case)
45 | str/join))
46 | prop))))
47 |
48 | ;; todo use the caching strategy seen in reagent.impl.template
49 |
50 | #?(:cljs
51 | (defn camelize-keys
52 | "Also replaces styled components with their css classname is key position."
53 | [style-map]
54 | (walk/postwalk
55 | (fn in-walk [v]
56 | (cond
57 | (keyword? v)
58 | (-> v name kebab->camel)
59 |
60 | (and (meta v) (contains? (meta v) ::hashed-name))
61 | (str "." (-> v meta ::hashed-name))
62 |
63 | :else v))
64 | style-map)))
65 |
66 | #?(:cljs
67 | (defn keyframes [anim-map]
68 | (styled-core/keyframes (clj->js (camelize-keys anim-map)))))
69 |
70 | (def cljs-props-key "dv.cljs-emotion/props")
71 |
72 | #?(:cljs
73 | (defn wrap-call-style-fn [anon-styles?]
74 | (fn [x]
75 | (cond
76 |
77 | ;; Another emotion styled component created with this lib.
78 | (and (meta x) (contains? (meta x) ::hashed-name))
79 | (str "." (-> x meta ::hashed-name))
80 |
81 | (fn? x)
82 | (fn [arg]
83 | ;; arg# is js props passed at runtime, we ship it back and forth js -> cljs -> js
84 |
85 | ;; js->clj is resulting in an infinite recur when children contains another styled component, so we remove it.
86 | (js-delete arg "children")
87 |
88 | (if anon-styles?
89 | ;; with anonymous styles there can be no props - so the theme is passed as the only argument
90 | (clj->js (camelize-keys (x (js->clj arg :keywordize-keys true))))
91 | (let [cljs-args (assoc (obj-get arg cljs-props-key)
92 | :theme (js->clj (obj-get arg "theme") :keywordize-keys true))]
93 | ;; invoke the user-supplied function which returns style data - convert whatever they return to js data structures.
94 | (clj->js (camelize-keys (x cljs-args))))))
95 |
96 | ;; maps come up in value position for nested selectors
97 | (map? x)
98 | (camelize-keys x)
99 |
100 | :else x))))
101 |
102 | #?(:cljs (goog-define ADD_CLASSNAMES "INITIAL"))
103 |
104 | #?(:cljs
105 | (defn add-class-names? []
106 | (if (boolean? ADD_CLASSNAMES)
107 | ADD_CLASSNAMES
108 | goog.DEBUG)))
109 |
110 | #?(:cljs
111 | (defn add-class-name [props class-name]
112 | (if (object? props)
113 | (doto props
114 | (goog.object/set "className"
115 | (->> [class-name (goog.object/get props "className")]
116 | (str/join " ")
117 | (str/trim))))
118 | (update props :className #(if (nil? %) class-name (str class-name " " %))))))
119 |
120 | #?(:cljs (defn hashit [string] (str "dvcss-" (emotion-hash string))))
121 |
122 | #?(:cljs
123 | (defn set-class-name [props class-name]
124 | (if class-name
125 | (let [hashed-name (hashit class-name)
126 | props (add-class-name props hashed-name)]
127 | (if (add-class-names?)
128 | (add-class-name props class-name)
129 | props))
130 | props)))
131 |
132 | #?(:cljs
133 | (defn map->obj [m]
134 | (reduce-kv (fn [o k v]
135 | ;; convert keywords to string only in key position
136 | (let [new-k (cond-> k (implements? INamed k) name)
137 | new-v (cond-> v (map? v) map->obj)]
138 | (doto o (obj-set new-k new-v))))
139 | #js{} m)))
140 |
141 | #?(:cljs
142 | (defn make-js-props [props class-name]
143 | (if (object? props)
144 | props
145 | (let [clj-props (set-class-name props class-name)
146 | js-props (map->obj clj-props)]
147 | (doto js-props (obj-set cljs-props-key clj-props))))))
148 |
149 | #?(:cljs
150 | (defn react-factory [el class-name]
151 | (fn
152 | ([]
153 | (jsx* el (clj->js (set-class-name {} class-name))))
154 | ([props]
155 | (try
156 | (cond
157 | (or (react/isValidElement props) (string? props))
158 | (jsx* el (set-class-name #js{} class-name) props)
159 |
160 | (map? props)
161 | ;; Do not use clj->js in order to preserve clojure data types like keywords that would not
162 | ;; survive a round-trip clj->js js->clj
163 | (jsx* el (make-js-props props class-name))
164 |
165 | (object? props)
166 | (jsx* el (set-class-name props class-name))
167 |
168 | (or (array? props) (coll? props))
169 | (jsx* el (set-class-name #js{} class-name) (force-children props))
170 |
171 | :else
172 | (jsx* el (set-class-name #js{} class-name)))
173 |
174 | (catch js/Object e
175 | (js/console.error "Error invoking an emotion styled component: " e))))
176 |
177 | ([props & children]
178 | ;; if props are a mapping type and not a react child
179 | (if (or (and (object? props) (not (react/isValidElement props))) (map? props))
180 | (let [js-props (make-js-props props class-name)]
181 | (if (seq children)
182 | (apply jsx* el js-props (force-children children))
183 | (jsx* el js-props)))
184 | (apply jsx* el (set-class-name #js{} class-name) (force-children (list* props children))))))))
185 |
186 | #?(:clj
187 | (defn get-type
188 | [styled-arg tag-name]
189 | (cond
190 | ;; if literals, don't need to determine type at runtime
191 | ;; a dom element like :div, same as styled.div``
192 | (string? tag-name) `(obj-get ~styled-arg ~tag-name)
193 | (keyword? tag-name) `(obj-get ~styled-arg ~(name tag-name))
194 | :else
195 | `(cond
196 | (string? ~tag-name)
197 | (obj-get ~styled-arg ~tag-name)
198 |
199 | (keyword? ~tag-name)
200 | (obj-get ~styled-arg ~(name tag-name))
201 |
202 | ;; Another styled component
203 | (::styled (meta ~tag-name))
204 | (.call ~styled-arg ~styled-arg (::styled (meta ~tag-name)))
205 |
206 | ;; A React component
207 | :else
208 | (.call ~styled-arg ~styled-arg ~tag-name)))))
209 |
210 | #?(:clj
211 | (defn get-cls-name
212 | [namespace-name print-style component-sym]
213 | (case print-style
214 | :full (str namespace-name "/" component-sym)
215 | :short (str component-sym)
216 | :nil nil)))
217 |
218 | #?(:clj (def default-classname-style :full))
219 |
220 | #?(:clj
221 | (defn get-cls-name-from-meta
222 | "Returns string or nil for the classname"
223 | [namespace-name component-sym]
224 | (if (contains? (meta component-sym) :styled/classname)
225 | (let [print-config (:styled/classname (meta component-sym))]
226 | (if (#{:full :short :nil} print-config)
227 | (get-cls-name namespace-name print-config component-sym)
228 | (throw (Exception. (str "Unknown option for class-name style in metadata passed to component: " component-sym)))))
229 | (get-cls-name namespace-name default-classname-style component-sym))))
230 |
231 | ;; emotion doesn't allow functions in nested position, only
232 | ;; objects and arrays of objects
233 | ;; but they do allow one function as a child
234 | ;; you can always wrap the call in a fn if you want dynamism like below
235 | ;https://github.com/emotion-js/emotion/blob/188dc0e785cfc9b10b3f9a6ead62b56ddd38e039/packages/core/src/global.js#L16
236 |
237 | #?(:cljs
238 | (defn global-style
239 | "Takes a cljs vector or hashmap of styles and converts to JS types before calling emotion's Global function
240 | from the @emotion/react package.
241 | Inserts styles into the DOM that target global elements and classes.
242 |
243 | https://emotion.sh/docs/@emotion/react"
244 | [props]
245 | (react/createElement Global #js{:styles (clj->js (camelize-keys props))})))
246 |
247 | ;; can use like so:
248 | (comment
249 | (global-style {:body {:background "#cce" "@media (min-width:700px)" {:background "white"}}})
250 | (global-style
251 | [(clj->js {:body {:font-family "serif"}}) {:body {:border "2px solid yellOW"}} {:body {:background-color "#ecccee"}}])
252 |
253 | ;; to adapt based on props, wrap in a fn:
254 | (defn my-globals [props]
255 | (global-style
256 | {:body {:background-color "red"}})))
257 |
258 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
259 | ;; Theme support
260 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
261 |
262 | #?(:cljs
263 | (defn theme-provider
264 | "Takes a hashmap of a style theme and react children to render with that theme in the React Context
265 | using emotion's ThemeProvider."
266 | [props & children]
267 | (when-not (contains? props :theme) (throw (js/Error. "You must pass a :theme to the theme-provider.")))
268 | (if (:theme props)
269 | (apply react/createElement ThemeProvider
270 | (clj->js props)
271 | (force-children children))
272 | (react/Children.map children (fn [c i] (react/cloneElement c #js{:key i}))))))
273 |
274 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
275 | ;; CSS prop support
276 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
277 |
278 | #?(:clj
279 | (defn ^:private css-body [props]
280 | `(do
281 | ;(assert (contains? ~props :css) "Props must contain :css key")
282 | (cljs.core/clj->js
283 | (assoc ~props :css
284 | (walk/postwalk
285 | ;; todo here you can do props validation also
286 | ;; should not allow anything that's not a symbol, map, vector, js-obj, js-array, fn
287 | (wrap-call-style-fn true)
288 | (:css ~props)))))))
289 | #?(:clj
290 | (defmacro jsx
291 | ([el props]
292 | (let [el (cond-> el (keyword? el) name)
293 | css-props (css-body props)]
294 | `(jsx* ~el ~css-props)))
295 |
296 | ([el props & children]
297 | (let [el (cond-> el (keyword? el) name)
298 | css-props (css-body props)]
299 | `(jsx* ~el ~css-props ~@children)))))
300 |
301 | #?(:cljs
302 | (defn convert-css
303 | "Takes a hashmap or vector of styles and converts to JS types, will pass any functions cljs data structures."
304 | [css]
305 | (clj->js (walk/postwalk (wrap-call-style-fn true) css))))
306 |
307 | #?(:cljs
308 | (defn css [css]
309 | (styled-core/css
310 | (if (object? css)
311 | css
312 | (clj->js (walk/postwalk (wrap-call-style-fn true) css))))))
313 |
--------------------------------------------------------------------------------
/src/main/dv/cljs_emotion_reagent.cljc:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion-reagent
2 | (:require
3 | #?@(:cljs [["react" :as react]
4 | ["@emotion/hash" :as emotion-hash*]
5 | ["@emotion/styled" :as styled*]
6 | ["@emotion/react" :as styled-core :refer [Global ThemeProvider]]])
7 | #?@(:cljs
8 | [[borkdude.dynaload :refer [dynaload]]
9 | [goog.object :as g]])
10 | [camel-snake-kebab.core :as csk]
11 | [clojure.walk :as walk]
12 | [clojure.string :as str])
13 | #?(:cljs (:require-macros [dv.cljs-emotion-reagent :refer [defstyled]])))
14 |
15 | ;; This is so consumers of this library don't need to pull in reagent.
16 | #?(:cljs (def r-as-element (dynaload 'reagent.core/as-element)))
17 | #?(:cljs (def r-convert-prop-value (dynaload 'reagent.impl.template/convert-prop-value)))
18 | #?(:cljs (def r-class-names (dynaload 'reagent.impl.util/class-names)))
19 | ;; Support plain cljs compiler and shadow.
20 | #?(:cljs (def emotion-hash (g/get emotion-hash* "default")))
21 | #?(:cljs (def styled (g/get styled* "default")))
22 | #?(:cljs (def jsx* styled-core/jsx))
23 |
24 | ;; Used to prevent generated code from needing to require goog.object
25 | (defn obj-set [o k v]
26 | #?(:cljs (g/set o k v)
27 | :clj nil))
28 |
29 | (defn obj-get [o k]
30 | #?(:cljs (g/get o k)
31 | :clj nil))
32 |
33 | #?(:cljs
34 | (defn relement?
35 | "Is it a reagent vector? (or a best effort guess at least.)"
36 | [el]
37 | (and
38 | (vector? el)
39 | (let [item (first el)]
40 | (or
41 | (keyword? item)
42 | (symbol? item)
43 | (fn? item))))))
44 |
45 | ;; from fulcro
46 | #?(:cljs
47 | (defn force-children
48 | "Utility function that will force a lazy sequence of children (recursively) into realized
49 | vectors (React cannot deal with lazy seqs in production mode)"
50 | [x]
51 | (if (seq? x)
52 | (to-array (mapv force-children x))
53 | (if (relement? x)
54 | (@r-as-element x)
55 | x))))
56 |
57 | #?(:cljs
58 | (defn kebab->camel [prop]
59 | (if (str/starts-with? prop ".")
60 | prop
61 |
62 | (if (str/includes? prop "-")
63 | (let [words (->> (re-seq #"[a-zA-Z]+" prop)
64 | (mapv str/capitalize))]
65 | (-> words
66 | (update 0 str/lower-case)
67 | str/join))
68 | prop))))
69 |
70 | #?(:cljs
71 | (defn camelize-keys
72 | "Takes a clj map and returns that map modified.
73 | Postwalk replaces kebab keywords with camel case version as is expected by React.
74 |
75 | Also replaces styled components with their CSS classname when they appear in key position."
76 | [style-map]
77 | (walk/postwalk
78 | (fn [v]
79 | (cond
80 | (keyword? v)
81 | (-> v name kebab->camel)
82 |
83 | (and (meta v) (contains? (meta v) ::hashed-name))
84 | (str "." (-> v meta ::hashed-name))
85 |
86 | :else v))
87 | style-map)))
88 |
89 | #?(:cljs
90 | (defn keyframes [anim-map]
91 | (styled-core/keyframes (clj->js (camelize-keys anim-map)))))
92 |
93 | (def cljs-props-key "dv.cljs-emotion/props")
94 |
95 | #?(:cljs
96 | (defn wrap-call-style-fn [anon-styles?]
97 | (fn [x]
98 | (cond
99 |
100 | ;; Another emotion styled component created with this lib.
101 | (and (meta x) (contains? (meta x) ::hashed-name))
102 | (str "." (-> x meta ::hashed-name))
103 |
104 | (fn? x)
105 | (fn [arg]
106 | ;; arg# is js props passed at runtime, we ship it back and forth js -> cljs -> js
107 |
108 | ;; js->clj is resulting in an infinite recur when children contains another styled component, so we remove it.
109 | (js-delete arg "children")
110 |
111 | (if anon-styles?
112 | ;; with anonymous styles there can be no props - so the theme is passed as the only argument
113 | (clj->js (camelize-keys (x (js->clj arg :keywordize-keys true))))
114 | (let [cljs-args (assoc (obj-get arg cljs-props-key)
115 | :theme (js->clj (obj-get arg "theme") :keywordize-keys true))]
116 | ;; invoke the user-supplied function which returns style data - convert whatever they return to js data structures.
117 | (clj->js (camelize-keys (x cljs-args))))))
118 |
119 | ;; maps come up in value position for nested selectors
120 | (map? x)
121 | (camelize-keys x)
122 |
123 | :else x))))
124 |
125 | #?(:cljs (goog-define ADD_CLASSNAMES "INITIAL"))
126 |
127 | #?(:cljs
128 | (defn add-class-names? []
129 | (if (boolean? ADD_CLASSNAMES)
130 | ADD_CLASSNAMES
131 | goog.DEBUG)))
132 |
133 | #?(:cljs
134 | (defn add-class-name [props class-name]
135 | (if (object? props)
136 | (doto props
137 | (obj-set "className"
138 | (->> [class-name (obj-get props "className")]
139 | (str/join " ")
140 | (str/trim))))
141 | (update props :className #(if (nil? %) class-name (str class-name " " %))))))
142 |
143 | #?(:cljs (defn hashit [string] (str "dvcss-" (emotion-hash string))))
144 |
145 | #?(:cljs
146 | (defn set-class-name [props class-name]
147 | (if class-name
148 | (let [hashed-name (hashit class-name)
149 | props (add-class-name props hashed-name)]
150 | (if (add-class-names?)
151 | (add-class-name props class-name)
152 | props))
153 | props)))
154 |
155 | #?(:cljs
156 | (defn map->obj [m]
157 | (reduce-kv (fn [o k v]
158 | ;; convert keywords to string only in key position
159 | (let [new-k (cond-> k (implements? INamed k) name)
160 | new-v (cond-> v (map? v) map->obj)]
161 | (doto o (obj-set new-k new-v))))
162 | #js{} m)))
163 |
164 | #?(:cljs
165 | (defn make-js-props
166 | "Allows using kebab-case prop names."
167 | [props class-name]
168 | (if (object? props)
169 | props
170 | (let [clss (:class props)
171 | props (cond-> props clss (assoc :class (@r-class-names clss)))
172 | ;; converts properties for JS call as expected by react class->className, on-click->onClick etc.
173 | clj-props (set-class-name props class-name)
174 | js-props (@r-convert-prop-value props)
175 | js-props (set-class-name js-props class-name)]
176 | (doto js-props (obj-set cljs-props-key clj-props))))))
177 |
178 | #?(:cljs
179 | (defn react-factory
180 | [el class-name]
181 | (fn
182 | ([]
183 | (jsx* el (clj->js (set-class-name {} class-name))))
184 | ([props]
185 | (try
186 | (cond
187 | (or (react/isValidElement props) (string? props))
188 | (jsx* el (set-class-name #js{} class-name) props)
189 |
190 | (map? props)
191 | (let [props (make-js-props props class-name)]
192 | (jsx* el props))
193 |
194 | (object? props)
195 | (jsx* el (set-class-name props class-name))
196 |
197 | (relement? props)
198 | (jsx* el (set-class-name #js{} class-name) (@r-as-element props))
199 |
200 | (seq? props)
201 | (jsx* el (set-class-name #js{} class-name) (force-children props))
202 |
203 | (array? props)
204 | (jsx* el (set-class-name #js{} class-name) props)
205 |
206 | :else
207 | (jsx* el (set-class-name #js{} class-name)))
208 |
209 | (catch js/Object e
210 | (js/console.error "Error invoking an emotion styled component: " e))))
211 |
212 | ([props & children]
213 | ;; if props are a mapping type and not a react child
214 | (if (or (and (object? props) (not (react/isValidElement props))) (map? props))
215 | (let [js-props (make-js-props props class-name)]
216 | (if (seq children)
217 | (apply jsx* el js-props (force-children children))
218 | (jsx* el js-props)))
219 | (apply jsx* el (set-class-name #js{} class-name) (force-children (list* props children))))))))
220 |
221 | #?(:clj
222 | (defn get-type
223 | [styled-arg tag-name]
224 | (cond
225 | ;; if literals, don't need to determine type at runtime
226 | (string? tag-name) `(obj-get ~styled-arg ~tag-name)
227 | (keyword? tag-name) `(obj-get ~styled-arg ~(name tag-name))
228 | :else
229 | `(cond
230 | ;; a dom element like :div, same as styled.div``
231 | (string? ~tag-name)
232 | (obj-get ~styled-arg ~tag-name)
233 |
234 | (keyword? ~tag-name)
235 | (obj-get ~styled-arg ~(name tag-name))
236 |
237 | ;; Another styled component
238 | (::styled (meta ~tag-name))
239 | (.call ~styled-arg ~styled-arg (::styled (meta ~tag-name)))
240 |
241 | ;; A React component
242 | :else
243 | (.call ~styled-arg ~styled-arg ~tag-name)))))
244 |
245 | #?(:clj
246 | (defn get-cls-name
247 | [namespace-name print-style component-sym]
248 | (case print-style
249 | :full (str namespace-name "/" component-sym)
250 | :short (str component-sym)
251 | :nil nil)))
252 |
253 | #?(:clj (def default-classname-style :full))
254 |
255 | #?(:clj
256 | (defn get-cls-name-from-meta
257 | "Returns string or nil for the classname"
258 | [namespace-name component-sym]
259 | (if (contains? (meta component-sym) :styled/classname)
260 | (let [print-config (:styled/classname (meta component-sym))]
261 | (if (#{:full :short :nil} print-config)
262 | (get-cls-name namespace-name print-config component-sym)
263 | (throw (Exception. (str "Unknown option for class-name style in metadata passed to component: " component-sym)))))
264 | (get-cls-name namespace-name default-classname-style component-sym))))
265 |
266 | #?(:clj
267 | (defmacro defstyled
268 | ([component-name el & children]
269 | (let [component-type (gensym "component-type")
270 | clss (gensym "clss")
271 | class-name (gensym "className")
272 | full-class-name (gensym "fullClassName")
273 | children* (gensym "children")]
274 | `(let [~class-name ~(get-cls-name-from-meta (-> &env :ns :name) component-name)
275 | ~full-class-name ~(str (-> &env :ns :name) "/" component-name)
276 | ~children*
277 | (walk/postwalk
278 | ;; todo here you can do props validation also
279 | ;; should not allow anything that's not a symbol, map, vector, js-obj, js-array, fn
280 | (wrap-call-style-fn false)
281 | ~(vec children))
282 | ;; pass js structures to the lib
283 | ~children* (cljs.core/clj->js ~children*)
284 | ~component-type ~(get-type `styled el)
285 | ~clss (.apply ~component-type ~component-type ~children*)]
286 | (obj-set ~clss "displayName" ~(str (-> &env :ns :name) "/" component-name))
287 | ;(goog.object/set ~clss "displayName" ~(str (-> &env :ns :name) "/" component-name))
288 |
289 | (def ~component-name
290 | (with-meta (react-factory ~clss ~class-name)
291 | {::styled ~clss
292 | ::hashed-name (hashit ~full-class-name)}))
293 |
294 | (cljs.core/specify! ~component-name
295 | ;~'IPrintWithWriter
296 | ;(~'-pr-writer [this# writer# _#]
297 | ; (~'-write writer# (cljs.core/str this#)))
298 | ~'Object
299 | (~'toString [this#]
300 | (cljs.core/str "." (::hashed-name (meta ~component-name))))))))))
301 |
302 | #?(:cljs
303 | (def ^:private global* (react-factory Global nil)))
304 |
305 | ;; emotion doesn't allow functions in nested position, only
306 | ;; objects and arrays of objects
307 | ;; but they do allow one function as a child
308 | ;; you can always wrap the call in a fn if you want dynamism like below
309 | ;https://github.com/emotion-js/emotion/blob/188dc0e785cfc9b10b3f9a6ead62b56ddd38e039/packages/core/src/global.js#L16
310 |
311 | #?(:cljs
312 | (defn global-style [props]
313 | (global* {:styles (camelize-keys props)})))
314 |
315 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
316 | ;; Theme support
317 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
318 |
319 | #?(:cljs
320 | (defn theme-provider
321 | [props & children]
322 | (when-not (contains? props :theme)
323 | (throw (js/Error. "You must pass a :theme to the theme-provider.")))
324 | (apply react/createElement ThemeProvider
325 | (clj->js props)
326 | (force-children children))))
327 |
328 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
329 | ;; CSS prop support
330 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
331 |
332 | #?(:clj
333 | (defn css-body [props]
334 | `(do
335 | (assert (contains? ~props :css) "Props must contain :css key")
336 | (cljs.core/clj->js
337 | (assoc ~props :css
338 | (walk/postwalk
339 | ;; todo here you can do props validation also
340 | ;; should not allow anything that's not a symbol, map, vector, js-obj, js-array, fn
341 | (wrap-call-style-fn true)
342 | (:css ~props)))))))
343 | #?(:clj
344 | (defmacro jsx
345 | ([el props]
346 | (let [el (cond-> el (keyword? el) name)
347 | css-props (css-body props)]
348 | `(jsx* ~el ~css-props)))
349 |
350 | ([el props children]
351 | (let [el (cond-> el (keyword? el) name)
352 | css-props (css-body props)]
353 | `(jsx* ~el ~css-props (reagent.core/as-element ~children))))))
354 |
355 | #?(:cljs
356 | (defn css [& css]
357 | (styled-core/css (clj->js (walk/postwalk (wrap-call-style-fn true) css)))))
358 |
--------------------------------------------------------------------------------
/src/main/dv/cljs_emotion.cljc:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion
2 | (:require
3 | #?@(:cljs [["react" :as react]
4 | ["@emotion/hash" :as emotion-hash*]
5 | ["@emotion/styled" :as styled*]
6 | ["@emotion/react" :as styled-core :refer [Global ThemeProvider]]
7 | [goog.object :as g]])
8 | [clojure.string :as str]
9 | [clojure.walk :as walk])
10 | #?(:cljs (:require-macros [dv.cljs-emotion :refer [defstyled]])))
11 |
12 | ;; Support plain cljs compiler and shadow.
13 | #?(:cljs (def emotion-hash (g/get emotion-hash* "default")))
14 | #?(:cljs (def styled (g/get styled* "default")))
15 | #?(:cljs (def jsx* styled-core/jsx))
16 |
17 | ;; Used to prevent generated code from needing to require goog.object
18 | (defn obj-set [o k v]
19 | #?(:cljs (g/set o k v)
20 | :clj nil))
21 |
22 | (defn obj-get [o k]
23 | #?(:cljs (g/get o k)
24 | :clj nil))
25 |
26 | ;; from fulcro
27 | #?(:cljs
28 | (defn force-children
29 | "Utility function that will force a lazy sequence of children (recursively) into realized
30 | vectors (React cannot deal with lazy seqs in production mode)"
31 | [x]
32 | (if (seq? x)
33 | (to-array (mapv force-children x))
34 | x)))
35 |
36 | #?(:cljs
37 | (defn kebab->camel
38 | [prop]
39 | (if (str/starts-with? prop ".")
40 | prop
41 |
42 | (if (str/includes? prop "-")
43 | (let [words (->> (re-seq #"[a-zA-Z]+" prop)
44 | (mapv str/capitalize))]
45 | (-> words
46 | (update 0 str/lower-case)
47 | str/join))
48 | prop))))
49 |
50 | ;; todo use the caching strategy seen in reagent.impl.template
51 |
52 | #?(:cljs
53 | (defn camelize-keys
54 | "Also replaces styled components with their css classname is key position."
55 | [style-map]
56 | (walk/postwalk
57 | (fn in-walk [v]
58 | (cond
59 | (keyword? v)
60 | (-> v name kebab->camel)
61 |
62 | (and (meta v) (contains? (meta v) ::hashed-name))
63 | (str "." (-> v meta ::hashed-name))
64 |
65 | :else v))
66 | style-map)))
67 |
68 | #?(:cljs
69 | (defn keyframes [anim-map]
70 | (styled-core/keyframes (clj->js (camelize-keys anim-map)))))
71 |
72 | (def cljs-props-key "dv.cljs-emotion/props")
73 |
74 | #?(:cljs
75 | (defn wrap-call-style-fn [anon-styles?]
76 | (fn [x]
77 | (cond
78 |
79 | ;; Another emotion styled component created with this lib.
80 | (and (meta x) (contains? (meta x) ::hashed-name))
81 | (str "." (-> x meta ::hashed-name))
82 |
83 | (fn? x)
84 | (fn [arg]
85 | ;; arg# is js props passed at runtime, we ship it back and forth js -> cljs -> js
86 |
87 | ;; js->clj is resulting in an infinite recur when children contains another styled component, so we remove it.
88 | (js-delete arg "children")
89 |
90 | (if anon-styles?
91 | ;; with anonymous styles there can be no props - so the theme is passed as the only argument
92 | (clj->js (camelize-keys (x (js->clj arg :keywordize-keys true))))
93 | (let [cljs-args (assoc (obj-get arg cljs-props-key)
94 | :theme (js->clj (obj-get arg "theme") :keywordize-keys true))]
95 | ;; invoke the user-supplied function which returns style data - convert whatever they return to js data structures.
96 | (clj->js (camelize-keys (x cljs-args))))))
97 |
98 | ;; maps come up in value position for nested selectors
99 | (map? x)
100 | (camelize-keys x)
101 |
102 | :else x))))
103 |
104 | #?(:cljs (goog-define ADD_CLASSNAMES "INITIAL"))
105 |
106 | #?(:cljs
107 | (defn add-class-names? []
108 | (if (boolean? ADD_CLASSNAMES)
109 | ADD_CLASSNAMES
110 | goog.DEBUG)))
111 |
112 | #?(:cljs
113 | (defn add-class-name [props class-name]
114 | (if (object? props)
115 | (doto props
116 | (goog.object/set "className"
117 | (->> [class-name (goog.object/get props "className")]
118 | (str/join " ")
119 | (str/trim))))
120 | (update props :className #(if (nil? %) class-name (str class-name " " %))))))
121 |
122 | #?(:cljs (defn hashit [string] (str "dvcss-" (emotion-hash string))))
123 |
124 | #?(:cljs
125 | (defn set-class-name [props class-name]
126 | (if class-name
127 | (let [hashed-name (hashit class-name)
128 | props (add-class-name props hashed-name)]
129 | (if (add-class-names?)
130 | (add-class-name props class-name)
131 | props))
132 | props)))
133 |
134 | #?(:cljs
135 | (defn map->obj [m]
136 | (reduce-kv (fn [o k v]
137 | ;; convert keywords to string only in key position
138 | (let [new-k (cond-> k (implements? INamed k) name)
139 | new-v (cond-> v (map? v) map->obj)]
140 | (doto o (obj-set new-k new-v))))
141 | #js{} m)))
142 |
143 | #?(:cljs
144 | (defn make-js-props [props class-name]
145 | (if (object? props)
146 | props
147 | (let [clj-props (set-class-name props class-name)
148 | js-props (map->obj clj-props)]
149 | (doto js-props (obj-set cljs-props-key clj-props))))))
150 |
151 | #?(:cljs
152 | (defn react-factory [el class-name]
153 | (fn
154 | ([]
155 | (jsx* el (clj->js (set-class-name {} class-name))))
156 | ([props]
157 | (try
158 | (cond
159 | (or (react/isValidElement props) (string? props))
160 | (jsx* el (set-class-name #js{} class-name) props)
161 |
162 | (map? props)
163 | ;; Do not use clj->js in order to preserve clojure data types like keywords that would not
164 | ;; survive a round-trip clj->js js->clj
165 | (jsx* el (make-js-props props class-name))
166 |
167 | (object? props)
168 | (jsx* el (set-class-name props class-name))
169 |
170 | (or (array? props) (coll? props))
171 | (jsx* el (set-class-name #js{} class-name) (force-children props))
172 |
173 | :else
174 | (jsx* el (set-class-name #js{} class-name)))
175 |
176 | (catch js/Object e
177 | (js/console.error "Error invoking an emotion styled component: " e))))
178 |
179 | ([props & children]
180 | ;; if props are a mapping type and not a react child
181 | (if (or (and (object? props) (not (react/isValidElement props))) (map? props))
182 | (let [js-props (make-js-props props class-name)]
183 | (if (seq children)
184 | (apply jsx* el js-props (force-children children))
185 | (jsx* el js-props)))
186 | (apply jsx* el (set-class-name #js{} class-name) (force-children (list* props children))))))))
187 |
188 | #?(:clj
189 | (defn get-type
190 | [styled-arg tag-name]
191 | (cond
192 | ;; if literals, don't need to determine type at runtime
193 | ;; a dom element like :div, same as styled.div``
194 | (string? tag-name) `(obj-get ~styled-arg ~tag-name)
195 | (keyword? tag-name) `(obj-get ~styled-arg ~(name tag-name))
196 | :else
197 | `(cond
198 | (string? ~tag-name)
199 | (obj-get ~styled-arg ~tag-name)
200 |
201 | (keyword? ~tag-name)
202 | (obj-get ~styled-arg ~(name tag-name))
203 |
204 | ;; Another styled component
205 | (::styled (meta ~tag-name))
206 | (.call ~styled-arg ~styled-arg (::styled (meta ~tag-name)))
207 |
208 | ;; A React component
209 | :else
210 | (.call ~styled-arg ~styled-arg ~tag-name)))))
211 |
212 | #?(:clj
213 | (defn get-cls-name
214 | [namespace-name print-style component-sym]
215 | (case print-style
216 | :full (str namespace-name "/" component-sym)
217 | :short (str component-sym)
218 | :nil nil)))
219 |
220 | #?(:clj (def default-classname-style :full))
221 |
222 | #?(:clj
223 | (defn get-cls-name-from-meta
224 | "Returns string or nil for the classname"
225 | [namespace-name component-sym]
226 | (if (contains? (meta component-sym) :styled/classname)
227 | (let [print-config (:styled/classname (meta component-sym))]
228 | (if (#{:full :short :nil} print-config)
229 | (get-cls-name namespace-name print-config component-sym)
230 | (throw (Exception. (str "Unknown option for class-name style in metadata passed to component: " component-sym)))))
231 | (get-cls-name namespace-name default-classname-style component-sym))))
232 |
233 | #?(:clj
234 | (defmacro defstyled
235 | ([component-name el & children]
236 | (let [component-type (gensym "component-type")
237 | clss (gensym "clss")
238 | class-name (gensym "className")
239 | full-class-name (gensym "fullClassName")
240 | children* (gensym "children")]
241 | `(let [~class-name ~(get-cls-name-from-meta (-> &env :ns :name) component-name)
242 | ~full-class-name ~(str (-> &env :ns :name) "/" component-name)
243 |
244 | ~children*
245 | (walk/postwalk
246 | ;; todo here you can do props validation also
247 | ;; should not allow anything that's not a symbol, map, vector, js-obj, js-array, fn
248 | (wrap-call-style-fn false)
249 | ~(vec children))
250 |
251 | ;; pass js structures to the lib
252 | ~children* (cljs.core/clj->js ~children*)
253 | ~component-type ~(get-type `styled el)
254 | ~clss (.apply ~component-type ~component-type ~children*)]
255 | (obj-set ~clss "displayName" ~(str (-> &env :ns :name) "/" component-name))
256 |
257 | (def ~component-name
258 | (with-meta (react-factory ~clss ~class-name)
259 | {::styled ~clss
260 | ::hashed-name (hashit ~full-class-name)}))
261 | (cljs.core/specify! ~component-name
262 | ~'Object
263 | (~'toString [this#]
264 | (cljs.core/str "." (::hashed-name (meta ~component-name))))))))))
265 |
266 | #?(:clj
267 | (comment
268 | (macroexpand-1 '(defstyled button4 button3 {:fontSize "20em"}))
269 | (macroexpand '(defstyled button3 :button
270 | {:background "lightblue" :color "green"}))
271 |
272 | (macroexpand-1
273 | '(defstyled button3 :button
274 | {:background "lightblue"
275 | button1 {:color "green"}}
276 | (fn [jsprops cp]
277 | {:color (or (:color cp) "white")})))
278 |
279 | ;; postwalk doesn't continue expanding replaced values, like the fn call here:
280 | (walk/postwalk
281 | (fn [i]
282 | (cond
283 | (fn? i) (i)
284 | (keyword? i) (kebab->camel (name i))
285 | :else i))
286 | [{:background-color "blue"}
287 | (fn [] {:border-radius 5})])))
288 |
289 | (comment
290 | (camelize-keys
291 | [{:background "lightblue"
292 | :font-size 20
293 | :border-radius "10px"}
294 | {:background-image "url(xyz.com/my-image)"}])
295 | (camelize-keys
296 | {:background "lightblue"
297 | :font-size 20
298 | "@media(min-width: 200px)" {:font-size 33}
299 | :border-radius "10px"})
300 |
301 | (walk/postwalk
302 | (fn [item]
303 | (println "item: " item " map entry: " (map-entry? item) " vec? " (vector? item))
304 | (cond
305 | (keyword? item)
306 | (do
307 | (println "found keyword")
308 | (keyword (kebab->camel (name item))))
309 | :elseeeee item)
310 | )
311 | {:background "lightblue"
312 | :font-size 20
313 | "@media(min-width: 200px)"
314 | [{:font-size 33}
315 | {:background-color "purple"}]
316 | :border-radius "10px"}))
317 |
318 | ;; emotion doesn't allow functions in nested position, only
319 | ;; objects and arrays of objects
320 | ;; but they do allow one function as a child
321 | ;; you can always wrap the call in a fn if you want dynamism like below
322 | ;https://github.com/emotion-js/emotion/blob/188dc0e785cfc9b10b3f9a6ead62b56ddd38e039/packages/core/src/global.js#L16
323 |
324 | #?(:cljs
325 | (defn global-style
326 | "Takes a cljs vector or hashmap of styles and converts to JS types before calling emotion's Global function
327 | from the @emotion/react package.
328 | Inserts styles into the DOM that target global elements and classes.
329 |
330 | https://emotion.sh/docs/@emotion/react"
331 | [props]
332 | (react/createElement Global #js{:styles (clj->js (camelize-keys props))})))
333 |
334 | ;; can use like so:
335 | (comment
336 | (global-style {:body {:background "#cce" "@media (min-width:700px)" {:background "white"}}})
337 | (global-style
338 | [(clj->js {:body {:font-family "serif"}}) {:body {:border "2px solid yellOW"}} {:body {:background-color "#ecccee"}}])
339 |
340 | ;; to adapt based on props, wrap in a fn:
341 | (defn my-globals [props]
342 | (global-style
343 | {:body {:background-color "red"}})))
344 |
345 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
346 | ;; Theme support
347 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
348 |
349 | #?(:cljs
350 | (defn theme-provider
351 | "Takes a hashmap of a style theme and react children to render with that theme in the React Context
352 | using emotion's ThemeProvider."
353 | [props & children]
354 | (when-not (contains? props :theme) (throw (js/Error. "You must pass a :theme to the theme-provider.")))
355 | (if (:theme props)
356 | (apply react/createElement ThemeProvider
357 | (clj->js props)
358 | (force-children children))
359 | (react/Children.map children (fn [c i] (react/cloneElement c #js{:key i}))))))
360 |
361 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
362 | ;; CSS prop support
363 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
364 |
365 | #?(:clj
366 | (defn ^:private css-body [props]
367 | `(do
368 | ;(assert (contains? ~props :css) "Props must contain :css key")
369 | (cljs.core/clj->js
370 | (assoc ~props :css
371 | (walk/postwalk
372 | ;; todo here you can do props validation also
373 | ;; should not allow anything that's not a symbol, map, vector, js-obj, js-array, fn
374 | (wrap-call-style-fn true)
375 | (:css ~props)))))))
376 | #?(:clj
377 | (defmacro jsx
378 | ([el props]
379 | (let [el (cond-> el (keyword? el) name)
380 | css-props (css-body props)]
381 | `(jsx* ~el ~css-props)))
382 |
383 | ([el props & children]
384 | (let [el (cond-> el (keyword? el) name)
385 | css-props (css-body props)]
386 | `(jsx* ~el ~css-props ~@children)))))
387 |
388 | #?(:cljs
389 | (defn convert-css
390 | "Takes a hashmap or vector of styles and converts to JS types, will pass any functions cljs data structures."
391 | [css]
392 | (clj->js (walk/postwalk (wrap-call-style-fn true) css))))
393 |
394 | #?(:cljs
395 | (defn css [& css]
396 | (styled-core/css (clj->js (walk/postwalk (wrap-call-style-fn true) css)))))
397 |
--------------------------------------------------------------------------------
/src/dev/dv/cljs_emotion/devcards.cljs:
--------------------------------------------------------------------------------
1 | (ns dv.cljs-emotion.devcards
2 | (:require
3 | [devcards.core :as dc :refer (defcard)]
4 | [dv.cljs-emotion.fulcro-cards]
5 | [dv.cljs-emotion.reagent-cards]
6 | [dv.cljs-emotion.reagent-debug]
7 | [dv.cljs-emotion.debug]
8 | [dv.cljs-emotion.helix-cards]
9 | [dv.cljs-emotion.target-styled]
10 | [sablono.core :as sab :refer [html]]
11 | ["polished" :as p :refer [darken]]
12 | [dv.cljs-emotion :as em :refer [jsx defstyled keyframes global-style theme-provider]]))
13 |
14 | (enable-console-print!)
15 |
16 | (defcard
17 | "
18 | Hi there. This page uses devcards to demonstrate usage of the dv.cljs-emotion library.
19 | This library is a small wrapper around the emotion css-in-js JavaScript library that
20 | makes it easy to use all of CSS within a react application.
21 |
22 | The source of these cards can be found here:
23 | https://github.com/dvingo/cljs-emotion/tree/master/src/dev/dv/cljs_emotion
24 |
25 | The main API of this library is: `[defstyled keyframes global-style theme-provider]`
26 |
27 | defstyled is a wrapper around `@emotion/styled`
28 | Styled is multi-arity and supports passing functions that return maps of styles, a vector of styles that
29 | will be merged top down (first to last) and a map, you can also pass JavaScript objects and arrays.
30 |
31 | This library converts between cljs and js structures, but the multi-arity capability is built into emotion already.
32 | ")
33 |
34 | (defcard
35 | "# Use it
36 |
37 | Require the library:
38 | ```clojure
39 | (require [dv.cljs-emotion :refer [defstyled keyframes global-style theme-provider]])
40 | ```
41 |
42 | Or for reagent support:
43 | ```clojure
44 | (require [dv.cljs-emotion-reagent :refer [defstyled keyframes global-style theme-provider]])
45 | ```
46 |
47 | You can pass any number of children to defstyled:
48 |
49 | ```clojure
50 | (defstyled sample1 :div
51 | {:background-color \"RebeccaPurple\"})
52 | ```
53 |
54 | ```clojure
55 | (defstyled sample1 :div
56 | {:background-color \"RebeccaPurple\"}
57 | #js{:color \"yellow\"}
58 | (fn [props] {:border-radius 4}))
59 | ```
60 |
61 | This library delegates to emotion's styled API, while adding a transform layer for cljs->js data structures.
62 | "
63 | )
64 |
65 | (def global-data (atom {:on? false}))
66 |
67 | (def btn-styles
68 | {"button"
69 | {:background-color "#f2f2f2"
70 | :background-image "linear-gradient(to bottom, #f2f2f2, #f2f2f2)"
71 | :border "1px solid #bfbfbf"
72 | :text-shadow "0 1px 0 rgba(255, 255, 255, 0.5)"
73 | :border-radius 3
74 | :cursor "pointer"
75 | :color "#8c8c8c"
76 | :font-family "sans-serif"
77 | :line-height "1rem"
78 | :font-weight 700
79 | :margin "16px 0 0 16px"
80 | :padding "9px 16px 9px"
81 | :transition "all 20ms ease-out"
82 | :vertical-align "top"
83 | :box-shadow (str "inset 0 1px 0 white, inset 0 -1px 0 #d9d9d9,"
84 | " inset 0 0 0 1px #f2f2f2, 0 2px 4px rgba(0, 0, 0, 0.2)")
85 | ":active" {:box-shadow "inset 0 2px 3px rgba(0, 0, 0, 0.2)"}
86 | ":hover" {:background "#f2f2f2"
87 | :border-color "#8c8c8c"
88 | :box-shadow "inset 0 1px 0 white, inset 0 -1px 0 #d9d9d9, inset 0 0 0 1px #f2f2f2"}}})
89 |
90 | (def button-styles
91 | {"button"
92 | {:padding "1rem 2rem"
93 | :border-radius "4px"
94 | :transition "all .5s"
95 | ":hover" {:box-shadow " 0px 10px 10px rgba(0,0,0,0.2)"
96 | :transform "translateY(-3px)"
97 | "::after" {:transform "scaleX(1.4) scaleY(1.6)"
98 | :opacity 0}}
99 | "::after" {:content "''"
100 | :text-decoration "none"
101 | :text-transform "uppercase"
102 | :position "absolute"
103 | :width "100 %"
104 | :height "100 %"
105 | :top 0
106 | :left 0
107 | :border-radius 100
108 | :display "inline-block"
109 | :z-index -1
110 | :transition "all .5s"}}})
111 |
112 | (defcard
113 | "For a quick demonstration we will change some global styles on this page.
114 |
115 | In this example we add some styles to `