├── .github
└── FUNDING.yml
├── .gitignore
├── CHANGES.md
├── README.md
├── doc
└── potential_features.md
├── example-resources
└── public
│ └── devcards
│ ├── css
│ ├── devcard-api.css
│ └── two-zero.css
│ └── index.html
├── example_src
└── devdemos
│ ├── core.cljs
│ ├── css_opt_out.cljs
│ ├── custom_cards.cljs
│ ├── defcard_api.cljs
│ ├── edn_render.cljs
│ ├── errors.cljs
│ ├── extentions.cljs
│ ├── maintain_state.cljs
│ ├── om_next.cljs
│ ├── reagent.cljs
│ ├── source_code_display.cljs
│ ├── start_ui.cljs
│ ├── testing.cljs
│ └── two_zero.cljs
├── index.html
├── project.clj
├── resources
└── public
│ └── devcards
│ └── css
│ ├── com_rigsomelight_devcards.css
│ ├── com_rigsomelight_devcards_addons.css
│ ├── com_rigsomelight_edn.css
│ ├── com_rigsomelight_edn_flex.css
│ ├── com_rigsomelight_github_highlight.css
│ ├── default.css
│ └── zenburn.css
├── site
├── devdemos.js
└── two-zero.css
└── src
├── deps.cljs
└── devcards
├── core.clj
├── core.cljs
├── js_libs
├── highlight.ext.js
├── highlight.pack.js
├── marked.ext.js
└── marked.min.js
├── system.clj
├── system.cljs
└── util
├── edn_renderer.cljs
├── markdown.cljs
├── utils.clj
└── utils.cljs
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [bhauman]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | pom.xml.asc
2 | .idea
3 | *.iml
4 | pom.xml
5 | *jar
6 | /lib/
7 | /classes/
8 | /out/
9 | /target/
10 | /site/out
11 | .lein-deps-sum
12 | .lein-repl-history
13 | .lein-plugins/
14 | checkouts/
15 | .nrepl-port
16 | .lein-classpath
17 | .rbenv-version
18 | config.ru
19 | example-resources/public/js/compiled/*
20 | example-resources/public/devcards/js/compiled/*
21 | .\#*
22 | \#*
23 |
24 | *-init.clj
25 | figwheel_server.log
26 | .rebel_readline_history
27 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | ## 0.2.6 Updated for latest React 16.4
2 |
3 | * requires `cljsjs/create-react-class` - you may need to exclude this
4 | along with `cljsjs/react` and `cljsjs/react-dom` if you are using
5 | npm dependencies
6 | * should effortlessly support the latest reagent
7 | * fixed outstanding issue that made it difficult to work with npm dependencies
8 | * moved to `cljsjs/marked` instead of using showdown - I'm considering
9 | injecting a wrapped version of this lib so that it doesn't conflict
10 | if you happen to be using it from npm.
11 | * fixed an edn rendering error caused by MapEntry changes
12 | * fixed the 1024 buffer limitation, can be configured via `:closure-defines {devcards.core/card-buffer-size 10000}`
13 | * added tool friendly way to opt-in to devcards via `:closure-defines {devcards.core/active true}`
14 | * discoverd that `(set! (.-createClass (.-React goog.global)) create-react-class)` is a sweet
15 | hack to get `omcljs/om` to work with latest React
16 |
17 | ## 0.2.1-7 Some reagent fixes
18 |
19 | * Improved Reagent reloading PR #100
20 | * removed woarnings on duplicate "is" tests PR #101
21 |
22 | ## 0.2.1-6
23 |
24 | * Fixed a regression where Component local state was lost during reload
25 | * refactored to accommodate Om Next extension
26 | * fixed some React missing key warnings
27 |
28 | ## 0.2.1-5 Fixing minor dependency conflicts
29 |
30 | * getting rid of dep conflicts
31 |
32 | ## 0.2.1-4 Om Next Helpers
33 |
34 | * bumping sablono deps
35 | * Om Next helpers added by @anmonteiro
36 | * more react warnings killed by @tristanstraub
37 |
38 | ## 0.2.1-3
39 |
40 | * markdown bullet indentation fix
41 | * got rid of a bunch of React warnings
42 | * fix printing JavaScript Symbols
43 | * various documentation updates
44 |
45 | ## 0.2.1 React 14
46 |
47 | * now depends on React 14
48 | * fixing the `:watch-atom false` option
49 | * added the `:actual` output for failing tests
50 | * allowing the config of default card options
51 |
52 | ## 0.2.0-8 Improved Reagent support
53 |
54 | * **added `devcards.core/devcard-rg`** as a shortcut to defining
55 | `(defcard title (dc/reagent [:div "hey"]))` which can now be
56 | expressed as `(defcard-rg title [:div "hey"])`
57 | * improved Reagent documentation
58 | see: http://rigsomelight.com/devcards/#!/devdemos.reagent
59 | - I could really use some more help with Reagent docs
60 | * **Reagent breaking change** you can no longer supply bare reagent components to
61 | `devcards.core/reagent` or `devcards.core/defcard-rg` You always
62 | need to pass a Reagent element of the form `[component ... args]`
63 | This makes reagent support consistent with the `defcard` api where you have to
64 | supply a ReactElement and not a component
65 |
66 | ## 0.2.0-7 Reagent support with bad bug
67 |
68 | ## 0.2.0-5
69 |
70 | * isolated more css into the addons css - thanks to magnars
71 | * a new `:classname` option to the devcard options that will be added
72 | to the card body - thanks to codebeige
73 | * bumped React version to 0.13.3-1
74 | * added base support for React 14 - thanks to minimal
75 | * made `start-devcard-ui*` public
76 | * a bunch of documentation has been added to rigsomelight.com/devcards
77 |
78 | ## 0.2.0-2 First documented release
79 |
80 | * Support for `cljs.test` async testing
81 |
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Devcards
2 |
3 | ### Current release:
4 |
5 | [](https://clojars.org/devcards)
6 |
7 | Devcards aims to provide ClojureScript developers with an interactive
8 | visual REPL. Devcards makes it simple to interactively surface code
9 | examples that have a visual aspect into a browser interface.
10 |
11 | Devcards is **not** a REPL, as it is driven by code that exists in
12 | your source files, but it attempts to provide a REPL-like experience
13 | by allowing developers to quickly try different code examples and
14 | see how they behave in an actual DOM.
15 |
16 | Devcards is centered around a notion of a *card*. Every card
17 | represents some code to be displayed. Devcards provides an interface
18 | which allows the developer to navigate to different namespaces and view
19 | the *cards* that have been defined in that namespace.
20 |
21 | When used in conjunction with [lein figwheel][leinfigwheel] the cards
22 | can be created and edited **"live"** in one's ClojureScript source
23 | files. Essentially lifting the code example out of the file into the
24 | browser for you to try out immediately.
25 |
26 |
27 |
28 | For example, the following code will create a *card* for a Sablono
29 | template that you might be working on:
30 |
31 | ```clojure
32 | (defcard two-zero-48-view
33 | (sab/html
34 | [:div.board
35 | [:div.cells
36 | [:div {:class "cell xpos-1 ypos-1"} 4]
37 | [:div {:class "cell xpos-1 ypos-2"} 2]
38 | [:div {:class "cell xpos-1 ypos-3"} 8]]]))
39 | ```
40 |
41 | When used with [lein-figwheel][leinfigwheel], saving the file that
42 | contains this definition will cause this Sablono template to be
43 | rendered into the Devcards interface.
44 |
45 | Read: [The Hard Sell](http://rigsomelight.com/devcards/#!/devdemos.core)
46 |
47 | [See the introduction video.](https://vimeo.com/97078905)
48 |
49 | [See the Strange Loop talk.](https://www.youtube.com/watch?v=G7Z_g2fnEDg)
50 |
51 | # Why???
52 |
53 | We primarily design and iterate on our front end applications *inside*
54 | the main application itself. In other words, our execution environment
55 | is constrained by the shape and demands of the application we are
56 | working on. This is extremely limiting.
57 |
58 | This doesn't seem like a problem, eh?
59 |
60 | Well think of it this way: the main application and its many
61 | subcomponents can potentially embody a tremendous number of states. But
62 | working against a single instance of the application only lets you
63 | look at one state at a time. What if you could work on the application
64 | or component in several states at the same time? This is a powerful
65 | multiplier. You are **increasing the bandwidth of the feedback** you are
66 | getting while working on your code.
67 |
68 | Another problem is that we often manually place our components into
69 | different **important** states to run them through their paces as we
70 | develop them. But ... these test states are **ephemeral**. Wouldn't
71 | it be better to **keep** a development "page" as a permanent asset
72 | where these components are displayed in these various states as a
73 |
74 | * a lab space for future development
75 | * a code reference for new developers, and your future self
76 | * a tool for QA and application testers
77 |
78 | Developing your components in a different context than your main
79 | application **starkly reveals environmental coupling**, in the same
80 | way that unit tests often do. This can lead to developing components
81 | that are more independent than the ones that are developed inside the
82 | main app.
83 |
84 | One more thing: developing your components in a SPA that isn't your
85 | main application provides you a space to create and use visual
86 | components that are intended to help you understand the code you are
87 | working on. We are UI programmers after all, why wait for IDEs to
88 | create the tools we need? Most problems are unique and can benefit
89 | tremendously from the creation of a very thin layer of custom tooling.
90 |
91 | Developing inside the main application is constraining and it isn't
92 | until you develop inside a **meta application** that you can see this
93 | more clearly. With a meta application, you now have a space to try
94 | things out that **do not have to interface or fit into the main
95 | application**. This is extremely important as it gives you space to
96 | try new things without the cost that is currently associated with
97 | experiments (branching, new html host file, etc).
98 |
99 | ## Examples
100 |
101 | Regardless of which path you take to get started with Devcards please
102 | see the following examples:
103 |
104 | [Introduction examples](http://rigsomelight.com/devcards/#!/devdemos.core) ([src](https://github.com/bhauman/devcards/blob/master/example_src/devdemos/core.cljs))
105 |
106 | [An example implementation of 2048](http://rigsomelight.com/devcards/#!/devdemos.two_zero) ([src](https://github.com/bhauman/devcards/blob/master/example_src/devdemos/two_zero.cljs))
107 |
108 | [An introduction to the `defcard` api](http://rigsomelight.com/devcards/#!/devdemos.defcard_api) ([src](https://github.com/bhauman/devcards/blob/master/example_src/devdemos/defcard_api.cljs))
109 |
110 | ## Super Quick Start
111 |
112 | There is a Devcards Leiningen template to get you up an running quickly.
113 |
114 | Make sure you have the [latest version of leiningen installed](https://github.com/technomancy/leiningen#installation).
115 |
116 | Type the following to create a fresh project with devcards setup for you:
117 |
118 | ```
119 | lein new devcards hello-world
120 | ```
121 |
122 | Then
123 |
124 | ```
125 | cd hello-world
126 |
127 | lein figwheel
128 | ```
129 |
130 | to start the figwheel interactive devserver.
131 |
132 | Then visit `http://localhost:3449/cards.html`
133 |
134 | ## Quick Trial
135 |
136 | If you want to quickly interact with a bunch of devcards demos:
137 |
138 | ```
139 | git clone https://github.com/bhauman/devcards.git
140 |
141 | cd devcards
142 |
143 | lein figwheel
144 | ```
145 |
146 | Then visit `http://localhost:3449/devcards/index.html`
147 |
148 | The code for the cards you are viewing in the devcards interface is
149 | located in the `example_src` directory.
150 |
151 | Go ahead and edit the code in the examples and see how the devcards
152 | interface responds.
153 |
154 | ## Usage
155 |
156 | First make sure you include the following `:dependencies` in your `project.clj` file.
157 |
158 | ```clojure
159 | [org.clojure/clojurescript "1.10.238"]
160 | [devcards "0.2.5"]
161 | ```
162 |
163 | You will need an HTML file to host the devcards interface. It makes
164 | sense to have a separate file to host devcards. I would
165 | create the following `resources/public/cards.html` file (this is the same
166 | file as in the leiningen template).
167 |
168 |
169 | ```html
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | ```
182 |
183 | ## Usage With Figwheel
184 |
185 | [lein-figwheel](https://github.com/bhauman/lein-figwheel)
186 | is not required to use Devcards but it is definitely recommended
187 | if you want to experience interactive coding with Devcards.
188 | See the [lein-figwheel repo](https://github.com/bhauman/lein-figwheel)
189 | for instructions on how to do that.
190 |
191 | Configure your devcards build:
192 |
193 | ```clojure
194 | :cljsbuild {
195 | :builds [
196 | {:id "devcards"
197 | :source-paths ["src"]
198 | :figwheel { :devcards true } ;; <- note this
199 | :compiler { :main "{{your lib name}}.core"
200 | :asset-path "js/compiled/devcards_out"
201 | :output-to "resources/public/js/{{your lib name}}_devcards.js"
202 | :output-dir "resources/public/js/devcards_out"
203 | :source-map-timestamp true }}]
204 | }
205 | ```
206 |
207 |
208 | Next you will need to include the Devcards macros into your file:
209 |
210 | ```clojure
211 | (ns example.core
212 | (:require
213 | [sablono.core :as sab]) ; just for example
214 | (:require-macros
215 | [devcards.core :refer [defcard]]))
216 |
217 | (defcard my-first-card
218 | (sab/html [:h1 "Devcards is freaking awesome!"]))
219 | ```
220 |
221 | This will create a card in the devcards interface.
222 |
223 | Take a look at [the `defcard` api](http://rigsomelight.com/devcards/#!/devdemos.defcard_api) ([src](https://github.com/bhauman/devcards/blob/master/example_src/devdemos/defcard_api.cljs))
224 |
225 | ## Usage without Figwheel
226 |
227 | Figwheel does some magic so that Devcards can be included or excluded
228 | from your code easily. You can certainly use Devcards without Figwheel,
229 | but there are three things that you will need to do.
230 |
231 | #### You need to specify `:devcards true` **in the build-options** of your ClojureScript build
232 |
233 | ```clojure
234 | { :main "{{name}}.core"
235 | :devcards true ; <- note this
236 | :asset-path "js/compiled/devcards_out"
237 | :output-to "resources/public/js/{{sanitized}}_devcards.js"
238 | :output-dir "resources/public/js/devcards_out"
239 | :source-map-timestamp true }
240 | ```
241 |
242 | This is important as it is a signal to the `defcard` macro to render
243 | the cards. This is equivalent to adding `:figwheel { :devcards true }`
244 | in our figwheel based build above, but since we aren't using figwheel
245 | in this build adding the figwheel options doesn't help.
246 |
247 | #### You will need to require `devcards.core` in the files that use devcards as such:
248 |
249 | ```clojure
250 | (ns example.core
251 | (:require
252 | [devcards.core :as dc] ; <-- here
253 | [sablono.core :as sab]) ; just for this example
254 | (:require-macros
255 | [devcards.core :refer [defcard]])) ; <-- and here
256 |
257 | (defcard my-first-card
258 | (sab/html [:h1 "Devcards is freaking awesome!"]))
259 | ```
260 |
261 | This isn't required with Figwheel because it puts `devcards.core` into the
262 | build automatically.
263 |
264 | #### You will need to start the Devcards UI
265 |
266 | ```
267 | (devcards.core/start-devcard-ui!)
268 | ```
269 |
270 | Make sure this is included in the file you have specified as `:main`
271 | in your build options. As mentioned above, you don't want the Devcards UI to compete with
272 | your application's UI so you will want to make sure it isn't getting
273 | launched.
274 |
275 |
276 | ## Devcards as a Standalone Website
277 |
278 | Devcards can easily be hosted as a standalone website by following
279 | steps similar to those needed to use it locally without figwheel.
280 | In this example, we will be adding a `hostedcards` profile to build
281 | our site.
282 |
283 | #### Add `:devcards true` **to the build-options** of our ClojureScript build profile
284 |
285 | ```clojure
286 | {:id "hostedcards"
287 | :source-paths ["src"]
288 | :compiler {:main "{{your lib name}}.core"
289 | :devcards true ; <- note this
290 | :asset-path "js/compiled/out"
291 | :output-to "resources/public/js/compiled/{{your lib name}}.js"
292 | :optimizations :advanced}}
293 | ```
294 |
295 | #### Require `devcards.core`in the files that use devcards
296 |
297 | ```clojure
298 | (ns {{your lib name}}.core
299 | (:require
300 | [devcards.core :as dc])
301 | (:require-macros
302 | [devcards.core :refer [defcard]]))
303 | ```
304 |
305 | ### Start the Devcards UI in {{your lib name}}.core
306 | ```clojure
307 | (devcards.core/start-devcard-ui!)
308 | ```
309 |
310 | ### Include the compiled JS in our HTML
311 |
312 | ```html
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 | ```
326 |
327 |
328 | ### Run our Build
329 |
330 | `lein cljsbuild once hostedcards`
331 |
332 | Once the build is complete, simply copy the contents of `resources\public`
333 | to your webserver and serve it up as you would any other page.
334 |
335 | ## FAQ
336 |
337 | #### Does Devcards only work with React or Om?
338 |
339 | Nope, it can work with arbitrary CLJS code examples as well. Devcards
340 | provides a `dom-node` helper that will give you a node in the DOM to
341 | display stuff in.
342 |
343 | #### Does Devcards require Figwheel?
344 |
345 | Devcards will work automatically with REPL workflow or boot-reload.
346 |
347 | You can also just reload the browser after making a change.
348 |
349 | #### What do I do for deployment?
350 |
351 | Devcards has been rewritten so that you can write Devcards alongside
352 | your code with no impact on your production code.
353 |
354 | That being said it is often helpful to move the bulk of your cards to
355 | a different buildpath that is only built when working on the **devcards**
356 | build.
357 |
358 | When working with devcards I often have three builds "devcards",
359 | "dev", "prod".
360 |
361 |
362 |
363 |
364 | [leinfigwheel]: https://github.com/bhauman/lein-figwheel
365 |
366 |
--------------------------------------------------------------------------------
/doc/potential_features.md:
--------------------------------------------------------------------------------
1 | ## Todo
2 |
3 | Make edn renderer render differences.
4 |
--------------------------------------------------------------------------------
/example-resources/public/devcards/css/devcard-api.css:
--------------------------------------------------------------------------------
1 | .red-box {
2 | border: 5px solid red;
3 | }
4 |
--------------------------------------------------------------------------------
/example-resources/public/devcards/css/two-zero.css:
--------------------------------------------------------------------------------
1 | .board-area {
2 | background-color: rgb(187,173,160);
3 | width: 499px;
4 | height: 499px;
5 | border-radius: 6px;
6 | position: relative;
7 |
8 | /* initialize as 3d */
9 | -webkit-transform: translate3d(0,0,0);
10 | -moz-transform: translate3d(0,0,0);
11 | transform: translate3d(0,0,0);
12 | }
13 |
14 | .board-area-one-row {
15 | height: 136px;
16 | }
17 |
18 | .one-row-board .board-area {
19 | height: 136px;
20 | overflow: hidden;
21 | }
22 |
23 | .cell-pos {
24 | height: 106px;
25 | width: 106px;
26 | position:absolute;
27 | border-radius: 4px;
28 |
29 | -webkit-transition: all 0.12s;
30 | transition: all 0.12s;
31 |
32 | /* initialize as 3d */
33 | -webkit-transform: translate3d(0,0,0);
34 | -moz-transform: translate3d(0,0,0);
35 | transform: translate3d(0,0,0);
36 | }
37 |
38 | .cell-empty {
39 | background-color: rgb(205,193,180);
40 | }
41 |
42 | .cell {
43 | background-color: rgb(205,193,180);
44 | height: 106px;
45 | width: 106px;
46 | /* position: relative; */
47 | border-radius: 4px;
48 | font-family: "Helvetica Neue", Arial, sans-serif;
49 | font-size: 55px;
50 | line-height: 102px;
51 | font-weight: bold;
52 | text-align: center;
53 | vertical-align: middle;
54 |
55 | /* initialize as 3d */
56 | -webkit-transform: translate3d(0,0,0);
57 | -moz-transform: translate3d(0,0,0);
58 | transform: translate3d(0,0,0);
59 | }
60 |
61 | .cell.highlight {
62 | -webkit-animation: highlight 0.1s;
63 | }
64 |
65 | @-webkit-keyframes highlight {
66 | 0% { -webkit-transform: scale3d(1.2,1.2,1.0);
67 | opacity: 0.7;}
68 | 100% { -webkit-transform: scale3d(1.0,1.0,1.0);
69 | opacity: 1.0;}
70 | }
71 |
72 | .cell.reveal {
73 | -webkit-animation: reveal 0.1s;
74 | }
75 |
76 | @-webkit-keyframes reveal {
77 | 0% { -webkit-transform: scale3d(0.1,0.1,1.0);
78 | opacity: 0.1;
79 | }
80 | 100% { -webkit-transform: scale3d(1.0,1.0,1.0);
81 | opacity: 1.0;}
82 | }
83 |
84 | .pos-top-0 { top: 15px; }
85 | .pos-top-1 { top: 136px; }
86 | .pos-top-2 { top: 257px; }
87 | .pos-top-3 { top: 378px; }
88 |
89 | .pos-left-0 { left: 15px; }
90 | .pos-left-1 { left: 136px; }
91 | .pos-left-2 { left: 257px; }
92 | .pos-left-3 { left: 378px; }
93 |
94 |
95 |
96 | .cell-num-2 {
97 | background-color: rgb(238, 228,218);
98 | color: rgb(110,102,93);
99 | }
100 |
101 | .cell-num-4 {
102 | background-color: rgb(237, 224,200);
103 | color: rgb(119,110,101);
104 | }
105 |
106 | .cell-num-8 {
107 | background-color: rgb(242, 177, 121);
108 | color: rgb(249,246,242);
109 | }
110 |
111 | .cell-num-16 {
112 | background-color: rgb(245, 149, 99);
113 | color: rgb(249,246,242);
114 | }
115 |
116 | .cell-num-32 {
117 | background-color: rgb(245, 124, 95);
118 | color: rgb(249,246,242);
119 | }
120 |
121 | .cell-num-64 {
122 | background-color: rgb(246, 94, 59);
123 | color: rgb(249,246,242);
124 | }
125 |
126 | .cell-num-128 {
127 | background-color: rgb(237, 207,114);
128 | color: rgb(249,246,242);
129 | font-size: 48px;
130 | }
131 |
132 | .cell-num-256 {
133 | background-color: rgb(237, 204, 97);
134 | color: rgb(249,246,242);
135 | font-size: 48px;
136 | border: 1px solid rgba(238, 228, 218, 0.5);
137 | box-shadow: 0 0 25px 5px rgb(237, 204, 97);
138 | }
139 |
140 | .cell-num-512 {
141 | background-color: rgb(237, 204, 97);
142 | color: rgb(249,246,242);
143 | font-size: 48px;
144 | border: 1px solid rgba(238, 228, 218, 0.5);
145 | box-shadow: 0 0 25px 5px rgb(237, 204, 97);
146 | }
147 |
148 | .cell-num-1024 {
149 | background-color: rgb(237, 204, 97);
150 | color: rgb(249,246,242);
151 | font-size: 40px;
152 | border: 1px solid rgba(238, 228, 218, 0.5);
153 | box-shadow: 0 0 25px 5px rgb(237, 204, 97);
154 | }
155 |
156 | .cell-num-2048 {
157 | background-color: rgb(237, 204, 97);
158 | color: rgb(249,246,242);
159 | font-size: 40px;
160 | border: 1px solid rgba(238, 228, 218, 0.5);
161 | box-shadow: 0 0 25px 5px rgb(237, 204, 97);
162 | }
163 |
164 |
165 | @media (max-width: 480px){
166 | .board-area {
167 | width: 280px;
168 | height: 280px;
169 | }
170 | .cell-pos, .cell {
171 | width: 60px;
172 | height: 60px;
173 | }
174 |
175 | .cell {
176 | font-size: 25px;
177 | line-height: 60px;
178 | }
179 |
180 | .cell-num-128, .cell-num-256, .cell-num-512 {
181 | font-size: 26px;
182 | }
183 |
184 | .cell-num-1024, .cell-num-2048 {
185 | font-size: 21px;
186 | }
187 |
188 | .pos-top-0 { top: 8px; }
189 | .pos-top-1 { top: 76px; }
190 | .pos-top-2 { top: 144px; }
191 | .pos-top-3 { top: 212px; }
192 |
193 | .pos-left-0 { left: 8px; }
194 | .pos-left-1 { left: 76px; }
195 | .pos-left-2 { left: 144px; }
196 | .pos-left-3 { left: 212px; }
197 |
198 | }
199 |
200 |
201 |
--------------------------------------------------------------------------------
/example-resources/public/devcards/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/example_src/devdemos/css_opt_out.cljs:
--------------------------------------------------------------------------------
1 | (ns devdemos.css-opt-out
2 | (:require
3 | [sablono.core :as sab :include-macros true]
4 | [devcards.core :as dc]
5 | [clojure.string :as string])
6 | (:require-macros
7 | [devcards.core :refer [defcard]]))
8 |
9 | (defcard
10 | "# Devcards CSS
11 |
12 | Devcards inlines its own CSS into the `` of the HTML document that
13 | hosts your cards. This is done because it is awkward to get and
14 | include CSS and other assets from a CLJS library (in a jar file) into an HTML file.
15 |
16 | Inlining CSS into the document makes the initial setup of Devcards much easier.
17 |
18 | There are four CSS files that are included:
19 |
20 | * Devcards main CSS (for card headings, ui and navigation)
21 | * Devcards addons CSS (adding default typography styles to the card body, etc)
22 | * EDN highlighting CSS (for the built in EDN renderer)
23 | * Code highlighting CSS (for highlight.js)
24 |
25 | If you inspect `` tag of this document or of your Devcards UI you
26 | will see four `
54 | ```
55 |
56 | _I'm trying to keep extraneous CSS in the addons_
57 |
58 |
59 | You can find all the orginal CSS files here:
60 | [https://github.com/bhauman/devcards/blob/master/resources/public/devcards/css](https://github.com/bhauman/devcards/blob/master/resources/public/devcards/css)
61 |
62 | It's probably best to copy and edit the original CSS if you have any
63 | tricky CSS issues."
64 | )
65 |
66 |
--------------------------------------------------------------------------------
/example_src/devdemos/custom_cards.cljs:
--------------------------------------------------------------------------------
1 | (ns devdemos.custom-cards
2 | (:require
3 | [devcards.core :as dc]
4 | [sablono.core :as sab :include-macros true])
5 | (:require-macros
6 | [devcards.core :refer [defcard defcard-doc]]))
7 |
8 | (defcard
9 | "# Extending Devcards
10 |
11 | There are several ways to get custom behavior out of devcards.
12 |
13 | * create a React Component
14 | * create an instance of `devcards.core/IDevcardOptions`
15 | * create an instance of `devcards.core/IDevcard`
16 |
17 | Implementing a React Component is the most straightforward way to
18 | create some tooling on top of Devcards.
19 |
20 | Here is a sketch of such usage:
21 |
22 | ```clojure
23 | (defn graph-state-overtime [state-atom state-filter]
24 | ...
25 | code that does some cool graphing
26 | ...)
27 |
28 | (defcard state-over-time-view
29 | (fn [state _]
30 | ;; return a ReactElement
31 | (sab/html
32 | [:div
33 | (your-component state)
34 | (graph-state-overtime state :x)]))
35 | ;; initial state
36 | {:x 0})
37 | ```
38 |
39 | You can create a macro to make the above composition much more
40 | convenient to use.
41 |
42 | ```clojure
43 | ;; in a clj namespace
44 | (defmacro def-state-plot-card [vname component init-state filter-fn]
45 | `(defcard ~vname
46 | (fn [state# _]
47 | (sab/html
48 | [:div
49 | (~component state)
50 | (graph-state-overtime state# ~filter-fn)]])
51 | ~init-state))
52 |
53 | ;; use this in a cljs namespace
54 | (def-state-plot-card cool-state-plot
55 | my-component
56 | {:x 0}
57 | :x)
58 | ```
59 |
60 | ## Reaching into Devcards arguments with IDevcardOptions
61 |
62 | Using React components works great if you are focusing on the content
63 | area of a card. But sometimes you want to leverage the argument
64 | parsing of the `defcard` macro and the functionality of the base
65 | devcard system itself.
66 |
67 | If you want to intercept the arguments that the `defcard` macro has
68 | parsed and alter them. You can do this with
69 | `devcards.core/IDevcardOptions`.
70 |
71 | Let's use `IDevcardOptions` to discover the options that are passed
72 | to its protocol method `-devcard-options`.
73 |
74 | ```clojure
75 | (defcard devcard-options-example-name
76 | \"Devcard options documentation.\"
77 | (reify dc/IDevcardOptions
78 | (-devcard-options [_ opts]
79 | (assoc opts :main-obj opts))) ;; <-- alter :main-obj to be the passed in opts
80 | {:devcards-options-init-state true})
81 | ```
82 |
83 | This is a bit subtle but implementing `IDevcardOptions` allows us to
84 | intercept the arguments that have been passed to the original
85 | `defcard`. You can see resulting card rendered below:
86 | ")
87 |
88 | (defcard devcard-options-example-namee
89 | "Devcard options documentation."
90 | (reify dc/IDevcardOptions
91 | (-devcard-options [_ opts]
92 | (assoc opts :main-obj opts)))
93 | {:devcards-options-init-state true})
94 |
95 | (defcard
96 | "As you can see above, we are intercepting the options that were
97 | parsed out by the `defcard` macro and we have a chance to operate on
98 | them before they are sent to the underlying system.
99 |
100 | These options are:
101 |
102 | * `:name` - the name of the card (changing this affects nothing)
103 | * `:documentation` - the docs associated with the card
104 | * `:main-obj` - the main object that is subject of display
105 | * `:initial-data` - the initial data for the card state atom
106 | * `:options` - the options for the devcard system (like `:inspect-data true`)
107 | * `:path` - the path to the card in the devcards interface (normally `[ns var]`)
108 |
109 | You can alter any of these args before returning the `opts`.
110 |
111 | Here is an example where we change the path name of a card.
112 |
113 | ```clojure
114 | (defcard example-2
115 | (reify dc/IDevcardOptions
116 | (-devcard-options [_ opts]
117 | (assoc opts :path
118 | [:devdemos.custom_cards
119 | :this-is-a-changed-path-name]))))
120 | ```
121 |
122 | And you can see this card rendered below:
123 |
124 | ")
125 |
126 |
127 | (defcard example-2
128 | (reify dc/IDevcardOptions
129 | (-devcard-options [_ opts]
130 | (assoc opts
131 | :path
132 | [:devdemos.custom_cards
133 | :this-is-a-changed-path-name]))))
134 |
135 | (defcard
136 | "The above card's heading has been altered from `example-2` to
137 | `this-is-a-changed-path-name`. We could have changed the `ns` part
138 | of the path name but then the card would no longer be on this page!
139 |
140 | You may have noticed that we are getting a JavaScript Object of some sort
141 | rendered in the body of the card. This is because the `:main-obj` is
142 | the reified instance of IDevcardOptions. This is where the magic comes in, you can
143 | specify any `:main-obj` you like.
144 |
145 | Here's an example where we create a `state-reset` control that we can
146 | use in our cards.
147 |
148 | ```clojure
149 | (defn state-reset [component]
150 | (reify dc/IDevcardOptions
151 | (-devcard-options [_ opts]
152 | (assoc opts
153 | :main-obj
154 | (fn [state owner]
155 | (sab/html
156 | [:div
157 | [:button
158 | {:onClick
159 | (fn []
160 | (reset! state (:initial-data opts)))}
161 | \"reset state\"]
162 | (if (fn? component)
163 | (component state owner)
164 | component)]))))))
165 |
166 | (defn counter [state]
167 | (sab/html
168 | [:div
169 | [:h3 \"Counter : \" (:count @state)]
170 | [:button {:onClick #(swap! state (fn [s] (update-in s [:count] inc)))}
171 | \"inc\"]]))
172 |
173 | (defcard counter-example
174 | (state-reset (fn [state _] (counter state)))
175 | {:count 0}
176 | {:inspect-data true})
177 | ```
178 |
179 | You can see the resulting card below. You can increment the example
180 | counter and then easily reset the state of the counter to the initial
181 | value.
182 | ")
183 |
184 | (defn state-reset [component]
185 | (reify dc/IDevcardOptions
186 | (-devcard-options [_ opts]
187 | (assoc opts
188 | :main-obj
189 | (fn [state owner]
190 | (sab/html
191 | [:div
192 | [:button
193 | {:onClick
194 | (fn []
195 | (reset! state (:initial-data opts)))}
196 | "reset state"]
197 | (if (fn? component)
198 | (component state owner)
199 | component)]))))))
200 |
201 | (defn counter [state]
202 | (sab/html
203 | [:div
204 | [:h3 "Counter : " (:count @state)]
205 | [:button {:onClick #(swap! state (fn [s] (update-in s [:count] inc)))}
206 | "inc"]]))
207 |
208 | (defcard counter-example
209 | (state-reset (fn [state _] (counter state)))
210 | {:count 0}
211 | {:inspect-data true})
212 |
213 | (defcard
214 | "There are other ways to accomplish this same goal. I just wanted to
215 | demonstrate that `IDevcardOptions` can offer a bit of flexibility
216 | when creating tools for devcards.
217 |
218 | Don't forget you can wrap all of this in a macro to make expressive
219 | consice tools for your workflow.")
220 |
221 | (defcard
222 | "## The `IDevcard` protocol
223 |
224 | If you want to escape the base functionality of Devcards and render
225 | your own card with it's own functionality. The `devcards.core/IDevcard`
226 | protocol can help.
227 |
228 | The protocal is simple you just need to implement a function called
229 | `devcards.core/-devcard` and return a `ReactElement`.
230 |
231 | ```clojure
232 | (defcard
233 | (reify dc/IDevcard
234 | (-devcard [_ opts]
235 | (sab/html [:h3 \"This is a card without any base Devcard functionality\"]))))
236 | ```
237 | ")
238 |
239 | (defcard idevcard-example
240 | (reify dc/IDevcard
241 | (-devcard [_ opts]
242 | (sab/html [:h3 "This is a card without any base Devcard functionality"]))))
243 |
244 | (defcard
245 | "## Low level api `devcards.core/register-card`
246 |
247 | You can create a card with the low level api
248 | `devcards.core/register-card`. This function takes a map with two keys:
249 |
250 | * `:path` - the path to the card in the devcards interface (normally [ns var])
251 | * `:func` - a thunk (function of no args) that returns a ReactElement
252 |
253 | Here is an example of using `register-card`:
254 |
255 | ```clojure
256 | (dc/register-card
257 | {:path [:devdemos.custom_cards
258 | :registered-card]
259 | :func (fn [] (sab/html [:h1 \"** Registered card **\"]))})
260 | ```
261 |
262 | You can see this in action below
263 |
264 | ")
265 |
266 | (dc/register-card
267 | {:path [:devdemos.custom_cards
268 | :registered-card]
269 | :func (fn [] (sab/html [:h1 "** Registered card **"]))})
270 |
271 | (defcard
272 | "## The `devcards.core/defcard*` macro
273 |
274 | The `devcard*` macro makes it easy to bypass base `devcard`
275 | functionality.
276 |
277 | It is defined as so:
278 |
279 | ```clojure
280 | (defmacro defcard*
281 | ([vname expr]
282 | (when (utils/devcards-active?)
283 | `(devcards.core/register-card ~{:path (name->path &env vname)
284 | :func `(fn [] ~expr)}))))
285 | ```
286 |
287 | As you can see it uses the `register-card` function but also captures
288 | path information. The `defcard*` macro can be helpful when composing
289 | your own macros for Devcards.
290 |
291 |
292 | ")
293 |
--------------------------------------------------------------------------------
/example_src/devdemos/defcard_api.cljs:
--------------------------------------------------------------------------------
1 | (ns devdemos.defcard-api
2 | (:require
3 | [devcards.core]
4 | [om.core :as om :include-macros true]
5 | [om.dom :as dom :include-macros true]
6 | [reagent.core :as reagent]
7 | [clojure.string :as string]
8 | [sablono.core :as sab :include-macros true]
9 | [cljs.test :as t :include-macros true :refer-macros [testing is]])
10 | (:require-macros
11 | ;; Notice that I am not including the 'devcards.core namespace
12 | ;; but only the macros. This helps ensure that devcards will only
13 | ;; be created when the :devcards is set to true in the build config.
14 | [devcards.core :as dc :refer [defcard defcard-doc noframe-doc deftest dom-node]]))
15 |
16 | (defcard-doc
17 | "#It all starts with `defcard`
18 |
19 | Once you have Devcards setup and have required the devcards macros as below
20 | ```clojure
21 | (:require-macros
22 | [devcards.core :as dc :refer [defcard]])
23 | ```
24 | You can then use the `defcard` macro. `defcard` is a multipurpose
25 | macro which is designed to take what you are working on elevate
26 | live into the browser. It can handle many types of data but
27 | primarily takes any type of ReactElement.
28 |
29 | So this would be the \"Hello World\" of Devcards,"
30 |
31 | '(defcard (sab/html [:h3 "Hello world"]))
32 |
33 | "You can see this devcard rendered below:")
34 |
35 | (defcard (sab/html [:h3 "Hello World!"]))
36 |
37 | (defcard
38 | "# These cards are hot ... loaded
39 |
40 | One thing that isn't easy to see from reading this page is that when
41 | you define a Devcard in your code and save it, a card instantly
42 | appears in the Devcards interface. It shows up on the page in the
43 | order of its definition, and when you comment out or delete the
44 | card from your code it dissapears from the interface.
45 |
46 | ## `defcard` takes 5 arguments
47 |
48 | * **name** an optional symbol name for the card to be used as a heading and to
49 | locate it in the Devcards interface
50 | * **documentation** an optional string literal of markdown documentation
51 | * **main object** a required object for the card to display
52 | * **initial data** an optional Atom, RAtom or Clojure data structure (normally
53 | a Map), used to initialize the a state that the devcard will pass back to
54 | your code examples. More on this later ...
55 | * **devcard options** an optional map of options for the underlying devcard
56 |
57 | ```
58 | (defcard hello-world ;; optional symbol name
59 | \"**Optional Mardown documentation**\" ;; optional literal string doc
60 | {:object \"of focus\"} ;; required object of focus
61 | {} ;; optional intial data
62 | {} ;; optional devcard config
63 | )
64 | ```
65 |
66 | We are going to explore these arguments and examples of how they work
67 | below.
68 |
69 | This is pretty *meta* as I am using Devcards to document Devcards.
70 | So please take this page as an example of **interactive literate
71 | programming**. Where you create a story about your code, supported
72 | by live examples of how it works.
73 |
74 | ## What's in a name?
75 |
76 | The first optional arg to `defcard` is the **name**. This is a
77 | symbol and it is used to provide a distinct key for the card
78 | you are creating.
79 |
80 | For cards that aren't stateful like documentation and such the name
81 | really isn't necessary but when you create cards that are displaying
82 | stateful running widgets then this key will help ensure that the
83 | underlying state is mapped back to the correct card.
84 |
85 | The name will be used to create a header on the card. The header can
86 | be clicked to display and work on the card by itself.
87 |
88 | For instance here is a card with a name:
89 |
90 | ```
91 | (defcard first-example)
92 | ```
93 |
94 | You can see this card with its header just below. If you click on
95 | the `first-example` card header, you will be presented with the card
96 | by itself, so that you can work on the card in isolation.
97 |
98 |
99 | ")
100 |
101 | (defcard first-example
102 | (sab/html [:div])
103 | {}
104 | {:heading true})
105 |
106 | (defcard-doc
107 | "## Name absentia
108 |
109 | In the absense of a name, the heading of the card will not be displayed.
110 |
111 | Devcards generate's a card name in the order that it shows up on
112 | the page. You can see this autogenerated name by setting the
113 | `:heading` option to `true`.
114 |
115 | ```
116 | (defcard {} ; main obj
117 | {} ; initial data
118 | {:heading true}) ; devcard options: forces header to show
119 | ```
120 |
121 | Which is displayed as so:")
122 |
123 | (defcard {} {} {:heading true})
124 |
125 | (defcard
126 | "The generated card name above is *card-4*. This makes sense
127 | because it's the fouth card on the page that has no name.
128 |
129 | The generated card name will work in many cases but not for all.
130 | It's best to have a name for cards with state.")
131 |
132 | (defcard
133 | "## Optional markdown doc
134 |
135 | You can also add an optional markdown documentation to your card like this:
136 | ```
137 | (defcard example-2 \"## Example: This is optional markdown\")
138 | ```
139 | ")
140 |
141 | (defcard example-2 "## Example: This is optional markdown")
142 |
143 | (defcard
144 | "Since the name `example-2` is optional you can write docs just like this:
145 |
146 | ```
147 | (defcard
148 | \"## Example: writing markdown docs is intended to be easy.
149 |
150 | You should be able to add docs to your code examples easily.\")
151 | ```")
152 |
153 | (defcard-doc
154 | "# The object of our attention
155 |
156 | The main object that we are displaying comes after the optional
157 | **name** and **documentation** and it will be displayed in the
158 | **body** of the card.
159 |
160 | As mentioned before this object can be many things but perhaps most
161 | importantly it can be a ReactElement.
162 |
163 | For example this is valid:"
164 |
165 | (dc/mkdn-pprint-code
166 | '(defcard react-example (sab/html [:h3 "Example: Rendering a ReactElement"])))
167 |
168 | "Above we simply passed a ReactElement created by `sablono` to `defcard`
169 | and it gets rendered as the following card:")
170 |
171 | (defcard react-example (sab/html [:h3 "Example: Rendering a ReactElement"]))
172 |
173 | (defcard
174 | "## A string is interpreted as markdown
175 |
176 | In the example below we are not using a string literal so the first
177 | arg is really the main object and because it is of type `string` it
178 | will be interpreted as markdown.
179 |
180 | ```
181 | (defcard (str \"This is the main object and it **will** be interpreted as markdown\"))
182 | ```
183 | ")
184 |
185 |
186 | (defcard (str "This is the main object and **will** be interpreted as markdown"))
187 |
188 | (defcard
189 | "## Many types are displayed as edn
190 |
191 | As we are programming we often want to see the result of an
192 | evaluation, for this reason `defcard` will display many types of
193 | data as edn.
194 |
195 | This is a growing list of items but right now look at the following examples:")
196 |
197 | (defcard
198 | "**Map**s are displayed as edn:
199 | ```
200 | (defcard {:this \"is a map\"})
201 | ```"
202 | {:this "is a map"})
203 |
204 | (defcard
205 | "**Vector**s are displayed as edn:
206 |
207 | ```
208 | (defcard [\"This\" \"is\" \"a\" \"vector\"])
209 | ```"
210 | (string/split "This is a vector" #"\s" ))
211 |
212 | (defcard
213 | "**Set**s are displayed as edn
214 |
215 | ```
216 | (defcard #{1 2 3})
217 | ```"
218 | #{1 2 3})
219 |
220 | (defcard
221 | "**List**s are displayed as edn
222 |
223 | ```
224 | (defcard (list 1 2 3))
225 | ```"
226 | (list 1 2 3))
227 |
228 | (defcard
229 | "## Atoms are displayed as observed edn
230 |
231 | When you pass an atom to `defcard` as the main object its contents
232 | will be rendered as edn. And when the atom changes so will the
233 | displayed edn.
234 |
235 | ```
236 | (defonce observed-atom
237 | (let [a (atom 0)]
238 | (js/setInterval (fn [] (swap! observed-atom inc)) 1000)
239 | a))
240 |
241 | (defcard atom-observing-card observed-atom)
242 | ```
243 |
244 | This will produce the timer card that you can see below:")
245 |
246 | (defonce observed-atom
247 | (let [a (atom {:time 0})]
248 | (js/setInterval (fn [] (swap! observed-atom update-in [:time] inc)) 1000)
249 | a))
250 |
251 | (defcard atom-observing-card observed-atom {} {:history false})
252 |
253 | (defcard-doc
254 | "## A function as a main object
255 |
256 | The main point of devcards is to get your code out of the source
257 | file and up and running in front of you as soon as possible. To this
258 | end devcards tries to provide several generic ways for you to run
259 | your code in the devcards interface. The main way is to pass a
260 | function to the `defcard` macro as the main object.
261 |
262 | Instead of a ReactElement you can provide a function the takes two
263 | parameters and returns a ReactElement like so:"
264 |
265 | (dc/mkdn-pprint-code
266 | '(defcard (fn [data-atom owner]
267 | (sab/html [:div [:h2 "Example: fn that returns React"]
268 | (prn-str data-atom)]))))
269 | "In this example the `data-atom` is a ClojureScript Atom and
270 | the`owner` is the enclosing cards ReactElement.")
271 |
272 | (defcard
273 | (fn [data-atom owner]
274 | (sab/html [:div [:h3 "Example: fn that returns React"]
275 | (prn-str data-atom)])))
276 |
277 | (defcard-doc
278 | "If `data-atom` in the above example changes then the card will be re-rendered.
279 |
280 | Let's make a quick example counter:"
281 | (dc/mkdn-pprint-code
282 | '(defcard
283 | (fn [data-atom owner]
284 | (sab/html [:div [:h3 "Example Counter: " (:count @data-atom)]
285 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]])))))
286 |
287 | (defcard
288 | (fn [data-atom owner]
289 | (sab/html [:div [:h3 "Example Counter: " (:count @data-atom)]
290 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]])))
291 |
292 | (defcard-doc
293 | "## Initial state
294 |
295 | The counter example above was very interesting but what if you want
296 | to introduce some initial state?
297 |
298 | Well the next option after the main object is the **initial-data**
299 | parameter. You can use it like so:"
300 | (dc/mkdn-pprint-code
301 | '(defcard
302 | (fn [data-atom owner]
303 | (sab/html [:div [:h3 "Example Counter w/Initial Data: " (:count @data-atom)]
304 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]]))
305 | {:count 50})))
306 |
307 | (defcard
308 | (fn [data-atom owner]
309 | (sab/html [:div [:h3 "Example Counter w/Initial Data: " (:count @data-atom)]
310 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]]))
311 | {:count 50})
312 |
313 | (defcard-doc
314 | "## Initial state can be an Atom
315 |
316 | You can also pass an Atom as the initial state. This is a very
317 | important feature of devcards as it allows you to share state
318 | between cards.
319 |
320 | The following examples share state:"
321 |
322 | (dc/mkdn-pprint-code
323 | '(defonce first-example-state (atom {:count 55})))
324 |
325 | (dc/mkdn-pprint-code
326 | '(defcard example-counter
327 | (fn [data-atom owner]
328 | (sab/html [:h3 "Example Counter w/Shared Initial Atom: " (:count @data-atom)]))
329 | first-example-state))
330 |
331 | (dc/mkdn-pprint-code
332 | '(defcard example-incrementer
333 | (fn [data-atom owner]
334 | (sab/html [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "increment"]))
335 | first-example-state))
336 |
337 | (dc/mkdn-pprint-code
338 | '(defcard example-decrementer
339 | (fn [data-atom owner]
340 | (sab/html [:button {:onClick (fn [] (swap! data-atom update-in [:count] dec))} "decrement"]))
341 | first-example-state))
342 | "As you can see, we created three cards that all share the same state.
343 |
344 | If you try these example cards below you will see that they are all wired together:")
345 |
346 | (defonce first-example-state (atom {:count 55}))
347 |
348 | (defcard example-counter
349 | (fn [data-atom owner]
350 | (sab/html [:h3 "Example Counter w/Shared Initial Atom: " (:count @data-atom)]))
351 | first-example-state)
352 |
353 | (defcard example-incrementer
354 | (fn [data-atom owner]
355 | (sab/html [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc)) } "increment"]))
356 | first-example-state)
357 |
358 | (defcard example-decrementer
359 | (fn [data-atom owner]
360 | (sab/html [:button {:onClick (fn [] (swap! data-atom update-in [:count] dec)) } "decrement"]))
361 | first-example-state)
362 |
363 | (defcard
364 | "# Reseting the state of a card
365 |
366 | The **initial state** is just the initial state of the card. What if
367 | you want to reset the card and start from the initial state or some
368 | new initial state?
369 |
370 | There is a simple trick: you just change the name of the card. I
371 | often add and remove a `*` at the end of a card name to bump the
372 | state out of the card in read in a new initial state.
373 |
374 | I am debating adding knobs for these things to the heading panel of
375 | the card. A knob to reset the state, a knob to turn on history, a
376 | knob to display the data in the atom. Let me know if you think this
377 | is a good idea.")
378 |
379 | (defcard
380 | "# Devcard options
381 |
382 | The last argument to `defcard` is an optional map of options.
383 |
384 | Here are the available options with their defaults:
385 |
386 | ```
387 | {
388 | :frame true ;; whether to enclose the card in a padded frame
389 | :heading true ;; whether to add a heading panel to the card
390 | :padding true ;; whether to have padding around the body of the card
391 | :hidden false ;; whether to diplay the card or not
392 | :inspect-data false ;; whether to display the data in the card atom
393 | :watch-atom true ;; whether to watch the atom and render on change
394 | :history false ;; whether to record a change history of the atom
395 | :classname \"\" ;; provide card with a custom classname
396 | :projection identity ;; provide a projection function for card state
397 | }
398 | ```
399 |
400 | Most of these are fairly straight forward. Whats important to know
401 | is that you can change any of these live and the card will respond
402 | with the new behavior.
403 |
404 | Here are some cards that exercise these options:")
405 |
406 | (defcard no-framed
407 | (str "## This is a devcard
408 |
409 | And it doesn't have a frame")
410 | {}
411 | {:frame false})
412 |
413 | (defcard no-heading
414 | (str "# this card is hiding its heading")
415 | {}
416 | {:heading false})
417 |
418 | (defcard no-padding
419 | (str " this card is has no padding on its body")
420 | {}
421 | {:padding false})
422 |
423 | (defcard custom-classname
424 | (str " this card has a custom class `.red-box`")
425 | {}
426 | {:classname "red-box"})
427 |
428 | (defcard inspect-data
429 | (fn [data-atom owner]
430 | (sab/html [:div [:h3 "Inspecting data on this Counter: " (:count @data-atom)]
431 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]]))
432 | {:count 50}
433 | {:inspect-data true})
434 |
435 | (defcard inspect-data-and-record-history
436 | (fn [data-atom owner]
437 | (sab/html [:div [:h3 "Inspecting data and recording history this Counter: " (:count @data-atom)]
438 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]]))
439 | {:count 50}
440 | {:inspect-data true :history true})
441 |
442 | (defcard project-data
443 | (fn [data-atom _]
444 | (sab/html [:div [:h3 "Inspecting data but only some of the data: Counter" (:count @data-atom)]
445 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]
446 | [:div "Random other state that is not important: " (:whatever @data-atom)]]))
447 | {:count 50
448 | :whatever "this state is present but not shown in `inspect-data` part."}
449 | {:inspect-data true
450 | :projection (fn [state] (select-keys state [:count]))})
451 |
452 |
453 | (defcard-doc
454 | "## Accessing the DOM with `dom-node`
455 |
456 | While Devcards was written in and are very easy to use in
457 | conjunction with React. You may want to write something that writes
458 | directly to the DOM.
459 |
460 | The helper macro `dom-node` takes a function that accepts a DOM
461 | node and ClojureScript Atom and returns a ReactElement."
462 |
463 | (dc/mkdn-pprint-code
464 | '(defcard example-dom-node
465 | (dom-node (fn [data-atom node]
466 | (set! (.-innerHTML node) "