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.
- package/.vitepress/config.js +166 -0
- package/CHANGELOG.md +187 -0
- package/components.js +4 -1
- package/dist/native-document.components.min.js +593 -300
- package/dist/native-document.dev.js +99 -75
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/advanced-components.md +213 -608
- package/docs/anchor.md +173 -312
- package/docs/cache.md +95 -803
- package/docs/cli.md +179 -0
- package/docs/components/accordion.md +172 -0
- package/docs/components/alert.md +99 -0
- package/docs/components/avatar.md +160 -0
- package/docs/components/badge.md +102 -0
- package/docs/components/breadcrumb.md +89 -0
- package/docs/components/button.md +183 -0
- package/docs/components/card.md +69 -0
- package/docs/components/context-menu.md +118 -0
- package/docs/components/data-table.md +345 -0
- package/docs/components/dropdown.md +214 -0
- package/docs/components/form/autocomplete-field.md +81 -0
- package/docs/components/form/checkbox-field.md +41 -0
- package/docs/components/form/checkbox-group-field.md +54 -0
- package/docs/components/form/color-field.md +64 -0
- package/docs/components/form/date-field.md +92 -0
- package/docs/components/form/field-collection.md +63 -0
- package/docs/components/form/file-field.md +203 -0
- package/docs/components/form/form-control.md +87 -0
- package/docs/components/form/image-field.md +90 -0
- package/docs/components/form/index.md +115 -0
- package/docs/components/form/number-field.md +65 -0
- package/docs/components/form/radio-field.md +51 -0
- package/docs/components/form/select-field.md +123 -0
- package/docs/components/form/slider.md +136 -0
- package/docs/components/form/string-field.md +134 -0
- package/docs/components/form/textarea-field.md +65 -0
- package/docs/components/form-fields.md +372 -0
- package/docs/components/getting-started.md +264 -0
- package/docs/components/icons.md +321 -0
- package/docs/components/index.md +337 -0
- package/docs/components/layout.md +279 -0
- package/docs/components/list.md +73 -0
- package/docs/components/menu.md +215 -0
- package/docs/components/modal.md +156 -0
- package/docs/components/pagination.md +95 -0
- package/docs/components/popover.md +131 -0
- package/docs/components/progress.md +111 -0
- package/docs/components/shortcut-manager.md +221 -0
- package/docs/components/simple-table.md +107 -0
- package/docs/components/skeleton.md +155 -0
- package/docs/components/spinner.md +100 -0
- package/docs/components/splitter.md +133 -0
- package/docs/components/stepper.md +163 -0
- package/docs/components/switch.md +113 -0
- package/docs/components/tabs.md +153 -0
- package/docs/components/toast.md +119 -0
- package/docs/components/tooltip.md +151 -0
- package/docs/components/traits.md +261 -0
- package/docs/conditional-rendering.md +170 -588
- package/docs/contributing.md +300 -25
- package/docs/core-concepts.md +205 -374
- package/docs/elements.md +251 -367
- package/docs/extending-native-document-element.md +192 -207
- package/docs/filters.md +153 -1122
- package/docs/getting-started.md +193 -267
- package/docs/i18n.md +241 -0
- package/docs/index.md +76 -0
- package/docs/lifecycle-events.md +143 -75
- package/docs/list-rendering.md +227 -852
- package/docs/memory-management.md +134 -47
- package/docs/native-document-element.md +337 -186
- package/docs/native-fetch.md +99 -630
- package/docs/observable-resource.md +364 -0
- package/docs/observables.md +592 -526
- package/docs/routing.md +244 -653
- package/docs/state-management.md +134 -241
- package/docs/svg-elements.md +231 -0
- package/docs/theming.md +409 -0
- package/docs/tutorials/.gitkeep +0 -0
- package/docs/validation.md +95 -97
- package/docs/vitepress-conventions.md +219 -0
- package/package.json +34 -13
- package/readme.md +269 -89
- package/src/components/card/Card.js +93 -39
- package/src/components/card/index.js +1 -1
- package/src/components/form/field/types/FileField.js +3 -3
- package/src/components/icon/Icon.js +107 -0
- package/src/components/icon/icon-getters.js +142 -0
- package/src/components/icon/icons.js +171 -0
- package/src/components/icon/index.js +17 -0
- package/src/components/icon/types/Icon.d.ts +191 -0
- package/src/components/index.d.ts +6 -1
- package/src/components/list/HasListItem.js +171 -0
- package/src/components/list/List.js +41 -107
- package/src/components/list/ListDivider.js +39 -0
- package/src/components/list/ListGroup.js +76 -59
- package/src/components/list/ListItem.js +117 -69
- package/src/components/list/index.js +3 -1
- package/src/components/list/types/List.d.ts +45 -32
- package/src/components/list/types/ListDivider.ts +16 -0
- package/src/components/list/types/ListGroup.d.ts +51 -29
- package/src/components/list/types/ListItem.d.ts +40 -30
- package/src/components/spacer/Spacer.js +1 -1
- package/src/core/data/ObservableResource.js +5 -0
- package/src/core/data/observable-helpers/observable.prototypes.js +2 -0
- package/src/core/elements/content-formatter.js +16 -2
- package/src/core/elements/form.js +1 -1
- package/src/core/elements/img.js +1 -1
- package/src/core/elements/medias.js +2 -2
- package/src/core/elements/meta-data.js +1 -1
- package/src/core/wrappers/AttributesWrapper.js +37 -22
- package/src/core/wrappers/ElementCreator.js +9 -16
- package/src/core/wrappers/HtmlElementWrapper.js +26 -7
- package/src/core/wrappers/NDElement.js +12 -1
- package/src/core/wrappers/prototypes/attributes-extensions.js +24 -24
- package/src/core/wrappers/prototypes/nd-element-extensions.js +1 -8
- package/src/ui/components/card/CardRender.js +133 -0
- package/src/ui/components/card/card.css +169 -0
- package/src/ui/components/contextmenu/ContextmenuRender.js +1 -1
- package/src/ui/components/icon/material/MaterialIconRender.js +71 -0
- package/src/ui/components/icon/material/material.css +15 -0
- package/src/ui/components/icon/material/material.map.js +170 -0
- package/src/ui/components/icon/phosphor/PhosphorIconRender.js +71 -0
- package/src/ui/components/icon/phosphor/phosphor.css +17 -0
- package/src/ui/components/icon/phosphor/phosphor.map.js +165 -0
- package/src/ui/components/icon/tabler/TablerIconRender.js +59 -0
- package/src/ui/components/icon/tabler/tabler.css +14 -0
- package/src/ui/components/icon/tabler/tabler.map.js +165 -0
- package/src/ui/components/list/ListRender.js +18 -0
- package/src/ui/components/list/divider/ListDividerRender.js +10 -0
- package/src/ui/components/list/divider/list-divider.css +12 -0
- package/src/ui/components/list/group/ListGroupRender.js +61 -0
- package/src/ui/components/list/group/list-group.css +62 -0
- package/src/ui/components/list/item/ListItemRender.js +238 -0
- package/src/ui/components/list/item/list-item.css +191 -0
- package/src/ui/components/list/list.css +24 -0
- package/src/ui/components/spacer/SpacerRender.js +10 -0
- 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
|