native-document 1.0.166 → 1.0.169

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.vitepress/config.js +166 -0
  2. package/CHANGELOG.md +187 -0
  3. package/components.js +4 -1
  4. package/dist/native-document.components.min.js +593 -300
  5. package/dist/native-document.dev.js +99 -75
  6. package/dist/native-document.dev.js.map +1 -1
  7. package/dist/native-document.min.js +1 -1
  8. package/docs/advanced-components.md +213 -608
  9. package/docs/anchor.md +173 -312
  10. package/docs/cache.md +95 -803
  11. package/docs/cli.md +179 -0
  12. package/docs/components/accordion.md +172 -0
  13. package/docs/components/alert.md +99 -0
  14. package/docs/components/avatar.md +160 -0
  15. package/docs/components/badge.md +102 -0
  16. package/docs/components/breadcrumb.md +89 -0
  17. package/docs/components/button.md +183 -0
  18. package/docs/components/card.md +69 -0
  19. package/docs/components/context-menu.md +118 -0
  20. package/docs/components/data-table.md +345 -0
  21. package/docs/components/dropdown.md +214 -0
  22. package/docs/components/form/autocomplete-field.md +81 -0
  23. package/docs/components/form/checkbox-field.md +41 -0
  24. package/docs/components/form/checkbox-group-field.md +54 -0
  25. package/docs/components/form/color-field.md +64 -0
  26. package/docs/components/form/date-field.md +92 -0
  27. package/docs/components/form/field-collection.md +63 -0
  28. package/docs/components/form/file-field.md +203 -0
  29. package/docs/components/form/form-control.md +87 -0
  30. package/docs/components/form/image-field.md +90 -0
  31. package/docs/components/form/index.md +115 -0
  32. package/docs/components/form/number-field.md +65 -0
  33. package/docs/components/form/radio-field.md +51 -0
  34. package/docs/components/form/select-field.md +123 -0
  35. package/docs/components/form/slider.md +136 -0
  36. package/docs/components/form/string-field.md +134 -0
  37. package/docs/components/form/textarea-field.md +65 -0
  38. package/docs/components/form-fields.md +372 -0
  39. package/docs/components/getting-started.md +264 -0
  40. package/docs/components/icons.md +321 -0
  41. package/docs/components/index.md +337 -0
  42. package/docs/components/layout.md +279 -0
  43. package/docs/components/list.md +73 -0
  44. package/docs/components/menu.md +215 -0
  45. package/docs/components/modal.md +156 -0
  46. package/docs/components/pagination.md +95 -0
  47. package/docs/components/popover.md +131 -0
  48. package/docs/components/progress.md +111 -0
  49. package/docs/components/shortcut-manager.md +221 -0
  50. package/docs/components/simple-table.md +107 -0
  51. package/docs/components/skeleton.md +155 -0
  52. package/docs/components/spinner.md +100 -0
  53. package/docs/components/splitter.md +133 -0
  54. package/docs/components/stepper.md +163 -0
  55. package/docs/components/switch.md +113 -0
  56. package/docs/components/tabs.md +153 -0
  57. package/docs/components/toast.md +119 -0
  58. package/docs/components/tooltip.md +151 -0
  59. package/docs/components/traits.md +261 -0
  60. package/docs/conditional-rendering.md +170 -588
  61. package/docs/contributing.md +300 -25
  62. package/docs/core-concepts.md +205 -374
  63. package/docs/elements.md +251 -367
  64. package/docs/extending-native-document-element.md +192 -207
  65. package/docs/filters.md +153 -1122
  66. package/docs/getting-started.md +193 -267
  67. package/docs/i18n.md +241 -0
  68. package/docs/index.md +76 -0
  69. package/docs/lifecycle-events.md +143 -75
  70. package/docs/list-rendering.md +227 -852
  71. package/docs/memory-management.md +134 -47
  72. package/docs/native-document-element.md +337 -186
  73. package/docs/native-fetch.md +99 -630
  74. package/docs/observable-resource.md +364 -0
  75. package/docs/observables.md +592 -526
  76. package/docs/routing.md +244 -653
  77. package/docs/state-management.md +134 -241
  78. package/docs/svg-elements.md +231 -0
  79. package/docs/theming.md +409 -0
  80. package/docs/tutorials/.gitkeep +0 -0
  81. package/docs/validation.md +95 -97
  82. package/docs/vitepress-conventions.md +219 -0
  83. package/package.json +34 -13
  84. package/readme.md +269 -89
  85. package/src/components/card/Card.js +93 -39
  86. package/src/components/card/index.js +1 -1
  87. package/src/components/form/field/types/FileField.js +3 -3
  88. package/src/components/icon/Icon.js +107 -0
  89. package/src/components/icon/icon-getters.js +142 -0
  90. package/src/components/icon/icons.js +171 -0
  91. package/src/components/icon/index.js +17 -0
  92. package/src/components/icon/types/Icon.d.ts +191 -0
  93. package/src/components/index.d.ts +6 -1
  94. package/src/components/list/HasListItem.js +171 -0
  95. package/src/components/list/List.js +41 -107
  96. package/src/components/list/ListDivider.js +39 -0
  97. package/src/components/list/ListGroup.js +76 -59
  98. package/src/components/list/ListItem.js +117 -69
  99. package/src/components/list/index.js +3 -1
  100. package/src/components/list/types/List.d.ts +45 -32
  101. package/src/components/list/types/ListDivider.ts +16 -0
  102. package/src/components/list/types/ListGroup.d.ts +51 -29
  103. package/src/components/list/types/ListItem.d.ts +40 -30
  104. package/src/components/spacer/Spacer.js +1 -1
  105. package/src/core/data/ObservableResource.js +5 -0
  106. package/src/core/data/observable-helpers/observable.prototypes.js +2 -0
  107. package/src/core/elements/content-formatter.js +16 -2
  108. package/src/core/elements/form.js +1 -1
  109. package/src/core/elements/img.js +1 -1
  110. package/src/core/elements/medias.js +2 -2
  111. package/src/core/elements/meta-data.js +1 -1
  112. package/src/core/wrappers/AttributesWrapper.js +37 -22
  113. package/src/core/wrappers/ElementCreator.js +9 -16
  114. package/src/core/wrappers/HtmlElementWrapper.js +26 -7
  115. package/src/core/wrappers/NDElement.js +12 -1
  116. package/src/core/wrappers/prototypes/attributes-extensions.js +24 -24
  117. package/src/core/wrappers/prototypes/nd-element-extensions.js +1 -8
  118. package/src/ui/components/card/CardRender.js +133 -0
  119. package/src/ui/components/card/card.css +169 -0
  120. package/src/ui/components/contextmenu/ContextmenuRender.js +1 -1
  121. package/src/ui/components/icon/material/MaterialIconRender.js +71 -0
  122. package/src/ui/components/icon/material/material.css +15 -0
  123. package/src/ui/components/icon/material/material.map.js +170 -0
  124. package/src/ui/components/icon/phosphor/PhosphorIconRender.js +71 -0
  125. package/src/ui/components/icon/phosphor/phosphor.css +17 -0
  126. package/src/ui/components/icon/phosphor/phosphor.map.js +165 -0
  127. package/src/ui/components/icon/tabler/TablerIconRender.js +59 -0
  128. package/src/ui/components/icon/tabler/tabler.css +14 -0
  129. package/src/ui/components/icon/tabler/tabler.map.js +165 -0
  130. package/src/ui/components/list/ListRender.js +18 -0
  131. package/src/ui/components/list/divider/ListDividerRender.js +10 -0
  132. package/src/ui/components/list/divider/list-divider.css +12 -0
  133. package/src/ui/components/list/group/ListGroupRender.js +61 -0
  134. package/src/ui/components/list/group/list-group.css +62 -0
  135. package/src/ui/components/list/item/ListItemRender.js +238 -0
  136. package/src/ui/components/list/item/list-item.css +191 -0
  137. package/src/ui/components/list/list.css +24 -0
  138. package/src/ui/components/spacer/SpacerRender.js +10 -0
  139. package/src/ui/index.js +13 -0
