Skip to content

Commit b705bcf

Browse files
Create CreatingANewComponent.md
1 parent d93e43c commit b705bcf

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

docs/docs/CreatingANewComponent.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Creating a re-com Component
2+
3+
This guide distils the common patterns found in the `re-com` source tree and explains how to create your own component. It assumes you are familiar with ClojureScript and Reagent.
4+
5+
## 1. Namespace setup
6+
7+
Create a new namespace under `src/re_com`. Components generally require several macros and utilities:
8+
9+
```clojure
10+
(ns re-com.my-component
11+
(:require-macros [re-com.core :refer [handler-fn at reflect-current-component]]
12+
[re-com.validate :refer [validate-args-macro]])
13+
(:require [re-com.debug :refer [->attr]]
14+
[re-com.theme :as theme]
15+
[re-com.util :refer [deref-or-value]]
16+
[re-com.config :refer [include-args-desc?]]))
17+
```
18+
19+
* `handler-fn` ensures event handlers do not accidentally return `false` which React interprets specially. See the commentary in `core.clj` lines 3‑32 for details.
20+
* `at` and `reflect-current-component` supply debugging metadata such as source coordinates and the current component name.
21+
* `validate-args-macro` performs argument validation when `goog.DEBUG` is enabled.
22+
23+
## 2. Describe component parts
24+
25+
Most components document their internal structure with a vector of maps known as `parts-desc` and a derived set `parts`. These are only included when `include-args-desc?` is true so that production builds remain lean.
26+
27+
An example from `buttons.cljs` lines 20‑28 illustrates the idea:
28+
29+
```clojure
30+
(def button-parts-desc
31+
(when include-args-desc?
32+
[{:name :wrapper :level 0 :class "rc-button-wrapper" :impl "[button]" :notes "Outer wrapper of the button, tooltip (if any), everything."}
33+
{:name :tooltip :level 1 :class "rc-button-tooltip" :impl "[popover-tooltip]" :notes "Tooltip, if enabled."}
34+
{:type :legacy :level 1 :class "rc-button" :impl "[:button]" :notes "The actual button."}]))
35+
36+
(def button-parts
37+
(when include-args-desc?
38+
(-> (map :name button-parts-desc) set)))
39+
```
40+
41+
Consumers can customise a component via the optional `:parts` argument by providing classes, styles or attrs keyed by these part names.
42+
43+
## 3. Document arguments
44+
45+
Each component declares an `args-desc` describing supported parameters, default values and validation functions. Again from `buttons.cljs` lines 30‑42:
46+
47+
```clojure
48+
(def button-args-desc
49+
(when include-args-desc?
50+
[{:name :label :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "label for the button"}
51+
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "called when the button is clicked"}
52+
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
53+
...]))
54+
```
55+
56+
The maps typically contain keys such as `:name`, `:required`, `:default`, `:type`, `:validate-fn` and `:description`. Validation helpers live in `re-com.validate` and include sets of allowed keywords, CSS style checks and so on.
57+
58+
## 4. Implement the component
59+
60+
A component function is usually form‑2 or form‑3. It begins with `validate-args-macro` to check its arguments and then produces hiccup markup. The button component demonstrates the pattern in lines 44‑91:
61+
62+
```clojure
63+
(defn button []
64+
(let [showing? (reagent/atom false)]
65+
(fn [& {:keys [label on-click tooltip tooltip-position disabled? class style attr parts src debug-as]
66+
:or {class "btn-default"}
67+
:as args}]
68+
(or
69+
(validate-args-macro button-args-desc args)
70+
(do
71+
(when-not tooltip (reset! showing? false))
72+
(let [disabled? (deref-or-value disabled?)
73+
the-button [:button
74+
(merge
75+
{:class (str "rc-button btn " class)
76+
:style (merge (flex-child-style "none") style)
77+
:disabled disabled?
78+
:on-click (handler-fn
79+
(when (and on-click (not disabled?))
80+
(on-click event)))}
81+
(when tooltip
82+
{:on-mouse-over (handler-fn (reset! showing? true))
83+
:on-mouse-out (handler-fn (reset! showing? false))})
84+
attr)
85+
label]]
86+
[box
87+
:src src
88+
:debug-as (or debug-as (reflect-current-component))
89+
:class (str "rc-button-wrapper display-inline-flex " (get-in parts [:wrapper :class]))
90+
:style (get-in parts [:wrapper :style])
91+
:attr (get-in parts [:wrapper :attr])
92+
:align :start
93+
:child (if tooltip
94+
[popover-tooltip
95+
:src (at)
96+
:label tooltip
97+
:position (or tooltip-position :below-center)
98+
:showing? showing?
99+
:anchor the-button
100+
:class (str "rc-button-tooltip " (get-in parts [:tooltip :class]))
101+
:style (get-in parts [:tooltip :style])
102+
:attr (get-in parts [:tooltip :attr])]
103+
the-button)]))))))
104+
```
105+
106+
Key takeaways:
107+
108+
* `validate-args-macro` returns a diagnostic component if validation fails.
109+
* `handler-fn` wraps event handlers to avoid accidentally returning `false`.
110+
* `deref-or-value` handles parameters that might be plain values or atoms.
111+
* `->attr` injects debugging attributes (`data-rc` and source coordinates) into the root element.
112+
113+
## 5. Exposing your component
114+
115+
To make the component part of the public API, add a simple `def` in `re_com/core.cljs` that re-exports it. For example, lines 64‑71 show how various button helpers are exposed:
116+
117+
```clojure
118+
(def button buttons/button)
119+
(def md-circle-icon-button buttons/md-circle-icon-button)
120+
(def md-icon-button buttons/md-icon-button)
121+
(def info-button buttons/info-button)
122+
(def row-button buttons/row-button)
123+
(def hyperlink buttons/hyperlink)
124+
```
125+
126+
Consumers then `require` `re-com.core` and call your function by its public name.
127+
128+
## 6. Validation internals
129+
130+
The validation system is implemented in `re_com.validate`. The `validate-args` function (lines 138‑170) performs several checks:
131+
132+
1. Unknown or misspelled argument names.
133+
2. Missing required arguments.
134+
3. Custom validation functions per argument.
135+
136+
If any problems are detected a special component renders in place of your component and details are logged to the console.
137+
138+
```clojure
139+
(defn validate-args [arg-defs passed-args]
140+
(if-not debug?
141+
nil
142+
(let [{:keys [parts-validate-fn]} arg-defs
143+
passed-arg-keys (set (remove #{:theme :re-com :part} (set (keys passed-args))))
144+
problems (as-> [] pvec
145+
(if-not parts-validate-fn
146+
pvec
147+
(parts-validate-fn passed-args pvec))
148+
(arg-names-known? (:arg-names arg-defs) passed-arg-keys pvec)
149+
(required-args? (:required-args arg-defs) passed-arg-keys pvec)
150+
(validate-fns? (:validated-args arg-defs) passed-args pvec)
151+
(remove nil? pvec))]
152+
(when-not (empty? problems)
153+
[debug/validate-args-error
154+
:problems problems
155+
:args passed-args
156+
:component (debug/short-component-name (component/component-name (reagent/current-component)))]))))
157+
```
158+
159+
## 7. Putting it all together
160+
161+
1. **Create** a namespace and require the helper macros and functions.
162+
2. **Define** `parts-desc`, `parts` and `args-desc` to describe your API.
163+
3. **Write** the component using Reagent hiccup, starting with `(validate-args-macro ...)`.
164+
4. **Use** `handler-fn`, `deref-or-value`, `->attr` and other utilities as needed.
165+
5. **Expose** the component via `re_com.core` for library consumers.
166+
167+
Following these conventions keeps new components consistent with the rest of `re-com` and ensures a good debugging experience.

0 commit comments

Comments
 (0)