native-document 1.0.166 → 1.0.168
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 +153 -0
- package/components.js +2 -1
- package/dist/native-document.components.min.js +495 -228
- package/dist/native-document.dev.js +7 -0
- 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/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/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/ListItem.d.ts +45 -34
- 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/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/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 +8 -0
|
@@ -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
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Layout Components
|
|
3
|
+
description: Stack, HStack, VStack, AbsoluteStack, FixedStack, RelativeStack - flexible layout building blocks
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Layout Components
|
|
7
|
+
|
|
8
|
+
Layout components provide flexible, semantic containers for building UI structure.
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
import { HStack, VStack, Row, Col, AbsoluteStack, FixedStack, RelativeStack, Divider } from 'native-document/components';
|
|
12
|
+
import { Stack } from 'native-document/components'; // only needed to register the renderer
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## `Stack` - Abstract Base
|
|
18
|
+
|
|
19
|
+
`Stack` is the abstract base class for all layout components. Do not use it directly - use `HStack`, `VStack`, `Row`, or `Col` instead.
|
|
20
|
+
|
|
21
|
+
All stack variants share the same `$description` structure and methods:
|
|
22
|
+
|
|
23
|
+
### `$description`
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
{
|
|
27
|
+
orientation: 'horizontal', // 'horizontal' | 'vertical'
|
|
28
|
+
content: [],
|
|
29
|
+
spacing: null, // string | number
|
|
30
|
+
alignment: 'center', // 'leading' | 'center' | 'trailing' | 'stretch'
|
|
31
|
+
justifyContent: 'between', // 'start' | 'center' | 'end' | 'between' | 'around'
|
|
32
|
+
wrap: false,
|
|
33
|
+
grow: false,
|
|
34
|
+
shrink: false,
|
|
35
|
+
reverse: false,
|
|
36
|
+
props: {}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Methods
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// Spacing
|
|
44
|
+
.spacing(8)
|
|
45
|
+
|
|
46
|
+
// Alignment
|
|
47
|
+
.alignLeading() // alignment: 'leading'
|
|
48
|
+
.alignCenter() // alignment: 'center'
|
|
49
|
+
.alignTrailing() // alignment: 'trailing'
|
|
50
|
+
.alignStretch() // alignment: 'stretch'
|
|
51
|
+
|
|
52
|
+
// Justify
|
|
53
|
+
.justifyStart()
|
|
54
|
+
.justifyCenter()
|
|
55
|
+
.justifyEnd()
|
|
56
|
+
.justifyBetween()
|
|
57
|
+
.justifyAround()
|
|
58
|
+
|
|
59
|
+
// Combined
|
|
60
|
+
.center() // alignCenter() + justifyCenter()
|
|
61
|
+
|
|
62
|
+
// Other
|
|
63
|
+
.wrap()
|
|
64
|
+
.grow()
|
|
65
|
+
.shrink()
|
|
66
|
+
.reverse()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Renderer
|
|
70
|
+
|
|
71
|
+
Each variant needs its own renderer registered separately:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import { HStack, VStack } from 'native-document/components';
|
|
75
|
+
import { HStackRender, VStackRender } from 'native-document/ui';
|
|
76
|
+
|
|
77
|
+
HStack.use(HStackRender);
|
|
78
|
+
VStack.use(VStackRender);
|
|
79
|
+
// Row and Col share HStack and VStack renderers automatically
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Or write your own:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
HStack.use(($description) => {
|
|
86
|
+
return Div({
|
|
87
|
+
class: buildStackClasses($description),
|
|
88
|
+
style: $description.spacing ? { gap: $description.spacing + 'px' } : {},
|
|
89
|
+
...$description.props
|
|
90
|
+
}, $description.content);
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## `HStack` - Horizontal Stack
|
|
97
|
+
|
|
98
|
+
Alias for `Stack` with `orientation: 'horizontal'`. Also available as `Row`.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
HStack([
|
|
102
|
+
Avatar(user.avatar),
|
|
103
|
+
Div(user.name)
|
|
104
|
+
]).alignCenter().spacing(12)
|
|
105
|
+
|
|
106
|
+
// Row is an alias for HStack
|
|
107
|
+
Row([
|
|
108
|
+
Button('Cancel').ghost(),
|
|
109
|
+
Button('Save').primary()
|
|
110
|
+
]).justifyEnd().spacing(8)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## `VStack` - Vertical Stack
|
|
116
|
+
|
|
117
|
+
`Stack` with `orientation: 'vertical'` and `alignment: 'leading'` by default. Also available as `Col`.
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
VStack([
|
|
121
|
+
H2('Title'),
|
|
122
|
+
P('Description'),
|
|
123
|
+
Button('Action').primary()
|
|
124
|
+
]).spacing(16).alignStretch()
|
|
125
|
+
|
|
126
|
+
// Col is an alias for VStack
|
|
127
|
+
Col([Label('Name'), Input({ value: name })]).spacing(4)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## `AbsoluteStack` / `FixedStack` / `RelativeStack` - Positioned Containers
|
|
133
|
+
|
|
134
|
+
All three extend `PositionStack` and share the same API. They differ only in CSS `position`:
|
|
135
|
+
|
|
136
|
+
| Component | CSS position |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `AbsoluteStack` | `absolute` |
|
|
139
|
+
| `FixedStack` | `fixed` |
|
|
140
|
+
| `RelativeStack` | `relative` |
|
|
141
|
+
|
|
142
|
+
### `$description`
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
{
|
|
146
|
+
position: 'absolute', // 'absolute' | 'fixed' | 'relative'
|
|
147
|
+
content: [],
|
|
148
|
+
top: null, // string | number
|
|
149
|
+
right: null,
|
|
150
|
+
bottom: null,
|
|
151
|
+
left: null,
|
|
152
|
+
width: null,
|
|
153
|
+
height: null,
|
|
154
|
+
zIndex: null,
|
|
155
|
+
anchor: null, // preset anchor shorthand
|
|
156
|
+
props: {}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Methods
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
AbsoluteStack(Div('Tooltip'))
|
|
164
|
+
.top(0)
|
|
165
|
+
.right(0)
|
|
166
|
+
.width(200)
|
|
167
|
+
.height(100)
|
|
168
|
+
.zIndex(50)
|
|
169
|
+
|
|
170
|
+
// Size helpers
|
|
171
|
+
.fullWidth() // width: '100%'
|
|
172
|
+
.fullHeight() // height: '100%'
|
|
173
|
+
.fullSize() // width + height: '100%'
|
|
174
|
+
.size(200, 100) // width: 200, height: 100
|
|
175
|
+
.size(200) // width: 200, height: 200
|
|
176
|
+
|
|
177
|
+
// Z-index helpers
|
|
178
|
+
.above(100) // zIndex: 100
|
|
179
|
+
.below() // zIndex: -1
|
|
180
|
+
|
|
181
|
+
// Anchor presets (position combinations)
|
|
182
|
+
.fill() // anchor: 'fill'
|
|
183
|
+
.topLeading() // anchor: 'top-leading'
|
|
184
|
+
.atTopCenter() // anchor: 'top-center'
|
|
185
|
+
.atTopTrailing() // anchor: 'top-trailing'
|
|
186
|
+
.atCenterLeading() // anchor: 'center-leading'
|
|
187
|
+
.atCenter() // anchor: 'center'
|
|
188
|
+
.atCenterTrailing() // anchor: 'center-trailing'
|
|
189
|
+
.atBottomLeading() // anchor: 'bottom-leading'
|
|
190
|
+
.atBottomCenter() // anchor: 'bottom-center'
|
|
191
|
+
.atBottomTrailing() // anchor: 'bottom-trailing'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Example
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// Floating action button
|
|
198
|
+
const fab = FixedStack(
|
|
199
|
+
Button(PlusIcon).circle().primary()
|
|
200
|
+
)
|
|
201
|
+
.atBottomTrailing()
|
|
202
|
+
.right(24)
|
|
203
|
+
.bottom(24)
|
|
204
|
+
.above(100)
|
|
205
|
+
|
|
206
|
+
// Overlay badge
|
|
207
|
+
const badge = AbsoluteStack(Span('3'))
|
|
208
|
+
.atTopTrailing()
|
|
209
|
+
.top(-8)
|
|
210
|
+
.right(-8)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## `Divider`
|
|
216
|
+
|
|
217
|
+
A horizontal or vertical separator, optionally with a label.
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
Divider(label?, props?)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### `$description`
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
{
|
|
227
|
+
label: null, // string | null
|
|
228
|
+
orientation: 'horizontal', // 'horizontal' | 'vertical'
|
|
229
|
+
props: {}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Methods
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
Divider() // horizontal, no label
|
|
237
|
+
Divider('OR') // with label
|
|
238
|
+
Divider().vertical() // vertical
|
|
239
|
+
|
|
240
|
+
.orientation('vertical')
|
|
241
|
+
.vertical()
|
|
242
|
+
.horizontal()
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Example
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
VStack([
|
|
249
|
+
Input({ placeholder: 'Email', value: email }),
|
|
250
|
+
Input({ placeholder: 'Password', type: 'password', value: password }),
|
|
251
|
+
Button('Sign in').primary().block(),
|
|
252
|
+
Divider('OR'),
|
|
253
|
+
Button('Continue with Google').ghost().block()
|
|
254
|
+
]).spacing(16)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Theming
|
|
261
|
+
|
|
262
|
+
```css
|
|
263
|
+
:root {
|
|
264
|
+
--divider-color: var(--gray-lite-3);
|
|
265
|
+
--divider-label-color: var(--gray);
|
|
266
|
+
--divider-label-size: var(--note-size);
|
|
267
|
+
--divider-label-gap: var(--space-comfortable);
|
|
268
|
+
--divider-thickness: 1px;
|
|
269
|
+
--divider-spacing: 16px;
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Next Steps
|
|
276
|
+
|
|
277
|
+
- **[Getting Started](./getting-started.md)** - Register renderers
|
|
278
|
+
- **[Button](./button.md)** - Button component
|
|
279
|
+
- **[Modal](./modal.md)** - Modal with draggable and resizable
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: List
|
|
3
|
+
description: Flexible list component with single/multi selection, checkbox or click-to-select modes, dividers, and keyboard navigation
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# List
|
|
7
|
+
|
|
8
|
+
> **Status: coming soon.** The `List` API is fully defined but the default renderer is not yet implemented. You can use `List` today by providing your own renderer via `List.use()`.
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
import { List } from 'native-document/components';
|
|
12
|
+
|
|
13
|
+
List(props?)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Custom Renderer
|
|
17
|
+
|
|
18
|
+
Until the default renderer ships, register your own:
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
import { Ul, Li } from 'native-document/elements';
|
|
22
|
+
|
|
23
|
+
List.use({
|
|
24
|
+
list: ($d, instance) => {
|
|
25
|
+
return Ul({ class: `list ${$d.inset ? 'list-inset' : ''}` });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
> `List.use()` expects an object with a `list` key, not a function directly.
|
|
31
|
+
|
|
32
|
+
## Methods
|
|
33
|
+
|
|
34
|
+
### Data
|
|
35
|
+
|
|
36
|
+
| Method | Parameters | Description |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `.items(items)` | `items: *[]` | Static item list |
|
|
39
|
+
| `.dynamic(obs?)` | `obs?: ObservableArray` | Bind to a reactive array |
|
|
40
|
+
| `.data(data)` | `data: *` | Data passed to the renderer |
|
|
41
|
+
|
|
42
|
+
### Selection
|
|
43
|
+
|
|
44
|
+
| Method | Parameters | Description |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `.selectable(enabled?)` | `enabled?: boolean` | Enable item selection |
|
|
47
|
+
| `.multiSelect(enabled?)` | `enabled?: boolean` | Allow multiple selections. Enables `.selectable()` automatically |
|
|
48
|
+
| `.selectByClick()` | - | Select items on click (mutually exclusive with `selectByCheckbox`) |
|
|
49
|
+
| `.selectByCheckbox()` | - | Select items via a checkbox (mutually exclusive with `selectByClick`) |
|
|
50
|
+
| `.selectedValuesModel(obs)` | `obs: Observable<*[]>` | Bind selected values to an external observable |
|
|
51
|
+
|
|
52
|
+
### Layout
|
|
53
|
+
|
|
54
|
+
| Method | Parameters | Description |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `.withDivider(enabled?)` | `enabled?: boolean` | Show a divider between items |
|
|
57
|
+
| `.inset(enabled?)` | `enabled?: boolean` | Add inset padding |
|
|
58
|
+
| `.loopOnKeyboard(enabled)` | `enabled: boolean` | Loop keyboard navigation at list boundaries. Default `true` |
|
|
59
|
+
|
|
60
|
+
### Events
|
|
61
|
+
|
|
62
|
+
| Method | Parameters | Description |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `.onItemClick(handler)` | `handler: (item, event) => void` | Fires when an item is clicked |
|
|
65
|
+
| `.onItemSelect(handler)` | `handler: (item) => void` | Fires when an item is selected |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Next Steps
|
|
70
|
+
|
|
71
|
+
- **[Components Overview](./index.md)** - BaseComponent and renderer pattern
|
|
72
|
+
- **[Getting Started](./getting-started.md)** - Register default renderers
|
|
73
|
+
- **[Traits](./traits.md)** - `HasItems` trait used by this component
|