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,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tabs
|
|
3
|
+
description: Tabs component with sortable tabs, closable tabs, overflow handling, and flexible navigation positioning
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tabs
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
import { Tabs } from 'native-document/components';
|
|
10
|
+
|
|
11
|
+
Tabs(props?)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Default Renderer
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import { TabsRender } from 'native-document/ui';
|
|
18
|
+
|
|
19
|
+
Tabs.use(TabsRender);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## `$description`
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
{
|
|
26
|
+
active: Observable(''),
|
|
27
|
+
tabs: {},
|
|
28
|
+
sortable: false,
|
|
29
|
+
tabAppearance: 'segmented', // 'segmented' | 'pills' | 'underline'
|
|
30
|
+
stickyHeader: false,
|
|
31
|
+
overflow: 'scroll', // 'scroll' | 'menu'
|
|
32
|
+
navigationBarPosition:'top',
|
|
33
|
+
tabsAlignment: 'leading', // 'leading' | 'trailing' | 'center' | 'justified'
|
|
34
|
+
closable: false,
|
|
35
|
+
focusOnNewTab: false,
|
|
36
|
+
props: {} // HTML attributes for the root element
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Methods
|
|
41
|
+
|
|
42
|
+
### Building tabs
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
.tab(label, content, key?)
|
|
46
|
+
.tabWithIcon(icon, label, content, key?)
|
|
47
|
+
|
|
48
|
+
.tab('Overview', OverviewPanel, 'overview')
|
|
49
|
+
.tabWithIcon(HomeIcon, 'Dashboard', DashboardPanel, 'dashboard')
|
|
50
|
+
|
|
51
|
+
.tabs([
|
|
52
|
+
{ key: 'home', label: 'Home', content: HomePanel },
|
|
53
|
+
{ key: 'profile', label: 'Profile', content: ProfilePanel }
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
.addTab(null, 'New Tab', EmptyPanel, 'tab-1')
|
|
57
|
+
.closeTab('settings')
|
|
58
|
+
|
|
59
|
+
.active('overview')
|
|
60
|
+
.active(Observable('overview'))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Appearance
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
.pills()
|
|
67
|
+
.segmented()
|
|
68
|
+
.underline()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Navigation position
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
.navigationBarAtTop()
|
|
75
|
+
.navigationBarAtLeft()
|
|
76
|
+
.navigationBarAtRight()
|
|
77
|
+
.navigationBarAsDock()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Alignment
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
.tabsAtLeading()
|
|
84
|
+
.tabsAtTrailing()
|
|
85
|
+
.tabsAtCenter()
|
|
86
|
+
.tabsJustified()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Behavior
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
.sortable()
|
|
93
|
+
.closable()
|
|
94
|
+
.stickyHeader()
|
|
95
|
+
.focusOnNewTab()
|
|
96
|
+
.overflow('scroll')
|
|
97
|
+
.overflow('menu')
|
|
98
|
+
.addPlusButton((tabs) => {
|
|
99
|
+
const key = `tab-${Date.now()}`;
|
|
100
|
+
tabs.addTab(null, 'New Tab', EmptyPanel, key);
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Events
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
.onChange((key) => loadTabContent(key))
|
|
108
|
+
.onClickTab((key) => console.log('Clicked:', key))
|
|
109
|
+
.onCloseTab((key) => confirmClose(key))
|
|
110
|
+
.onBeforeTabClose((key) => confirm('Close this tab?'))
|
|
111
|
+
.onAddTab((key) => console.log('Tab added:', key))
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Custom renderers
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
.renderTab(($tab) => HStack([$tab.icon, Span($tab.label)]).spacing(4))
|
|
118
|
+
.renderCloseButton(() => Span('x'))
|
|
119
|
+
.renderPlusButton(() => Span('+'))
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Example
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
Tabs()
|
|
126
|
+
.tabWithIcon(HomeIcon, 'Dashboard', DashboardPanel, 'dashboard')
|
|
127
|
+
.tabWithIcon(UsersIcon, 'Users', UsersPanel, 'users')
|
|
128
|
+
.tabWithIcon(SettingsIcon, 'Settings', SettingsPanel, 'settings')
|
|
129
|
+
.active('dashboard')
|
|
130
|
+
.tabsJustified()
|
|
131
|
+
.stickyHeader()
|
|
132
|
+
.onChange((key) => Router.push({ name: key }))
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Theming
|
|
138
|
+
|
|
139
|
+
```css
|
|
140
|
+
:root {
|
|
141
|
+
--tabs-border: var(--gray-lite-3);
|
|
142
|
+
--tabs-radius: var(--radius-button);
|
|
143
|
+
--tabs-font-size: var(--description-size);
|
|
144
|
+
--tabs-font-weight: 500;
|
|
145
|
+
--tab-padding: var(--space-cozy) var(--space-comfortable);
|
|
146
|
+
--tab-color: var(--gray);
|
|
147
|
+
--tab-color-active: var(--color-primary);
|
|
148
|
+
--tab-color-hover: var(--text-color);
|
|
149
|
+
--tab-bg-hover: var(--gray-lite-5);
|
|
150
|
+
--tab-indicator-height: 2px;
|
|
151
|
+
--tabs-content-padding: var(--space-comfortable) 0;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Toast
|
|
3
|
+
description: Toast notification component with auto-dismiss, actions, and positioning
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Toast
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
import { Toast } from 'native-document/components';
|
|
10
|
+
|
|
11
|
+
Toast(content, props?)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Default Renderer
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import { ToastRender } from 'native-document/ui';
|
|
18
|
+
import { ButtonRender } from 'native-document/ui';
|
|
19
|
+
|
|
20
|
+
Toast.use(ToastRender);
|
|
21
|
+
Button.use(ButtonRender); // required - actions are rendered as buttons
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `$description`
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
{
|
|
28
|
+
visibility: Observable(true),
|
|
29
|
+
type: null, // 'info' | 'success' | 'warning' | 'error'
|
|
30
|
+
title: null,
|
|
31
|
+
content: null,
|
|
32
|
+
icon: null,
|
|
33
|
+
showIcon: true,
|
|
34
|
+
duration: 5000, // ms, 0 = no auto-dismiss
|
|
35
|
+
closable: true,
|
|
36
|
+
pauseOnHover: true,
|
|
37
|
+
position: 'top-trailing',
|
|
38
|
+
actions: [],
|
|
39
|
+
props: {} // HTML attributes for the root element
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Methods
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
// Type
|
|
47
|
+
.info()
|
|
48
|
+
.success()
|
|
49
|
+
.warning()
|
|
50
|
+
.error()
|
|
51
|
+
|
|
52
|
+
// Content
|
|
53
|
+
.title('Saved!')
|
|
54
|
+
.content(Div('Your changes have been saved.'))
|
|
55
|
+
.icon(CheckIcon)
|
|
56
|
+
.showIcon(false)
|
|
57
|
+
|
|
58
|
+
// Behavior
|
|
59
|
+
.duration(3000)
|
|
60
|
+
.duration(0) // no auto-dismiss
|
|
61
|
+
.closable(false)
|
|
62
|
+
.pauseOnHover(false)
|
|
63
|
+
|
|
64
|
+
// Position
|
|
65
|
+
.atTopLeading()
|
|
66
|
+
.atTopTrailing() // default
|
|
67
|
+
.atTopCenter()
|
|
68
|
+
.atBottomLeading()
|
|
69
|
+
.atBottomTrailing()
|
|
70
|
+
.atBottomCenter()
|
|
71
|
+
|
|
72
|
+
// Actions
|
|
73
|
+
.action('Undo', () => undo())
|
|
74
|
+
.action('Retry', retry)
|
|
75
|
+
|
|
76
|
+
// Programmatic
|
|
77
|
+
.close()
|
|
78
|
+
|
|
79
|
+
// Events
|
|
80
|
+
.onClose(() => console.log('Dismissed'))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Example
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
const saveToast = Toast('Your changes have been saved.')
|
|
87
|
+
.success()
|
|
88
|
+
.title('Saved!')
|
|
89
|
+
.duration(3000)
|
|
90
|
+
.atTopTrailing()
|
|
91
|
+
.action('Undo', () => undoChanges())
|
|
92
|
+
|
|
93
|
+
Button('Save')
|
|
94
|
+
.primary()
|
|
95
|
+
.nd.onClick(async () => {
|
|
96
|
+
await saveData();
|
|
97
|
+
saveToast.show()
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Theming
|
|
104
|
+
|
|
105
|
+
```css
|
|
106
|
+
:root {
|
|
107
|
+
--toast-width: 320px;
|
|
108
|
+
--toast-padding: var(--space-comfortable);
|
|
109
|
+
--toast-gap: var(--space-cozy);
|
|
110
|
+
--toast-radius: var(--radius-card);
|
|
111
|
+
--toast-font-size: var(--note-size);
|
|
112
|
+
--toast-title-size: var(--description-size);
|
|
113
|
+
--toast-title-weight: 600;
|
|
114
|
+
--toast-shadow: var(--shadow-lg);
|
|
115
|
+
--toast-container-gap: var(--space-cozy);
|
|
116
|
+
--toast-container-offset: var(--space-comfortable);
|
|
117
|
+
--toast-duration: 0.25s;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tooltip
|
|
3
|
+
description: Tooltip component with automatic trigger binding, positioning, and interactive mode
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tooltip
|
|
7
|
+
|
|
8
|
+
```javascript
|
|
9
|
+
import { Tooltip } from 'native-document/components';
|
|
10
|
+
|
|
11
|
+
Tooltip(content, props?)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
When `Tooltip.use()` is called, it automatically adds a `.nd.tooltip()` method to all NDElements and BaseComponents.
|
|
15
|
+
|
|
16
|
+
## Default Renderer
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import { TooltipRender } from 'native-document/ui';
|
|
20
|
+
|
|
21
|
+
Tooltip.use(TooltipRender);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `$description`
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
{
|
|
28
|
+
trigger: null,
|
|
29
|
+
interaction: 'hover', // 'hover' | 'click' | 'focus'
|
|
30
|
+
content: null,
|
|
31
|
+
title: null,
|
|
32
|
+
position: 'top',
|
|
33
|
+
isOpen: Observable(false),
|
|
34
|
+
offset: 8,
|
|
35
|
+
hideDelay: 0,
|
|
36
|
+
arrow: true,
|
|
37
|
+
interactive: true,
|
|
38
|
+
updatePositionOn: null,
|
|
39
|
+
variant: null,
|
|
40
|
+
props: {}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Methods
|
|
45
|
+
|
|
46
|
+
| Method | Parameters | Description |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `.content(element)` | `element: NdChild` | Tooltip body content |
|
|
49
|
+
| `.title(text)` | `text: string` | Tooltip title |
|
|
50
|
+
| `.trigger(element)` | `element: HTMLElement` | Element that opens the tooltip |
|
|
51
|
+
| `.onHovered()` | - | Open on hover (default) |
|
|
52
|
+
| `.onClicked()` | - | Open on click |
|
|
53
|
+
| `.onFocused()` | - | Open on focus |
|
|
54
|
+
| `.atTop()` | - | Position above trigger |
|
|
55
|
+
| `.atBottom()` | - | Position below trigger |
|
|
56
|
+
| `.atLeft()` | - | Position left of trigger |
|
|
57
|
+
| `.atRight()` | - | Position right of trigger |
|
|
58
|
+
| `.arrow(enabled?)` | `enabled?: boolean` | Show/hide the arrow. Default: `true` |
|
|
59
|
+
| `.offset(px)` | `px: number` | Distance from the trigger in px |
|
|
60
|
+
| `.hideDelay(ms)` | `ms: number` | Delay before hiding on hover leave |
|
|
61
|
+
| `.interactive(enabled)` | `enabled: boolean` | Keep open when hovering the tooltip itself |
|
|
62
|
+
| `.updatePositionOn(observable)` | `observable: Observable` | Recalculate position when the observable changes |
|
|
63
|
+
| `.open()` | - | Open programmatically |
|
|
64
|
+
| `.close()` | - | Close programmatically |
|
|
65
|
+
| `.toggle()` | - | Toggle open/close |
|
|
66
|
+
|
|
67
|
+
## Via `.nd.tooltip()`
|
|
68
|
+
|
|
69
|
+
Once registered, any element gains `.nd.tooltip()`:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
// Simple string
|
|
73
|
+
Button('Delete')
|
|
74
|
+
.danger()
|
|
75
|
+
.nd.tooltip('Permanently delete this item')
|
|
76
|
+
|
|
77
|
+
// Tooltip instance for full control
|
|
78
|
+
const hint = Tooltip(
|
|
79
|
+
VStack([
|
|
80
|
+
Strong('Keyboard shortcut'),
|
|
81
|
+
Span('Cmd S')
|
|
82
|
+
]).spacing(4)
|
|
83
|
+
)
|
|
84
|
+
.atBottom()
|
|
85
|
+
.arrow()
|
|
86
|
+
|
|
87
|
+
Button('Save')
|
|
88
|
+
.primary()
|
|
89
|
+
.nd.tooltip(hint)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Presets
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
Tooltip.preset('shortcut', (content, props) => {
|
|
96
|
+
return Tooltip(content, props)
|
|
97
|
+
.atBottom()
|
|
98
|
+
.hideDelay(100);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
Button('Save').nd.tooltip(Tooltip.shortcut('Cmd S'))
|
|
102
|
+
Button('Open').nd.tooltip(Tooltip.shortcut('Cmd O'))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## `updatePositionOn`
|
|
106
|
+
|
|
107
|
+
Pass an observable to trigger a position recalculation when its value changes - useful when the trigger element moves or resizes dynamically:
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
const isExpanded = Observable(false);
|
|
111
|
+
|
|
112
|
+
Tooltip('More info')
|
|
113
|
+
.trigger(myButton)
|
|
114
|
+
.updatePositionOn(isExpanded)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Tooltip vs Popover
|
|
120
|
+
|
|
121
|
+
| | Tooltip | Popover |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| **Purpose** | Short contextual hint | Rich floating panel |
|
|
124
|
+
| **Content** | Text or simple element | Header, body, footer |
|
|
125
|
+
| **Triggered by** | Hover (default) | Click (default) |
|
|
126
|
+
| **Focus trap** | No | Optional |
|
|
127
|
+
| **Use when** | Labeling an icon, short help text | User profile card, settings panel |
|
|
128
|
+
|
|
129
|
+
See **[Popover](./popover.md)** for the richer alternative.
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Theming
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
:root {
|
|
138
|
+
--tooltip-bg: #1a1a2e;
|
|
139
|
+
--tooltip-color: var(--white);
|
|
140
|
+
--tooltip-border: transparent;
|
|
141
|
+
--tooltip-radius: var(--radius-button);
|
|
142
|
+
--tooltip-shadow: var(--shadow-lg);
|
|
143
|
+
--tooltip-padding: var(--space-cozy) var(--space-cozy-comfortable);
|
|
144
|
+
--tooltip-min-width: 0;
|
|
145
|
+
--tooltip-max-width: 280px;
|
|
146
|
+
--tooltip-z-index: 100001;
|
|
147
|
+
--tooltip-font-size: var(--note-size);
|
|
148
|
+
--tooltip-arrow-size: 6px;
|
|
149
|
+
--tooltip-animation-duration: 0.12s;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Traits
|
|
3
|
+
description: Reusable behavior mixins for NativeDocument components - HasEventEmitter, HasDraggable, HasResizable, HasItems, HasPosition
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Traits
|
|
7
|
+
|
|
8
|
+
Traits are behavior mixins that add capabilities to components. They are applied via `BaseComponent.use()` and follow the same composition pattern as the rest of the framework.
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
import { BaseComponent, HasEventEmitter, HasDraggable, HasResizable } from '@native-document/components';
|
|
12
|
+
|
|
13
|
+
BaseComponent.use(MyComponent, HasEventEmitter, HasDraggable, HasResizable);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> `HasDraggable` and `HasResizable` both **require** `HasEventEmitter` to be applied first.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## `HasEventEmitter`
|
|
21
|
+
|
|
22
|
+
Adds a custom event system to a component. Components use this to communicate state changes to their parent.
|
|
23
|
+
|
|
24
|
+
### Methods
|
|
25
|
+
|
|
26
|
+
| Method | Parameters | Description |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `.on(eventName, callback)` | `eventName: string`, `callback: function` | Register a listener. Multiple listeners allowed per event. |
|
|
29
|
+
| `.emit(eventName, ...args)` | `eventName: string`, `...args` | Emit an event. Async - all listeners awaited in order. |
|
|
30
|
+
| `.trigger(eventName, ...args)` | same as `emit` | Alias for `emit`. |
|
|
31
|
+
| `.hasListeners(eventName)` | `eventName: string` | Returns `true` if any listeners are registered. |
|
|
32
|
+
|
|
33
|
+
### Built-in component events
|
|
34
|
+
|
|
35
|
+
| Event | When |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `open` | Component becomes visible |
|
|
38
|
+
| `close` | Component is dismissed |
|
|
39
|
+
| `change` | Value or selection changes |
|
|
40
|
+
| `select` | An item is selected |
|
|
41
|
+
|
|
42
|
+
### Example
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
import { Modal } from '@native-document/components';
|
|
46
|
+
|
|
47
|
+
const confirmModal = Modal(Div('Are you sure?'))
|
|
48
|
+
.title('Confirm')
|
|
49
|
+
.on('close', (confirmed) => {
|
|
50
|
+
if (confirmed) deleteItem();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
Button('Delete')
|
|
54
|
+
.danger()
|
|
55
|
+
.nd.onClick(() => confirmModal.open())
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## `HasDraggable`
|
|
61
|
+
|
|
62
|
+
Adds mouse-driven drag behavior to a component. Requires `HasEventEmitter`.
|
|
63
|
+
|
|
64
|
+
### Methods
|
|
65
|
+
|
|
66
|
+
| Method | Parameters | Description |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `.makeDraggable(element, grip?)` | `element: HTMLElement`, `grip?: HTMLElement` | Makes the element draggable. Returns a cleanup function. `grip` defaults to `element`. |
|
|
69
|
+
| `.move(x, y)` | `x: number`, `y: number` | Programmatically move to an absolute position (px). |
|
|
70
|
+
|
|
71
|
+
### CSS classes
|
|
72
|
+
|
|
73
|
+
| Class | When applied |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `is-draggable` | On the grip element |
|
|
76
|
+
| `is-dragging` | While drag is active |
|
|
77
|
+
|
|
78
|
+
### Events
|
|
79
|
+
|
|
80
|
+
| Event | Args | When |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `onDragStart` | `(event, initialX, initialY)` | Mouse down on grip |
|
|
83
|
+
| `onDrag` | `(event, x, y)` | Mouse move while dragging |
|
|
84
|
+
| `onDragEnd` | - | Mouse up |
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
MyModal
|
|
88
|
+
.on('onDragStart', (e, x, y) => console.log('Started at', x, y))
|
|
89
|
+
.on('onDrag', (e, x, y) => console.log('Moving to', x, y))
|
|
90
|
+
.on('onDragEnd', () => console.log('Done'))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## `HasResizable`
|
|
96
|
+
|
|
97
|
+
Adds mouse-driven resize behavior to a component. Requires `HasEventEmitter`.
|
|
98
|
+
|
|
99
|
+
### Methods
|
|
100
|
+
|
|
101
|
+
| Method | Parameters | Description |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `.makeResizable(element, options?)` | `element: HTMLElement`, `options?: object` | Makes the element resizable. Returns a cleanup function. |
|
|
104
|
+
|
|
105
|
+
Options:
|
|
106
|
+
|
|
107
|
+
| Option | Type | Default | Description |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `directions` | `string[]` | `['right', 'bottom', 'bottom-right']` | Which resize handles to show. Available: `right`, `left`, `top`, `bottom`, `top-right`, `top-left`, `bottom-right`, `bottom-left` |
|
|
110
|
+
| `size.minWidth` | `number` | `200` | Minimum width in px |
|
|
111
|
+
| `size.minHeight` | `number` | `200` | Minimum height in px |
|
|
112
|
+
| `size.maxWidth` | `number` | - | Maximum width in px |
|
|
113
|
+
| `size.maxHeight` | `number` | - | Maximum height in px |
|
|
114
|
+
|
|
115
|
+
### CSS classes
|
|
116
|
+
|
|
117
|
+
| Class | Applied to | When |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `is-resizable` | Parent element | Always (when resizable) |
|
|
120
|
+
| `resize-handle is-{direction}` | Handle divs | Always |
|
|
121
|
+
| `is-resizing` | `document.body` | While resize is active |
|
|
122
|
+
|
|
123
|
+
### Events
|
|
124
|
+
|
|
125
|
+
| Event | Args | When |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `onResizeStart` | `(event, width, height)` | Mouse down on handle |
|
|
128
|
+
| `onResize` | `(event, width, height)` | Mouse move while resizing |
|
|
129
|
+
| `onResizeEnd` | `(width, height)` | Mouse up |
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
MyPanel
|
|
133
|
+
.on('onResizeStart', (e, w, h) => console.log('Started at', w, h))
|
|
134
|
+
.on('onResize', (e, w, h) => saveSize(w, h))
|
|
135
|
+
.on('onResizeEnd', (w, h) => persist({ width: w, height: h }))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## `HasItems`
|
|
141
|
+
|
|
142
|
+
Manages a reactive collection of items. Used internally by `Accordion`, `Menu`, `Tabs`, `Dropdown`.
|
|
143
|
+
|
|
144
|
+
### Methods
|
|
145
|
+
|
|
146
|
+
| Method | Parameters | Description |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `.dynamic(observableArray?)` | `observableArray?: ObservableArray` | Binds to an existing array or creates a new `Observable.array()`. |
|
|
149
|
+
| `.bind(observableArray?)` | same as `dynamic` | Alias for `dynamic`. |
|
|
150
|
+
| `.items(items)` | `items: array` | Replaces all current items. |
|
|
151
|
+
| `.clear()` | - | Removes all items. |
|
|
152
|
+
| `.removeItem(item)` | `item: *` | Removes a specific item by reference. |
|
|
153
|
+
|
|
154
|
+
### Example
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
import { BaseComponent, HasEventEmitter, HasItems } from '@native-document/components';
|
|
158
|
+
import { Div, Span } from 'native-document/elements';
|
|
159
|
+
import { ForEach } from 'native-document/elements';
|
|
160
|
+
|
|
161
|
+
function TagList(props = {}) {
|
|
162
|
+
if (!(this instanceof TagList)) return new TagList(props);
|
|
163
|
+
BaseComponent.call(this);
|
|
164
|
+
this.$description = { items: [], props };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
BaseComponent.extends(TagList);
|
|
168
|
+
BaseComponent.use(TagList, HasEventEmitter, HasItems);
|
|
169
|
+
|
|
170
|
+
TagList.use(($d) => {
|
|
171
|
+
return Div({ class: 'tag-list' },
|
|
172
|
+
ForEach($d.items, tag => Span({ class: 'tag' }, tag))
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
TagList()
|
|
177
|
+
.dynamic()
|
|
178
|
+
.items(['JavaScript', 'TypeScript', 'Python'])
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## `HasPosition` / `HasFullPosition`
|
|
184
|
+
|
|
185
|
+
Position shorthand methods for components that accept a `position` prop (`Tooltip`, `Popover`, `Toast`). Applied internally - you do not need to apply them manually.
|
|
186
|
+
|
|
187
|
+
### `HasPosition` - 4 directions + center
|
|
188
|
+
|
|
189
|
+
| Method | Sets `position` to |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `.atTop()` | `'top'` |
|
|
192
|
+
| `.atBottom()` | `'bottom'` |
|
|
193
|
+
| `.atLeft()` | `'left'` |
|
|
194
|
+
| `.atRight()` | `'right'` |
|
|
195
|
+
| `.atCenter()` | `'center'` |
|
|
196
|
+
|
|
197
|
+
### `HasFullPosition` - all positions
|
|
198
|
+
|
|
199
|
+
| Method | Sets `position` to |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `.atTop()` | `'top'` |
|
|
202
|
+
| `.atBottom()` | `'bottom'` |
|
|
203
|
+
| `.atLeft()` | `'left'` |
|
|
204
|
+
| `.atRight()` | `'right'` |
|
|
205
|
+
| `.atTopLeading()` | `'top-leading'` |
|
|
206
|
+
| `.atTopTrailing()` | `'top-trailing'` |
|
|
207
|
+
| `.atTopCenter()` | `'top-center'` |
|
|
208
|
+
| `.atBottomLeading()` | `'bottom-leading'` |
|
|
209
|
+
| `.atBottomTrailing()` | `'bottom-trailing'` |
|
|
210
|
+
| `.atBottomCenter()` | `'bottom-center'` |
|
|
211
|
+
| `.atLeadingCenter()` | `'leading-center'` |
|
|
212
|
+
| `.atTrailingCenter()` | `'trailing-center'` |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Building Custom Components with Traits
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
import { BaseComponent, HasEventEmitter, HasDraggable } from '@native-document/components';
|
|
220
|
+
import { Div, H2 } from 'native-document/elements';
|
|
221
|
+
|
|
222
|
+
function FloatingPanel(title, content, props = {}) {
|
|
223
|
+
if (!(this instanceof FloatingPanel)) {
|
|
224
|
+
return new FloatingPanel(title, content, props);
|
|
225
|
+
}
|
|
226
|
+
BaseComponent.call(this);
|
|
227
|
+
this.$description = { title, content, props };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
FloatingPanel.defaultTemplate = null;
|
|
231
|
+
FloatingPanel.use = function(template) {
|
|
232
|
+
FloatingPanel.defaultTemplate = template;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
BaseComponent.extends(FloatingPanel);
|
|
236
|
+
BaseComponent.use(FloatingPanel, HasEventEmitter, HasDraggable);
|
|
237
|
+
|
|
238
|
+
FloatingPanel.use(($d, component) => {
|
|
239
|
+
const header = Div({ class: 'panel-header' }, H2($d.title));
|
|
240
|
+
const body = Div({ class: 'panel-body' }, $d.content);
|
|
241
|
+
const panel = Div({ class: 'panel', style: 'position: absolute', ...$d.props }, [header, body]);
|
|
242
|
+
|
|
243
|
+
component.postBuild(() => {
|
|
244
|
+
component.makeDraggable(panel, header);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return panel;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
FloatingPanel('Settings', Div('Panel content'))
|
|
251
|
+
.on('onDragEnd', () => console.log('Moved'))
|
|
252
|
+
.nd
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Next Steps
|
|
258
|
+
|
|
259
|
+
- **[Components Overview](./index.md)** - BaseComponent philosophy
|
|
260
|
+
- **[Modal](./modal.md)** - Uses HasEventEmitter, HasDraggable, HasResizable
|
|
261
|
+
- **[Getting Started](./getting-started.md)** - Register renderers
|