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,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ShortcutManager
|
|
3
|
+
description: Register, display, and manage keyboard shortcuts with OS-aware rendering and context scoping
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ShortcutManager
|
|
7
|
+
|
|
8
|
+
`ShortcutManager` handles global and context-scoped keyboard shortcuts. It parses shortcut strings, renders them correctly per OS (Mac symbols or Windows labels), and dispatches handlers on keydown.
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
import { ShortcutManager } from 'native-document';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Initialization
|
|
17
|
+
|
|
18
|
+
Call `init()` once at app startup to activate the global keyboard listener:
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
ShortcutManager.init();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Shortcut Conventions
|
|
27
|
+
|
|
28
|
+
Two formats are supported:
|
|
29
|
+
|
|
30
|
+
### Short convention
|
|
31
|
+
|
|
32
|
+
Starts with `+`. The first `+` means Ctrl/Cmd (meta):
|
|
33
|
+
|
|
34
|
+
| Shortcut | Meaning |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `'+S'` | Ctrl/Cmd + S |
|
|
37
|
+
| `'++S'` | Ctrl/Cmd + Alt + S |
|
|
38
|
+
| `'+Shift+S'` | Ctrl/Cmd + Shift + S |
|
|
39
|
+
| `'+Alt+S'` | Ctrl/Cmd + Alt + S |
|
|
40
|
+
|
|
41
|
+
### Standard convention
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
'Ctrl+S'
|
|
45
|
+
'Cmd+Shift+S'
|
|
46
|
+
'Alt+F4'
|
|
47
|
+
'Ctrl+Alt+Delete'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Both formats are equivalent and can be mixed freely.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## OS-Aware Display
|
|
55
|
+
|
|
56
|
+
`ShortcutManager.display(shortcut)` formats a shortcut string for the current OS:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
ShortcutManager.display('+S')
|
|
60
|
+
// Mac: "⌘ S"
|
|
61
|
+
// Windows: "Ctrl+S"
|
|
62
|
+
|
|
63
|
+
ShortcutManager.display('+Shift+S')
|
|
64
|
+
// Mac: "⌘ ⇧ S"
|
|
65
|
+
// Windows: "Ctrl+Shift+S"
|
|
66
|
+
|
|
67
|
+
ShortcutManager.display('++S')
|
|
68
|
+
// Mac: "⌘ ⌥ S"
|
|
69
|
+
// Windows: "Ctrl+Alt+S"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Mac symbols:
|
|
73
|
+
|
|
74
|
+
| Key | Symbol |
|
|
75
|
+
|---|---|
|
|
76
|
+
| Meta / Cmd | `⌘` |
|
|
77
|
+
| Shift | `⇧` |
|
|
78
|
+
| Alt / Option | `⌥` |
|
|
79
|
+
| Ctrl | `⌃` |
|
|
80
|
+
|
|
81
|
+
Use `display()` in your renderer to show the shortcut label next to menu items:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
MenuItem.use(($description) => {
|
|
85
|
+
return Div({ class: 'menu-item' }, [
|
|
86
|
+
$description.label,
|
|
87
|
+
$description.shortcut
|
|
88
|
+
? Span({ class: 'shortcut' }, ShortcutManager.display($description.shortcut))
|
|
89
|
+
: null
|
|
90
|
+
]);
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Registering Shortcuts
|
|
97
|
+
|
|
98
|
+
### `ShortcutManager.register(shortcut, handler, options?)`
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
ShortcutManager.register('+S', () => save(), { source: 'editor' });
|
|
102
|
+
ShortcutManager.register('+Z', () => undo(), { source: 'editor' });
|
|
103
|
+
ShortcutManager.register('+Shift+Z', () => redo(), { source: 'editor' });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Options:
|
|
107
|
+
|
|
108
|
+
| Option | Type | Default | Description |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| `context` | `string` | `'global'` | Scope the shortcut to a named context |
|
|
111
|
+
| `source` | `string` | `'unknown'` | Label used in conflict warnings |
|
|
112
|
+
| `force` | `boolean` | `false` | Override an existing registration without warning |
|
|
113
|
+
|
|
114
|
+
### `ShortcutManager.unregister(shortcut, context?)`
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
ShortcutManager.unregister('+S');
|
|
118
|
+
ShortcutManager.unregister('+S', 'editor');
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `ShortcutManager.has(shortcut, context?)`
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
if (!ShortcutManager.has('+S')) {
|
|
125
|
+
ShortcutManager.register('+S', () => save());
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Contexts
|
|
132
|
+
|
|
133
|
+
Contexts let you scope shortcuts so the same key combination can do different things in different parts of the app. A `'global'` shortcut always fires. A context shortcut only fires when that context is active (you control activation logic):
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
// Global - always active
|
|
137
|
+
ShortcutManager.register('+S', () => save(), {
|
|
138
|
+
context: 'global',
|
|
139
|
+
source: 'app'
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Scoped - only active in the modal context
|
|
143
|
+
ShortcutManager.register('Escape', () => closeModal(), {
|
|
144
|
+
context: 'modal',
|
|
145
|
+
source: 'modal'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Unregister when leaving context
|
|
149
|
+
ShortcutManager.unregister('Escape', 'modal');
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Conflict Handling
|
|
155
|
+
|
|
156
|
+
Registering the same shortcut in the same context twice logs a warning:
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
ShortcutManager.register('+S', () => save(), { source: 'editor' });
|
|
160
|
+
ShortcutManager.register('+S', () => download(), { source: 'toolbar' });
|
|
161
|
+
// warn: "+S" is already registered by "editor" in context "global". Use { force: true } to override.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Use `force: true` to override without warning:
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
ShortcutManager.register('+S', () => download(), { force: true, source: 'toolbar' });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Usage with Menu Items
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
import { MenuItem } from 'native-document/components';
|
|
176
|
+
|
|
177
|
+
MenuItem()
|
|
178
|
+
.label('Save')
|
|
179
|
+
.icon(SaveIcon)
|
|
180
|
+
.shortcut('+S')
|
|
181
|
+
.action(() => save())
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The shortcut string is passed as-is to `ShortcutManager.display()` by the renderer to show the OS-formatted label.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Integration with Dropdown and Context Menu
|
|
189
|
+
|
|
190
|
+
`DropdownItem` and `ContextMenuItem` both accept a `.shortcut()` method that passes the string directly to `ShortcutManager.display()` in the renderer:
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
import { DropdownItem } from 'native-document/components';
|
|
194
|
+
|
|
195
|
+
DropdownItem()
|
|
196
|
+
.label('Save')
|
|
197
|
+
.icon(SaveIcon)
|
|
198
|
+
.shortcut('+S')
|
|
199
|
+
.action(() => save())
|
|
200
|
+
|
|
201
|
+
// ContextMenuItem works the same way
|
|
202
|
+
ContextMenuItem()
|
|
203
|
+
.label('Delete')
|
|
204
|
+
.shortcut('Delete')
|
|
205
|
+
.danger()
|
|
206
|
+
.action((data) => deleteRow(data))
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The shortcut label is display-only in Dropdown and ContextMenu items - they do not automatically register the shortcut with `ShortcutManager`. Register shortcuts separately if you want them to fire on keydown:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
ShortcutManager.register('+S', () => save(), { source: 'toolbar' });
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Next Steps
|
|
218
|
+
|
|
219
|
+
- **[Menu](./menu.md)** - Menu with shortcut labels
|
|
220
|
+
- **[Dropdown](./dropdown.md)** - Dropdown items with shortcuts
|
|
221
|
+
- **[Context Menu](./context-menu.md)** - Context menu items with shortcuts
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: SimpleTable
|
|
3
|
+
description: Lightweight table component for static or observable data with custom cell renderers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SimpleTable
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
import { SimpleTable } from 'native-document/components';
|
|
10
|
+
|
|
11
|
+
SimpleTable(props?)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
A lightweight table for static or observable data.
|
|
15
|
+
|
|
16
|
+
## Default Renderer
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import { SimpleTableRender } from 'native-document/ui';
|
|
20
|
+
|
|
21
|
+
SimpleTable.use(SimpleTableRender);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Methods
|
|
25
|
+
|
|
26
|
+
| Method | Parameters | Description |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `.column(key, title, props?, callback?)` | `key: string`, `title: string`, `props?: object`, `callback?: (value, row) => element` | Add a column. `callback` renders a custom cell. |
|
|
29
|
+
| `.group(title, fn)` | `title: string`, `fn: (group) => void` | Group columns under a shared header. |
|
|
30
|
+
| `.data(data)` | `data: array \| ObservableArray` | Static array or reactive observable array |
|
|
31
|
+
| `.empty(element)` | `element: NdChild` | Content shown when data is empty |
|
|
32
|
+
| `.noHeader()` | - | Hide the table header row |
|
|
33
|
+
| `.onRowClick(handler)` | `handler: (row) => void` | Fired when a row is clicked |
|
|
34
|
+
| `.rowProps(fn)` | `fn: (row) => object` | Dynamic HTML attributes per row |
|
|
35
|
+
|
|
36
|
+
## Example
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
SimpleTable()
|
|
40
|
+
.column('name', 'Name')
|
|
41
|
+
.column('email', 'Email')
|
|
42
|
+
.column('role', 'Role', {}, (value) => Badge(value).primary())
|
|
43
|
+
.column('actions', '', {}, (value, row) =>
|
|
44
|
+
HStack([
|
|
45
|
+
Button('Edit')
|
|
46
|
+
.small()
|
|
47
|
+
.nd.onClick(() => edit(row)),
|
|
48
|
+
Button('Delete')
|
|
49
|
+
.small()
|
|
50
|
+
.danger()
|
|
51
|
+
.nd.onClick(() => remove(row))
|
|
52
|
+
]).spacing(4)
|
|
53
|
+
)
|
|
54
|
+
.data(users)
|
|
55
|
+
.empty(Div('No users found'))
|
|
56
|
+
.onRowClick((row) => openUserDetail(row))
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Column Groups
|
|
60
|
+
|
|
61
|
+
Group related columns under a shared header:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
SimpleTable()
|
|
65
|
+
.column('name', 'Name')
|
|
66
|
+
.group('Address', (group) => {
|
|
67
|
+
group.column('city', 'City')
|
|
68
|
+
group.column('country', 'Country')
|
|
69
|
+
group.column('zip', 'ZIP')
|
|
70
|
+
})
|
|
71
|
+
.column('actions', '', {}, (value, row) => Button('Edit').small())
|
|
72
|
+
.data(users)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## SimpleTable vs DataTable
|
|
78
|
+
|
|
79
|
+
| | SimpleTable | DataTable |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| **Sorting** | Manual | Built-in |
|
|
82
|
+
| **Filtering / Search** | Manual | Built-in |
|
|
83
|
+
| **Pagination** | Manual | Built-in |
|
|
84
|
+
| **Selection** | No | Yes |
|
|
85
|
+
| **Server-side** | Manual | Built-in |
|
|
86
|
+
| **Use when** | Small static or observable list | Large dataset with sort/filter/pagination |
|
|
87
|
+
|
|
88
|
+
See **[DataTable](./data-table.md)** for the full-featured alternative.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Theming
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
:root {
|
|
97
|
+
--simple-table-font-size: var(--description-size);
|
|
98
|
+
--simple-table-cell-padding-v: var(--space-cozy);
|
|
99
|
+
--simple-table-cell-padding-h: var(--space-comfortable);
|
|
100
|
+
--simple-table-border-color: var(--color-border-tertiary);
|
|
101
|
+
--simple-table-header-color: var(--color-text-secondary);
|
|
102
|
+
--simple-table-header-bg: var(--color-background-secondary);
|
|
103
|
+
--simple-table-header-weight: 600;
|
|
104
|
+
--simple-table-hover-bg: var(--color-background-secondary);
|
|
105
|
+
--simple-table-empty-color: var(--color-text-secondary);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Skeleton
|
|
3
|
+
description: Placeholder that mimics content shape while loading
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skeleton
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
import { Skeleton } from 'native-document/components';
|
|
10
|
+
|
|
11
|
+
Skeleton(props?)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
A placeholder that mimics the shape of content while it loads.
|
|
15
|
+
|
|
16
|
+
## Default Renderer
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import { SkeletonRender } from 'native-document/ui';
|
|
20
|
+
|
|
21
|
+
Skeleton.use(SkeletonRender);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `$description`
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
{
|
|
28
|
+
type: 'rect', // 'rect' | 'circle' | 'text' | 'avatar' | 'image'
|
|
29
|
+
variant: 'pulse', // 'pulse' | 'wave'
|
|
30
|
+
borderRadiusType: 'rounded',
|
|
31
|
+
lines: null, // number - for text type
|
|
32
|
+
width: null,
|
|
33
|
+
height: null,
|
|
34
|
+
loading: null, // Observable<boolean>
|
|
35
|
+
repeat: null, // number - repeat N times
|
|
36
|
+
props: {} // HTML attributes for the root element
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Methods
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// Type
|
|
44
|
+
.rect()
|
|
45
|
+
.circle()
|
|
46
|
+
.text(lines?)
|
|
47
|
+
.avatar()
|
|
48
|
+
.image()
|
|
49
|
+
|
|
50
|
+
// Animation
|
|
51
|
+
.pulse()
|
|
52
|
+
.wave()
|
|
53
|
+
|
|
54
|
+
// Size
|
|
55
|
+
.width(200)
|
|
56
|
+
.height(100)
|
|
57
|
+
.size(width, height)
|
|
58
|
+
|
|
59
|
+
// Shape
|
|
60
|
+
.rounded()
|
|
61
|
+
.pill()
|
|
62
|
+
.smooth()
|
|
63
|
+
|
|
64
|
+
// Options
|
|
65
|
+
.lines(3)
|
|
66
|
+
.repeat(4)
|
|
67
|
+
.loading(isLoading)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Built-in Presets
|
|
71
|
+
|
|
72
|
+
Skeleton ships with four ready-to-use presets:
|
|
73
|
+
|
|
74
|
+
### `Skeleton.card(type?)`
|
|
75
|
+
|
|
76
|
+
A card with an image area and text lines below:
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
Skeleton.card()
|
|
80
|
+
Skeleton.card('featured') // adds type as extra CSS class
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `Skeleton.list(items?)`
|
|
84
|
+
|
|
85
|
+
A list of rows with avatar and text lines. Default: 3 items:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
Skeleton.list()
|
|
89
|
+
Skeleton.list(5)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `Skeleton.table(rows?, cols?)`
|
|
93
|
+
|
|
94
|
+
A table with a header row and data rows. Default: 5 rows, 4 cols:
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
Skeleton.table()
|
|
98
|
+
Skeleton.table(10, 6)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `Skeleton.paragraph(lines?)`
|
|
102
|
+
|
|
103
|
+
A block of text lines. Default: 3 lines:
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
Skeleton.paragraph()
|
|
107
|
+
Skeleton.paragraph(5)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Custom Presets
|
|
113
|
+
|
|
114
|
+
Register your own reusable shapes with `Skeleton.preset()` or `Skeleton.presets()`:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
Skeleton.preset('userRow', (skeleton) => {
|
|
118
|
+
return skeleton.avatar().circle().size(40, 40);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Register multiple at once
|
|
122
|
+
Skeleton.presets({
|
|
123
|
+
title: (s) => s.rect().width(200).height(24).rounded(),
|
|
124
|
+
avatar: (s) => s.circle().size(48, 48),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Usage
|
|
128
|
+
Skeleton.userRow()
|
|
129
|
+
Skeleton.title()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Example
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
ShowIf(isLoading, () =>
|
|
136
|
+
VStack([
|
|
137
|
+
Skeleton().avatar().circle().size(48, 48),
|
|
138
|
+
Skeleton().text(3)
|
|
139
|
+
]).spacing(8)
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Theming
|
|
146
|
+
|
|
147
|
+
```css
|
|
148
|
+
:root {
|
|
149
|
+
--skeleton-color: var(--gray-lite-4);
|
|
150
|
+
--skeleton-highlight: var(--gray-lite-5);
|
|
151
|
+
--skeleton-radius: var(--radius-button);
|
|
152
|
+
--skeleton-wave-duration: 1.6s;
|
|
153
|
+
--skeleton-pulse-duration: 1.2s;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Spinner
|
|
3
|
+
description: Loading indicator with multiple types, sizes, and overlay support
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Spinner
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
import { Spinner } from 'native-document/components';
|
|
10
|
+
|
|
11
|
+
Spinner(props?)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
A loading indicator.
|
|
15
|
+
|
|
16
|
+
## Default Renderer
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import { SpinnerRender } from 'native-document/ui';
|
|
20
|
+
|
|
21
|
+
Spinner.use(SpinnerRender);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `$description`
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
{
|
|
28
|
+
type: 'circle', // 'circle' | 'dots' | 'bars' | 'pulse' | 'ring'
|
|
29
|
+
variant: 'primary',
|
|
30
|
+
color: null,
|
|
31
|
+
size: 'small', // 'xs' | 'small' | 'medium' | 'large'
|
|
32
|
+
label: null,
|
|
33
|
+
labelPosition: null, // 'top' | 'bottom' | 'left' | 'right'
|
|
34
|
+
overlay: null,
|
|
35
|
+
backdrop: null,
|
|
36
|
+
fullScreenOverlay: null,
|
|
37
|
+
speed: 'normal', // 'slow' | 'normal' | 'fast'
|
|
38
|
+
props: {} // HTML attributes for the root element
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Methods
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
// Type
|
|
46
|
+
.circle()
|
|
47
|
+
.dots()
|
|
48
|
+
.bars()
|
|
49
|
+
.pulse()
|
|
50
|
+
.ring()
|
|
51
|
+
|
|
52
|
+
// Size
|
|
53
|
+
.extraSmall()
|
|
54
|
+
.small()
|
|
55
|
+
.medium()
|
|
56
|
+
.large()
|
|
57
|
+
|
|
58
|
+
// Style
|
|
59
|
+
.primary()
|
|
60
|
+
.color('#3b82f6')
|
|
61
|
+
.speed('fast')
|
|
62
|
+
|
|
63
|
+
// Label
|
|
64
|
+
.label('Loading...')
|
|
65
|
+
.labelPosition('bottom')
|
|
66
|
+
|
|
67
|
+
// Overlay
|
|
68
|
+
.overlay()
|
|
69
|
+
.fullScreenOverlay()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Example
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
ShowIf(isLoading, () => Spinner().large().label('Loading...'))
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Theming
|
|
81
|
+
|
|
82
|
+
```css
|
|
83
|
+
:root {
|
|
84
|
+
--spinner-size-extra-small: 16px;
|
|
85
|
+
--spinner-size-small: 24px;
|
|
86
|
+
--spinner-size-medium: 36px;
|
|
87
|
+
--spinner-size-large: 48px;
|
|
88
|
+
--spinner-size-extra-large: 64px;
|
|
89
|
+
--spinner-speed-slow: 1.2s;
|
|
90
|
+
--spinner-speed-normal: 0.8s;
|
|
91
|
+
--spinner-speed-fast: 0.4s;
|
|
92
|
+
--spinner-thickness: 3px;
|
|
93
|
+
--spinner-color: var(--color-primary);
|
|
94
|
+
--spinner-color-primary: var(--color-primary);
|
|
95
|
+
--spinner-color-secondary: var(--color-secondary-text);
|
|
96
|
+
--spinner-color-success: var(--color-success);
|
|
97
|
+
--spinner-color-danger: var(--color-danger);
|
|
98
|
+
--spinner-color-warning: var(--color-warning);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Splitter
|
|
3
|
+
description: Resizable split panel layout with horizontal and vertical orientations
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Splitter
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
import { Splitter, SplitterPanel, SplitterGutter } from 'native-document/components';
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Default Renderer
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
import { SplitterRender, SplitterPanelRender, SplitterGutterRender } from 'native-document/ui';
|
|
16
|
+
|
|
17
|
+
Splitter.use(SplitterRender);
|
|
18
|
+
SplitterPanel.use(SplitterPanelRender);
|
|
19
|
+
SplitterGutter.use(SplitterGutterRender);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## `Splitter`
|
|
25
|
+
|
|
26
|
+
### Methods
|
|
27
|
+
|
|
28
|
+
| Method | Parameters | Description |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `.horizontal()` | - | Side-by-side layout (default) |
|
|
31
|
+
| `.vertical()` | - | Stacked layout |
|
|
32
|
+
| `.gutterSize(px)` | `px: number` | Width of the drag handle in px |
|
|
33
|
+
| `.panel(content, options?, props?)` | `content: NdChild`, `options?: object` | Add a panel inline |
|
|
34
|
+
| `.panels(panels)` | `panels: SplitterPanel[]` | Add multiple panels at once |
|
|
35
|
+
| `.dynamic()` | - | Enable dynamic panel management (add/remove at runtime) |
|
|
36
|
+
| `.removePanel(panel)` | `panel: SplitterPanel` | Remove a panel dynamically |
|
|
37
|
+
| `.onResize(handler)` | `handler: (sizes) => void` | Fires on every resize |
|
|
38
|
+
| `.onPanelAdd(handler)` | `handler: (panel) => void` | Fires when a panel is added dynamically |
|
|
39
|
+
| `.onPanelRemove(handler)` | `handler: (panel) => void` | Fires when a panel is removed dynamically |
|
|
40
|
+
|
|
41
|
+
The inline `options` object for `.panel(content, options)` accepts the same properties as `SplitterPanel` methods: `size`, `minSize`, `maxSize`, `collapsible`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## `SplitterPanel`
|
|
46
|
+
|
|
47
|
+
| Method | Parameters | Description |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `.content(element)` | `element: NdChild` | Panel content |
|
|
50
|
+
| `.size(value)` | `value: string \| number` | Initial size (`'30%'`, `300`) |
|
|
51
|
+
| `.minSize(px)` | `px: number` | Minimum size in px |
|
|
52
|
+
| `.maxSize(px)` | `px: number` | Maximum size in px |
|
|
53
|
+
| `.collapsible(enabled?)` | `enabled?: boolean` | Allow collapsing to 0 |
|
|
54
|
+
| `.collapsed(val?)` | `val?: boolean` | Start collapsed |
|
|
55
|
+
| `.resizable(enabled?)` | `enabled?: boolean` | Enable/disable resize for this panel |
|
|
56
|
+
| `.fixed()` | - | Panel is not resizable and keeps its size |
|
|
57
|
+
| `.data(data)` | `data: *` | Attach metadata to the panel |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## `SplitterGutter`
|
|
62
|
+
|
|
63
|
+
The drag handle between panels. No extra methods - its appearance is controlled entirely by the renderer.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Dynamic panels
|
|
68
|
+
|
|
69
|
+
Use `.dynamic()` to add or remove panels at runtime. Track a panel reference to remove it later:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const sidebar = SplitterPanel(SidebarContent)
|
|
73
|
+
.size('25%')
|
|
74
|
+
.minSize(150)
|
|
75
|
+
.collapsible();
|
|
76
|
+
|
|
77
|
+
const splitter = Splitter()
|
|
78
|
+
.dynamic()
|
|
79
|
+
.panel(sidebar)
|
|
80
|
+
.panel(SplitterPanel(MainContent).size('75%'))
|
|
81
|
+
.onPanelAdd((panel) => console.log('Panel added'))
|
|
82
|
+
.onPanelRemove((panel) => console.log('Panel removed'));
|
|
83
|
+
|
|
84
|
+
// Remove the sidebar later
|
|
85
|
+
Button('Hide sidebar').nd.onClick(() => splitter.removePanel(sidebar))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Example
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
Splitter()
|
|
94
|
+
.horizontal()
|
|
95
|
+
.gutterSize(4)
|
|
96
|
+
.panel(
|
|
97
|
+
SplitterPanel(FileExplorer)
|
|
98
|
+
.size('20%')
|
|
99
|
+
.minSize(150)
|
|
100
|
+
.collapsible()
|
|
101
|
+
)
|
|
102
|
+
.panel(
|
|
103
|
+
SplitterPanel(
|
|
104
|
+
Splitter()
|
|
105
|
+
.vertical()
|
|
106
|
+
.panel(CodeEditor, { size: '70%' })
|
|
107
|
+
.panel(Terminal, { size: '30%', minSize: 100 })
|
|
108
|
+
).size('80%')
|
|
109
|
+
)
|
|
110
|
+
.onResize((sizes) => savePanelSizes(sizes))
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Theming
|
|
117
|
+
|
|
118
|
+
```css
|
|
119
|
+
:root {
|
|
120
|
+
--splitter-gutter-size: 1px;
|
|
121
|
+
--splitter-gutter-hit-area: 8px;
|
|
122
|
+
--splitter-gutter-color: var(--gray-lite-3);
|
|
123
|
+
--splitter-gutter-handle-size: 32px;
|
|
124
|
+
--splitter-gutter-handle-color: var(--gray-lite-2);
|
|
125
|
+
--splitter-gutter-handle-color-hover: var(--color-primary);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Next Steps
|
|
132
|
+
|
|
133
|
+
- **[Layout](./layout.md)** - Stack-based layout components
|