@@ -0,0 +1,321 @@
1
+ ---
2
+ title: Icons
3
+ description: Adapter-based icon system with chainable API and support for Tabler, Phosphor, and Google Material Icons
4
+ ---
5
+
6
+ # Icons
7
+
8
+ NativeDocument provides an adapter-based icon system. Icon names are semantic and stable - switching from one icon library to another requires changing only the adapter and the CSS import.
9
+
10
+ ```javascript
11
+ // Usage - always via the Icon namespace
12
+ Icon.search.fill()
13
+ Icon.star.fill().large()
14
+ Icon.arrowRight.bold().color('red')
15
+ Icon.user.fill().size(32)
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Setup
21
+
22
+ ### 1. Register the adapter
23
+
24
+ In your app entry point (`main.js`):
25
+
26
+ ```javascript
27
+ import { Icon } from 'native-document/icons';
28
+ import TablerIconRender from 'native-document/ui/icon/tabler';
29
+
30
+ Icon.use(TablerIconRender, {
31
+ variant: 'outline', // default variant
32
+ size: 'medium', // default size: small | medium | large | extraLarge
33
+ });
34
+ ```
35
+
36
+ ### 2. Import the CSS
37
+
38
+ In the same entry point, import the icon font alongside your theme:
39
+
40
+ ```javascript
41
+ // main.js
42
+ import 'native-document/src/ui/theme.scss';
43
+ import '@tabler/icons-webfont/dist/tabler-icons.css';
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Methods
49
+
50
+ ### Variants
51
+
52
+ | Method | Sets variant to |
53
+ |---|---|
54
+ | `.thin()` | `'thin'` |
55
+ | `.light()` | `'light'` |
56
+ | `.regular()` | `'regular'` |
57
+ | `.bold()` | `'bold'` |
58
+ | `.fill()` | `'fill'` |
59
+ | `.duotone()` | `'duotone'` |
60
+ | `.variant(name)` | any custom string |
61
+
62
+ ### Sizes
63
+
64
+ | Method | Sets size to |
65
+ |---|---|
66
+ | `.small()` | `'small'` - 16px |
67
+ | `.medium()` | `'medium'` - 24px |
68
+ | `.large()` | `'large'` - 32px |
69
+ | `.extraLarge()` | `'extraLarge'` - 48px |
70
+ | `.size(value)` | number in px or named string |
71
+
72
+ ### Other
73
+
74
+ | Method | Parameters | Description |
75
+ |---|---|---|
76
+ | `.color(value)` | `value: string` | CSS color value |
77
+ | `.weight(value)` | `value: string` | Stroke weight (adapter-dependent) |
78
+
79
+ All methods accept `Observable` values - the icon updates automatically when the observable changes:
80
+
81
+ ```javascript
82
+ const isDark = Observable(false);
83
+
84
+ Icon.star
85
+ .variant(isDark.transform((d) => d ? 'fill' : 'outline'))
86
+ .color(isDark.transform((d) => d ? 'gold' : 'currentColor'))
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Available icons
92
+
93
+ > The standard set covers the most common use cases. Each adapter maps these names to its own icon names - not every icon exists in every library.
94
+
95
+ ### Navigation
96
+ `Icon.arrowLeft`, `Icon.arrowRight`, `Icon.arrowUp`, `Icon.arrowDown`, `Icon.chevronLeft`, `Icon.chevronRight`, `Icon.chevronUp`, `Icon.chevronDown`, `Icon.home`, `Icon.menu`, `Icon.close`, `Icon.back`, `Icon.forward`, `Icon.expand`, `Icon.collapse`, `Icon.fullscreen`, `Icon.fullscreenExit`
97
+
98
+ ### Actions
99
+ `Icon.search`, `Icon.filter`, `Icon.sort`, `Icon.edit`, `Icon.delete`, `Icon.add`, `Icon.remove`, `Icon.save`, `Icon.copy`, `Icon.download`, `Icon.upload`, `Icon.share`, `Icon.refresh`, `Icon.more`, `Icon.settings`, `Icon.drag`, `Icon.resize`, `Icon.undo`, `Icon.redo`, `Icon.scan`, `Icon.print`, `Icon.import`, `Icon.export`
100
+
101
+ ### Feedback
102
+ `Icon.check`, `Icon.checkCircle`, `Icon.error`, `Icon.warning`, `Icon.info`, `Icon.help`, `Icon.loading`, `Icon.success`
103
+
104
+ ### User
105
+ `Icon.user`, `Icon.userCircle`, `Icon.users`, `Icon.lock`, `Icon.unlock`, `Icon.eye`, `Icon.eyeOff`, `Icon.bell`, `Icon.notification`
106
+
107
+ ### Files & Media
108
+ `Icon.file`, `Icon.folder`, `Icon.image`, `Icon.document`, `Icon.attachment`, `Icon.link`, `Icon.externalLink`, `Icon.play`, `Icon.pause`, `Icon.stop`, `Icon.mute`, `Icon.sound`, `Icon.video`, `Icon.camera`
109
+
110
+ ### Interface
111
+ `Icon.grid`, `Icon.list`, `Icon.calendar`, `Icon.clock`, `Icon.tag`, `Icon.tags`, `Icon.qrCode`, `Icon.barcode`
112
+
113
+ ### Text editing
114
+ `Icon.bold`, `Icon.italic`, `Icon.underline`, `Icon.strikethrough`, `Icon.alignLeft`, `Icon.alignCenter`, `Icon.alignRight`, `Icon.alignJustify`, `Icon.orderedList`, `Icon.unorderedList`, `Icon.indent`, `Icon.outdent`, `Icon.scissors`, `Icon.fontColor`, `Icon.highlight`
115
+
116
+ ### Data & charts
117
+ `Icon.barChart`, `Icon.lineChart`, `Icon.pieChart`, `Icon.areaChart`, `Icon.table`, `Icon.database`
118
+
119
+ ### Commerce & finance
120
+ `Icon.shoppingCart`, `Icon.creditCard`, `Icon.wallet`, `Icon.bank`, `Icon.gift`, `Icon.percentage`, `Icon.dollar`, `Icon.euro`
121
+
122
+ ### Communication
123
+ `Icon.mail`, `Icon.message`, `Icon.comment`, `Icon.phone`
124
+
125
+ ### System & dev
126
+ `Icon.cloud`, `Icon.cloudUpload`, `Icon.cloudDownload`, `Icon.api`, `Icon.code`, `Icon.bug`, `Icon.tool`, `Icon.robot`
127
+
128
+ ### Location
129
+ `Icon.map`, `Icon.compass`, `Icon.global`, `Icon.location`
130
+
131
+ ### Shapes & objects
132
+ `Icon.star`, `Icon.heart`, `Icon.flag`, `Icon.trophy`, `Icon.medal`, `Icon.rocket`, `Icon.fire`, `Icon.thunder`, `Icon.smile`, `Icon.frown`, `Icon.like`, `Icon.dislike`, `Icon.safety`, `Icon.key`
133
+
134
+ ---
135
+
136
+ ## Custom icons
137
+
138
+ Create your own renderer that wraps the base adapter. Use NativeDocument elements for reactive support:
139
+
140
+ ```javascript
141
+ import { Img, Span, ShowIf } from 'native-document/elements';
142
+ import TablerIconRender from 'native-document/ui/icon/tabler/TablerIconRender';
143
+
144
+ function MyIconRender($desc) {
145
+ // Handle your custom icons
146
+ if ($desc.name === 'logo' || $desc.name === 'dashboard') {
147
+ const size = $desc.size?.__$Observable
148
+ ? $desc.size.transform((s) => typeof s === 'number' ? `${s}px` : `${SIZES[s] ?? 24}px`)
149
+ : `${typeof $desc.size === 'number' ? $desc.size : 24}px`;
150
+
151
+ return Img({
152
+ src: `/icons/${$desc.name}.svg`,
153
+ 'aria-hidden': 'true',
154
+ style: {
155
+ width: size,
156
+ height: size,
157
+ color: $desc.color ?? 'currentColor',
158
+ },
159
+ });
160
+ }
161
+
162
+ // Fall back to Tabler for everything else
163
+ return TablerIconRender($desc);
164
+ }
165
+
166
+ Icon.use(MyIconRender, { size: 'medium' });
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Adapters
172
+
173
+ ### Tabler Icons
174
+
175
+ > Outline and filled variants - the standard set covers the most common icons from Tabler's library.
176
+
177
+ **Install**
178
+
179
+ ```bash
180
+ npm install @tabler/icons-webfont
181
+ ```
182
+
183
+ **main.js**
184
+
185
+ ```javascript
186
+ import { Icon } from 'native-document/icons';
187
+ import TablerIconRender from 'native-document/ui/icon/tabler';
188
+ import 'native-document/src/ui/theme.scss';
189
+ import '@tabler/icons-webfont/dist/tabler-icons.css';
190
+
191
+ // Uncomment to enable .fill() variant
192
+ // import '@tabler/icons-webfont/dist/tabler-icons-filled.css';
193
+
194
+ Icon.use(TablerIconRender, { variant: 'outline', size: 'medium' });
195
+ ```
196
+
197
+ **Supported variants**
198
+
199
+ | Variant | Description | Required CSS |
200
+ |---|---|---|
201
+ | `outline` (default) | Stroked icons | `tabler-icons.css` |
202
+ | `fill` | Filled icons | `tabler-icons-filled.css` |
203
+
204
+ **Example**
205
+
206
+ ```javascript
207
+ Icon.search // outline, medium
208
+ Icon.search.fill() // filled
209
+ Icon.star.fill().large() // filled, 32px
210
+ ```
211
+
212
+ ---
213
+
214
+ ### Phosphor Icons
215
+
216
+ > 6 weights - the standard set covers the most common icons from Phosphor's library.
217
+
218
+ **Install**
219
+
220
+ ```bash
221
+ npm install @phosphor-icons/web
222
+ ```
223
+
224
+ **main.js**
225
+
226
+ ```javascript
227
+ import { Icon } from 'native-document/icons';
228
+ import PhosphorIconRender from 'native-document/ui/icon/phosphor';
229
+ import 'native-document/src/ui/theme.scss';
230
+
231
+ // Check the exact path in your node_modules/@phosphor-icons/web/ directory
232
+ import '@phosphor-icons/web/src/regular/style.css';
233
+
234
+ // Import only the weights you need
235
+ // import '@phosphor-icons/web/src/thin/style.css';
236
+ // import '@phosphor-icons/web/src/light/style.css';
237
+ // import '@phosphor-icons/web/src/bold/style.css';
238
+ // import '@phosphor-icons/web/src/fill/style.css';
239
+ // import '@phosphor-icons/web/src/duotone/style.css';
240
+
241
+ Icon.use(PhosphorIconRender, { variant: 'regular', size: 'medium' });
242
+ ```
243
+
244
+ > The CSS path may vary depending on the installed version. If you get a resolve error, run `ls node_modules/@phosphor-icons/web/` to find the correct path.
245
+
246
+ **Supported variants**
247
+
248
+ | Variant | Description | Required CSS |
249
+ |---|---|---|
250
+ | `thin` | Thinnest stroke | `thin/style.css` |
251
+ | `light` | Light stroke | `light/style.css` |
252
+ | `regular` (default) | Standard stroke | `regular/style.css` |
253
+ | `bold` | Bold stroke | `bold/style.css` |
254
+ | `fill` | Filled | `fill/style.css` |
255
+ | `duotone` | Two-tone | `duotone/style.css` |
256
+
257
+ **Example**
258
+
259
+ ```javascript
260
+ Icon.search // regular, medium
261
+ Icon.search.bold() // bold
262
+ Icon.star.fill().large() // filled, 32px
263
+ Icon.heart.duotone() // duotone
264
+ ```
265
+
266
+ ---
267
+
268
+ ### Google Material Icons
269
+
270
+ > Filled, outlined, round, sharp, two-tone - the standard set covers the most common icons from Material's library.
271
+
272
+ **Install**
273
+
274
+ ```bash
275
+ npm install material-icons
276
+ ```
277
+
278
+ **main.js**
279
+
280
+ ```javascript
281
+ import { Icon } from 'native-document/icons';
282
+ import MaterialIconRender from 'native-document/ui/icon/material';
283
+ import 'native-document/src/ui/theme.scss';
284
+ import 'material-icons/iconfont/filled.css';
285
+
286
+ // Import only the styles you need
287
+ // import 'material-icons/iconfont/outlined.css';
288
+ // import 'material-icons/iconfont/round.css';
289
+ // import 'material-icons/iconfont/sharp.css';
290
+ // import 'material-icons/iconfont/two-tone.css';
291
+
292
+ Icon.use(MaterialIconRender, { variant: 'fill', size: 'medium' });
293
+ ```
294
+
295
+ **Supported variants**
296
+
297
+ | Variant | Description | Required CSS |
298
+ |---|---|---|
299
+ | `fill` / `regular` (default) | Filled icons | `filled.css` |
300
+ | `outline` | Outlined icons | `outlined.css` |
301
+ | `round` | Rounded icons | `round.css` |
302
+ | `sharp` | Sharp corners | `sharp.css` |
303
+ | `twoTone` | Two-tone icons | `two-tone.css` |
304
+
305
+ > Material Icons uses **ligatures** - the icon name is the text content of the element, converted to an icon by the font. This is handled automatically by the renderer.
306
+
307
+ **Example**
308
+
309
+ ```javascript
310
+ Icon.search // filled, medium
311
+ Icon.search.variant('outline') // outlined
312
+ Icon.star.large() // filled, 32px
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Next Steps
318
+
319
+ - **[Components Overview](./index.md)** - BaseComponent and renderer pattern
320
+ - **[Getting Started](./getting-started.md)** - Register default renderers
321
+ - **[Theming](../theming.md)** - CSS tokens reference
@@ -0,0 +1,337 @@
1
+ ---
2
+ title: Components
3
+ description: NativeDocument's headless UI component system - describe your interface without choosing a theme, build fast, change the look anytime
4
+ ---
5
+
6
+ # Components
7
+
8
+ NativeDocument ships a separate `@native-document/components` package with 50+ UI components.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @native-document/components
14
+ ```
15
+
16
+ ```javascript
17
+ import { Button, Modal, Tabs } from '@native-document/components';
18
+ ```
19
+
20
+ ---
21
+
22
+ ## The Philosophy
23
+
24
+ ### Describe your interface. Worry about the look later.
25
+
26
+ The goal of the component system is simple: let you **build fast without making visual decisions upfront**.
27
+
28
+ When you write `Button('Save').variant('primary').size('large').rounded().loading(isSubmitting)`, you are describing **what the component should look like and what state it is in** - primary, large, rounded, loading. You are not deciding *how* those descriptions translate to pixels. That is the renderer's job.
29
+
30
+ `rounded()` says "this should be rounded". The renderer decides whether that means `border-radius: 4px`, `border-radius: 9999px`, a CSS class, or a Tailwind utility. You can change that decision at any time without touching the component.
31
+
32
+ This means:
33
+
34
+ - **Start immediately** - no design system required, no theme to configure first
35
+ - **Change the look anytime** - edit one renderer file and every component updates automatically, without touching a single line of business logic
36
+ - **No CSS wars** - no specificity conflicts, no `!important`, no framework overrides
37
+ - **One component, any visual system** - Tailwind, Bootstrap, your own CSS, or bare styles
38
+
39
+ ### Describe intent. The renderer decides implementation.
40
+
41
+ Every component lets you express visual intent through a fluent API. A `Button` knows it can be `primary`, `large`, `rounded`, `loading`, or `disabled`. It does not know what CSS rules those words translate to - that is the renderer's responsibility.
42
+
43
+ When you need to change the UI from Bootstrap to Tailwind, or from flat to material design - you edit the renderer. Nothing else changes.
44
+
45
+ ### The `$description` Contract
46
+
47
+ Every component builds a `$description` object - a plain object that describes the current state of the component. The renderer receives this object and returns a DOM element.
48
+
49
+ ```javascript
50
+ // What Button.$description looks like:
51
+ {
52
+ label: 'Submit',
53
+ type: 'submit',
54
+ variant: 'primary',
55
+ size: 'large',
56
+ icon: null,
57
+ iconPosition: 'leading',
58
+ loading: Observable(false),
59
+ disabled: Observable(false),
60
+ outline: false,
61
+ block: false,
62
+ borderRadiusType: 'rounded',
63
+ props: { class: 'my-btn' },
64
+ render: null // custom per-instance renderer
65
+ }
66
+ ```
67
+
68
+ The renderer is a function that receives `$description` and returns a DOM element:
69
+
70
+ ```javascript
71
+ Button.use(($description, component) => {
72
+ return NativeButton({
73
+ type: $description.type || 'button',
74
+ class: buildClasses($description),
75
+ disabled: $description.disabled
76
+ }, [
77
+ ShowIf($description.loading, () => Spinner()),
78
+ $description.icon && $description.iconPosition === 'leading'
79
+ ? $description.icon
80
+ : null,
81
+ $description.label,
82
+ $description.icon && $description.iconPosition === 'trailing'
83
+ ? $description.icon
84
+ : null,
85
+ ]);
86
+ });
87
+ ```
88
+
89
+ ### Fluent API
90
+
91
+ Every component uses a fluent builder API. Methods configure the `$description` and return `this` for chaining. The component is only rendered when you access `.nd` or append it to the DOM:
92
+
93
+ ```javascript
94
+ Button('Submit')
95
+ .variant('primary')
96
+ .size('large')
97
+ .loading(isSubmitting)
98
+ .disabled(isDisabled)
99
+ .rounded()
100
+ .nd.onClick(() => submitForm())
101
+ ```
102
+
103
+ ### Rendering is Lazy
104
+
105
+ The component function runs immediately, but the DOM is not built until:
106
+
107
+ - You access `.nd` (triggers `.toNdElement()`)
108
+ - You append the component to the DOM directly
109
+
110
+ This means you can fully configure a component before it renders.
111
+
112
+ ---
113
+
114
+ ## Registering a Renderer
115
+
116
+ ### Global renderer
117
+
118
+ ```javascript
119
+ import { Button } from '@native-document/components';
120
+
121
+ // Called once at app startup - applies to all Button instances
122
+ Button.use(($description) => {
123
+ const classes = ['btn'];
124
+
125
+ if ($description.variant) { classes.push(`btn-${$description.variant}`); }
126
+ if ($description.size) { classes.push(`btn-${$description.size}`); }
127
+ if ($description.block) { classes.push('btn-block'); }
128
+ if ($description.outline) { classes.push('btn-outline'); }
129
+
130
+ return NativeButton({
131
+ type: $description.type || 'button',
132
+ class: classes.join(' '),
133
+ disabled: $description.disabled,
134
+ ...$description.props
135
+ }, $description.label);
136
+ });
137
+ ```
138
+
139
+ ### Per-instance renderer
140
+
141
+ Override the global renderer for a specific instance:
142
+
143
+ ```javascript
144
+ Button('Special')
145
+ .render(($description) => {
146
+ return NativeButton({ class: 'special-btn' }, $description.label);
147
+ })
148
+ .nd.onClick(() => console.log('Special!'))
149
+ ```
150
+
151
+ ---
152
+
153
+ ## `setDescription()` - Reactive Updates
154
+
155
+ `setDescription()` updates the `$description` object. If a key already holds an observable, it calls `.set()` on it instead of replacing it - keeping reactive bindings intact:
156
+
157
+ ```javascript
158
+ const btn = Button('Save').loading(Observable(false));
159
+
160
+ // Later - updates the observable, DOM reacts automatically
161
+ btn.setDescription({ loading: true });
162
+ ```
163
+
164
+ ---
165
+
166
+ ## `showIf()` / `visibility()`
167
+
168
+ Conditionally render the entire component:
169
+
170
+ ```javascript
171
+ Button('Admin Only')
172
+ .showIf(user.is(u => u.isAdmin))
173
+ .variant('danger')
174
+ ```
175
+
176
+ ---
177
+
178
+ ## `props()` - Pass HTML Attributes
179
+
180
+ Pass arbitrary HTML attributes to the root element:
181
+
182
+ ```javascript
183
+ Button('Submit')
184
+ .props({ id: 'submit-btn', 'data-testid': 'submit' })
185
+ .variant('primary')
186
+ ```
187
+
188
+ ---
189
+
190
+ ## `refSelf()` - Component Reference
191
+
192
+ Store a reference to the component instance (not the DOM element):
193
+
194
+ ```javascript
195
+ const refs = {};
196
+
197
+ Button('Save')
198
+ .variant('primary')
199
+ .nd.refSelf(refs, 'saveBtn');
200
+
201
+ // Later - call component methods
202
+ refs.saveBtn.loading(true);
203
+ refs.saveBtn.disabled(true);
204
+ ```
205
+
206
+ ---
207
+
208
+ ## `ghostDom()` - Companion Elements
209
+
210
+ Attach companion elements that are injected into the DOM alongside the component but not exposed to the parent. See [NDElement - ghostDom](../native-document-element.md) for the full explanation.
211
+
212
+ ---
213
+
214
+ ## `postBuild(callback)`
215
+
216
+ Register a callback that runs after the component renders:
217
+
218
+ ```javascript
219
+ Button('Submit')
220
+ .postBuild((element, component) => {
221
+ console.log('Button rendered:', element);
222
+ })
223
+ ```
224
+
225
+ ---
226
+
227
+ ## `BaseComponent.extends()` and `BaseComponent.use()`
228
+
229
+ These are the tools for building custom components:
230
+
231
+ ```javascript
232
+ import { BaseComponent } from '@native-document/components';
233
+
234
+ // Create a new component that inherits from BaseComponent
235
+ function MyCard(title, props = {}) {
236
+ if (!(this instanceof MyCard)) {
237
+ return new MyCard(title, props);
238
+ }
239
+ BaseComponent.call(this);
240
+ this.$description = { title, props };
241
+ }
242
+
243
+ MyCard.defaultTemplate = null;
244
+
245
+ MyCard.use = function(template) {
246
+ MyCard.defaultTemplate = template;
247
+ };
248
+
249
+ // Set up prototype chain
250
+ BaseComponent.extends(MyCard);
251
+
252
+ // Add methods
253
+ MyCard.prototype.subtitle = function(subtitle) {
254
+ this.$description.subtitle = subtitle;
255
+ return this;
256
+ };
257
+
258
+ // Register renderer
259
+ MyCard.use(($description) => {
260
+ return Div({ class: 'card', ...$description.props }, [
261
+ H2($description.title),
262
+ $description.subtitle ? P($description.subtitle) : null,
263
+ ]);
264
+ });
265
+
266
+ // Usage
267
+ MyCard('Hello').subtitle('World').nd
268
+ ```
269
+
270
+ ### `BaseComponent.use()` - Apply Traits
271
+
272
+ Mix behavior traits into a component:
273
+
274
+ ```javascript
275
+ import { BaseComponent, HasEventEmitter, HasDraggable } from '@native-document/components';
276
+
277
+ BaseComponent.use(MyCard, HasEventEmitter, HasDraggable);
278
+ // MyCard now has .on(), .emit(), .makeDraggable() etc.
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Default Renderers
284
+
285
+ The package ships with a default renderer for every component, imported from `'native-document/src/ui'`. You can use them as a starting point or ignore them entirely and write your own from scratch.
286
+
287
+ Create a `src/core/renderers.js` file and import it once at app startup:
288
+
289
+ ```javascript
290
+ import {
291
+ Button, Alert, Badge, Modal, Tabs, Dropdown, DropdownItem
292
+ // ... all components you use
293
+ } from 'native-document/components';
294
+
295
+ import {
296
+ ButtonRender, AlertRender, BadgeRender, ModalRender, TabsRender,
297
+ DropdownRender, DropdownItemRender
298
+ // ... matching renders
299
+ } from 'native-document/src/ui';
300
+
301
+ Button.use(ButtonRender);
302
+ Alert.use(AlertRender);
303
+ Badge.use(BadgeRender);
304
+ Modal.use(ModalRender);
305
+ Tabs.use(TabsRender);
306
+ Dropdown.use(DropdownRender);
307
+ DropdownItem.use(DropdownItemRender);
308
+ // ...
309
+ ```
310
+
311
+ > `ContextMenu` is the only exception - its `.use()` takes both a renderer and a handler:
312
+ > ```javascript
313
+ > import { ContextMenuRender, contextMenuHandler } from 'native-document/src/ui';
314
+ > ContextMenu.use(ContextMenuRender, contextMenuHandler);
315
+ > ```
316
+
317
+ When you want to customize, call `Component.use()` with your own renderer - it replaces the default globally:
318
+
319
+ ```javascript
320
+ Button.use(($d) => {
321
+ return NativeButton({
322
+ class: buildMyClasses($d),
323
+ ...$d.props
324
+ }, $d.label);
325
+ });
326
+ ```
327
+
328
+ You can also mix - use the defaults for most components and override only the ones you want to customize.
329
+
330
+ ---
331
+
332
+ ## Next Steps
333
+
334
+ - **[Getting Started](./getting-started.md)** - Build your first component with a renderer
335
+ - **[Traits](./traits.md)** - HasEventEmitter, HasDraggable, HasResizable
336
+ - **[Button](./button.md)** - Button component API
337
+ - **[Layout](./layout.md)** - Stack, Row, Col, Divider