native-document 1.0.199 → 1.0.200

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/components.js CHANGED
@@ -5,6 +5,7 @@ export * from './src/components/badge/index';
5
5
  export * from './src/components/breadcrumb/index';
6
6
  export * from './src/components/button/index';
7
7
  export * from './src/components/card/index';
8
+ export * from './src/components/drawer/index';
8
9
  export * from './src/components/context-menu/index';
9
10
  export * from './src/components/divider/index';
10
11
  export * from './src/components/dropdown/index';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.199",
3
+ "version": "1.0.200",
4
4
  "description": "A reactive JavaScript framework that preserves native DOM simplicity without sacrificing modern features",
5
5
  "author": "AfroCodeur <https://github.com/afrocodeur>",
6
6
  "license": "MIT",
@@ -0,0 +1,344 @@
1
+ import BaseComponent from '../BaseComponent';
2
+ import HasEventEmitter from '../../core/utils/HasEventEmitter';
3
+ import DebugManager from '../../core/utils/debug-manager';
4
+
5
+ /**
6
+ * Side panel that slides in from an edge of the screen, with an overlay behind it.
7
+ * Supports position (left, right, top, bottom), size, closable, footer actions,
8
+ * and open/close lifecycle events.
9
+ *
10
+ *
11
+ * @example
12
+ * const drawer = new Drawer(Div('Drawer body content'))
13
+ * .title('Create ticket')
14
+ * .subtitle('This ticket will be visible for sale immediately')
15
+ * .position('right')
16
+ * .size('400px')
17
+ * .closable(true)
18
+ * .overlay(true)
19
+ * .action('Cancel', (_, instance) => instance.close())
20
+ * .action('Create ticket', () => console.log('create'), 'primary')
21
+ * .onClose(() => console.log('closed'));
22
+ *
23
+ * Drawer.use((description, instance) => {
24
+ * // description.content, description.position, description.actions...
25
+ * return Div({ class: `drawer drawer--${description.position}` }, description.content);
26
+ * });
27
+ *
28
+ * @constructor
29
+ * @param {NdChild} content
30
+ * @param {GlobalAttributes} [props={}]
31
+ */
32
+ export default function Drawer(content = null, props = {}) {
33
+ if(!(this instanceof Drawer)) {
34
+ return new Drawer(content, props);
35
+ }
36
+
37
+ BaseComponent.call(this, props);
38
+
39
+ this.$description = {
40
+ title: null,
41
+ subtitle: null,
42
+ content,
43
+ position: 'right',
44
+ size: '400px',
45
+ overlay: true,
46
+ closeOnOverlayClick: true,
47
+ closable: true,
48
+ actions: [],
49
+ renderContent: null,
50
+ renderFooter: null,
51
+ renderHeader: null,
52
+ backdrop: true,
53
+ isOpen: null,
54
+ props,
55
+ };
56
+ this.aria = {
57
+ 'role': 'dialog',
58
+ 'aria-modal': 'true',
59
+ 'aria-hidden': 'true',
60
+ };
61
+ }
62
+
63
+ Drawer.defaultTemplate = null;
64
+
65
+ /**
66
+ * Registers the render template for Drawer.
67
+ * @param {(description: {
68
+ * title: NdChild|null,
69
+ * subtitle: NdChild|null,
70
+ * content: NdChild,
71
+ * position: 'left'|'right'|'top'|'bottom',
72
+ * size: string,
73
+ * overlay: boolean,
74
+ * closeOnOverlayClick: boolean,
75
+ * closable: boolean,
76
+ * actions: Array<{ label: NdChild, handler: Function|null, variant: string|null }>,
77
+ * props: GlobalAttributes,
78
+ * }, instance: Drawer) => NdChild} template
79
+ */
80
+ Drawer.use = function(template) {
81
+ Drawer.defaultTemplate = template;
82
+ };
83
+
84
+ BaseComponent.extends(Drawer);
85
+ BaseComponent.use(Drawer, HasEventEmitter);
86
+
87
+ /**
88
+ * @param {string} name
89
+ * @param {(d: Drawer) => Drawer} callback
90
+ */
91
+ Drawer.preset = function(name, callback) {
92
+ if (Drawer.prototype[name] || Drawer[name]) {
93
+ DebugManager.warn(`Warning: the ${name} method already exist in Drawer.`);
94
+ return;
95
+ }
96
+ Drawer[name] = (content, props) => callback(new Drawer(content, props));
97
+ };
98
+
99
+ /**
100
+ * @param {Record<string, (d: Drawer) => Drawer>} presets
101
+ */
102
+ Drawer.presets = function(presets) {
103
+ for (const name in presets) {
104
+ Drawer.preset(name, presets[name]);
105
+ }
106
+ };
107
+
108
+ /**
109
+ * Sets which edge the drawer slides in from
110
+ * @param {'left'|'right'|'top'|'bottom'} position
111
+ * @returns {this}
112
+ */
113
+ Drawer.prototype.position = function(position) {
114
+ this.$description.position = position;
115
+ return this;
116
+ };
117
+
118
+ /**
119
+ * Sets the drawer to slide in from the left
120
+ * @returns {this}
121
+ */
122
+ Drawer.prototype.atLeft = function() {
123
+ return this.position('left');
124
+ };
125
+
126
+ /**
127
+ * Sets the drawer to slide in from the right
128
+ * @returns {this}
129
+ */
130
+ Drawer.prototype.atRight = function() {
131
+ return this.position('right');
132
+ };
133
+
134
+ /**
135
+ * Sets the drawer to slide in from the top
136
+ * @returns {this}
137
+ */
138
+ Drawer.prototype.atTop = function() {
139
+ return this.position('top');
140
+ };
141
+
142
+ /**
143
+ * Sets the drawer to slide in from the bottom
144
+ * @returns {this}
145
+ */
146
+ Drawer.prototype.atBottom = function() {
147
+ return this.position('bottom');
148
+ };
149
+
150
+ /**
151
+ * Sets the width (for left/right) or height (for top/bottom) of the drawer
152
+ * @param {string} size - e.g. '400px', '30%'
153
+ * @returns {this}
154
+ */
155
+ Drawer.prototype.size = function(size) {
156
+ this.$description.size = size;
157
+ return this;
158
+ };
159
+
160
+ /**
161
+ * Sets the title of the drawer header
162
+ * @param {ValidChildren} title
163
+ * @returns {this}
164
+ */
165
+ Drawer.prototype.title = function(title) {
166
+ this.$description.title = title;
167
+ return this;
168
+ };
169
+
170
+ /**
171
+ * Sets the subtitle shown under the title
172
+ * @param {ValidChildren} subtitle
173
+ * @returns {this}
174
+ */
175
+ Drawer.prototype.subtitle = function(subtitle) {
176
+ this.$description.subtitle = subtitle;
177
+ return this;
178
+ };
179
+
180
+ /**
181
+ * Sets the content of the drawer
182
+ * @param {ValidChildren} content
183
+ * @returns {this}
184
+ */
185
+ Drawer.prototype.content = function(content) {
186
+ this.$description.content = content;
187
+ return this;
188
+ };
189
+
190
+ /**
191
+ * Sets the content render function
192
+ * @param {Function} callback
193
+ * @returns {this}
194
+ */
195
+ Drawer.prototype.renderContent = function(callback) {
196
+ this.$description.renderContent = callback;
197
+ return this;
198
+ };
199
+
200
+ /**
201
+ * Sets the footer render function (defaults to the action buttons row)
202
+ * @param {Function} callback
203
+ * @returns {this}
204
+ */
205
+ Drawer.prototype.renderFooter = function(callback) {
206
+ this.$description.renderFooter = callback;
207
+ return this;
208
+ };
209
+
210
+
211
+ /**
212
+ * Sets the header render function (defaults titl, subtitle and close button)
213
+ * @param {Function} callback
214
+ * @returns {this}
215
+ */
216
+ Drawer.prototype.renderHeader = function(callback) {
217
+ this.$description.renderHeader = callback;
218
+ };
219
+
220
+ /**
221
+ * Whether to render a dimmed overlay behind the drawer
222
+ * @param {boolean} [overlay=true]
223
+ * @returns {this}
224
+ */
225
+ Drawer.prototype.overlay = function(overlay = true) {
226
+ this.$description.overlay = !!overlay;
227
+ return this;
228
+ };
229
+
230
+ /**
231
+ * @param {Boolean} backdrop
232
+ * @return {this}
233
+ */
234
+ Drawer.prototype.overlay = function(backdrop = true) {
235
+ this.$description.backdrop = !!backdrop;
236
+ return this;
237
+ };
238
+
239
+ /**
240
+ * Whether clicking the overlay closes the drawer
241
+ * @param {boolean} [closeOnOverlayClick=true]
242
+ * @returns {this}
243
+ */
244
+ Drawer.prototype.closeOnOverlayClick = function(closeOnOverlayClick = true) {
245
+ this.$description.closeOnOverlayClick = !!closeOnOverlayClick;
246
+ return this;
247
+ };
248
+
249
+ /**
250
+ * Whether the drawer shows a close (x) button and can be dismissed
251
+ * @param {boolean} [closable=true]
252
+ * @returns {this}
253
+ */
254
+ Drawer.prototype.closable = function(closable = true) {
255
+ this.$description.closable = !!closable;
256
+ if(closable) {
257
+ this.showIf(closable);
258
+ }
259
+ return this;
260
+ };
261
+
262
+ /**
263
+ * @param {Observable} observable
264
+ * @return {Drawer}
265
+ */
266
+ Drawer.prototype.isOpen = function(observable) {
267
+ this.$description.isOpen = observable;
268
+ observable.subscribe((isOpen) => {
269
+ this.emit(isOpen ? 'open' : 'close');
270
+ });
271
+ return this;
272
+ };
273
+
274
+ /**
275
+ * Clears all footer action buttons
276
+ * @returns {this}
277
+ */
278
+ Drawer.prototype.clearActions = function() {
279
+ this.$description.actions = [];
280
+ return this;
281
+ };
282
+
283
+ /**
284
+ * Adds an action button to the drawer footer
285
+ * @param {string} label - The button label
286
+ * @param {Function} handler - The click handler
287
+ * @param {?string} variant - The button variant style (e.g. 'primary', 'secondary')
288
+ * @returns {this}
289
+ */
290
+ Drawer.prototype.action = function(label, handler, variant = null) {
291
+ handler = handler || ((_, instance) => instance.close());
292
+ this.$description.actions.push({ label, handler, variant });
293
+ return this;
294
+ };
295
+
296
+ /**
297
+ * Opens the drawer
298
+ */
299
+ Drawer.prototype.open = function() {
300
+ if(this.$description.isOpen.val() === true) {
301
+ return;
302
+ }
303
+ this.$description.isOpen.set(true);
304
+ };
305
+
306
+ /**
307
+ * Closes the drawer
308
+ */
309
+ Drawer.prototype.close = function() {
310
+ if(this.$description.isOpen.val() === false) {
311
+ return;
312
+ }
313
+ this.$description.isOpen.set(false);
314
+ };
315
+
316
+ /**
317
+ * Alias for open()
318
+ */
319
+ Drawer.prototype.show = Drawer.prototype.open;
320
+
321
+ /**
322
+ * Alias for close()
323
+ */
324
+ Drawer.prototype.hide = Drawer.prototype.close;
325
+
326
+ /**
327
+ * Registers a handler for the open event
328
+ * @param {(instance: Drawer) => void} handler
329
+ * @returns {this}
330
+ */
331
+ Drawer.prototype.onOpen = function(handler) {
332
+ this.on('open', handler);
333
+ return this;
334
+ };
335
+
336
+ /**
337
+ * Registers a handler for the close event
338
+ * @param {(instance: Drawer) => void} handler
339
+ * @returns {this}
340
+ */
341
+ Drawer.prototype.onClose = function(handler) {
342
+ this.on('close', handler);
343
+ return this;
344
+ };
@@ -0,0 +1,7 @@
1
+ import Drawer from './Drawer';
2
+
3
+
4
+
5
+ export {
6
+ Drawer,
7
+ };
@@ -0,0 +1,61 @@
1
+ import type {Observable, ValidChild} from '../../../../types/elements';
2
+ import type { ObservableItem } from '../../../../types/observable';
3
+ import type { BaseComponent } from '../../BaseComponent';
4
+ import type { GlobalAttributes } from '../../../../types/globals';
5
+
6
+ export type DrawerDescription = {
7
+ title: ValidChild | null;
8
+ subtitle: ValidChild | null;
9
+ content: ValidChild;
10
+ position: 'left' | 'right' | 'top' | 'bottom';
11
+ size: string;
12
+ overlay: boolean;
13
+ closeOnOverlayClick: boolean;
14
+ closable: boolean;
15
+ actions: Array<{
16
+ label: ValidChild;
17
+ handler: ((event: Event, instance: DrawerInterface) => void) | null;
18
+ variant: string | null;
19
+ }>;
20
+ contentRender: ((desc: DrawerDescription, instance: DrawerInterface) => ValidChild) | null;
21
+ footerRender: ((desc: DrawerDescription, instance: DrawerInterface) => ValidChild) | null;
22
+ visible: ObservableItem<boolean> | null;
23
+ props: GlobalAttributes;
24
+ };
25
+
26
+ export interface DrawerInterface extends BaseComponent {
27
+ position(position: 'left' | 'right' | 'top' | 'bottom'): this;
28
+ atLeft(): this;
29
+ atRight(): this;
30
+ atTop(): this;
31
+ atBottom(): this;
32
+ size(size: string): this;
33
+ title(title: ValidChild): this;
34
+ subtitle(subtitle: ValidChild): this;
35
+ content(content: ValidChild): this;
36
+ renderContent(template: (description: DrawerDescription, instance: DrawerInterface) => ValidChild): this;
37
+ renderFooter(template: (description: DrawerDescription, instance: DrawerInterface) => ValidChild): this;
38
+ renderHeader(template: (description: DrawerDescription, instance: DrawerInterface) => ValidChild): this;
39
+ overlay(overlay?: boolean): this;
40
+ closeOnOverlayClick(closeOnOverlayClick?: boolean): this;
41
+ closable(closable?: boolean): this;
42
+ isOpen(closable: Observable<Boolean>): this;
43
+ clearActions(): this;
44
+ action(label: string, handler?: ((event: Event, instance: DrawerInterface) => void) | null, variant?: string | null): this;
45
+ open(): void;
46
+ close(): void;
47
+ show(): void;
48
+ hide(): void;
49
+ onOpen(handler: (instance: DrawerInterface) => void): this;
50
+ onClose(handler: (instance: DrawerInterface) => void): this;
51
+ }
52
+
53
+
54
+ export declare function Drawer(props?: Record<string, unknown>, content?: ValidChild): DrawerInterface;
55
+ export declare namespace Drawer {
56
+
57
+ function use(template: (description: DrawerDescription, instance: DrawerInterface) => ValidChild): void;
58
+ function preset(name: string, callback: (instance: DrawerInterface) => DrawerInterface): void;
59
+ function presets(presets: Record<string, (instance: DrawerInterface) => DrawerInterface>): void;
60
+
61
+ }
@@ -162,6 +162,7 @@ export type { ToastWarning, ToastWarningInterface } from './toast/types/ToastWar
162
162
 
163
163
  // --- Tooltip -----------------------------------------------------------------
164
164
  export type { Tooltip, TooltipInterface, TooltipDescription } from './tooltip/types/Tooltip';
165
+ export type { Drawer, DrawerDescription, DrawerInterface } from './drawer/types/Drawer';
165
166
 
166
167
 
167
168
  // --- Icon -----------------------------------------------------------------
@@ -0,0 +1,76 @@
1
+ import {Aside, Div, H2, Header, Paragraph, Button, Footer, ShowIf} from '../../../core/elements';
2
+ import { $ } from '../../../core/data/Observable';
3
+
4
+ import './drawer.css';
5
+
6
+ export default function DrawerRender($desc, instance) {
7
+ const $isOpen = $desc.isOpen ?? $(false);
8
+ if(!$desc.isOpen) {
9
+ instance.isOpen($isOpen);
10
+ }
11
+
12
+ const props = instance.getEditableProps();
13
+ props.class.add('drawer-container');
14
+
15
+ if($desc.backdrop) {
16
+ props.class.add('drawer-has-backdrop');
17
+ }
18
+ props.class.add('at-'+$desc.position);
19
+ props.class.add('is-open', $isOpen);
20
+
21
+ const closeFn = () => {
22
+ $isOpen.set(false);
23
+ };
24
+
25
+ return ShowIf($isOpen, () => {
26
+ return Div({ ...instance.resolveProps() }, [
27
+ $desc.backdrop ? Div({ class: 'drawer-backdrop' }) : null,
28
+
29
+ Aside({ class: 'drawer-panel' },
30
+ Div({ class: 'drawer-panel-container' }, [
31
+ Header({ class: 'drawer-header' }, buildDrawerHeader($desc, instance, closeFn)),
32
+ buildDrawerBody($desc, instance, closeFn),
33
+ buildDrawerFooter($desc, instance, closeFn),
34
+ ])
35
+ ),
36
+ ]);
37
+ });
38
+ }
39
+
40
+ const buildDrawerHeader = ($desc, instance, closeFn) => {
41
+ if($desc.renderHeader) {
42
+ return $desc.renderHeader($desc, instance, closeFn);
43
+ }
44
+
45
+ if(!($desc.title || $desc.subtitle) && !$desc.closable) {
46
+ return null;
47
+ }
48
+
49
+ return [
50
+ Div({ class: 'drawer-title-container' }, [
51
+ H2({ class: 'drawer-title' }, $desc.title),
52
+ $desc.subtitle ? Paragraph({ class: 'drawer-sub-title' }, $desc.subtitle) : null,
53
+ ]),
54
+ $desc.closable
55
+ ? Button({ type: 'button', class: 'drawer-close', 'aria-label': 'Close menu' }, 'X').onClick(closeFn)
56
+ : null,
57
+ ];
58
+ };
59
+
60
+ const buildDrawerBody = ($desc, instance, closeFn) => {
61
+ if($desc.renderContent) {
62
+ return Div({ class: 'drawer-body' }, $desc.renderContent($desc, instance, closeFn));
63
+ }
64
+
65
+ return Div({ class: 'drawer-body' }, $desc.content);
66
+ };
67
+
68
+ const buildDrawerFooter = ($desc, instance, closeFn) => {
69
+ if($desc.renderFooter) {
70
+ return Footer({ class: 'drawer-footer' }, $desc.renderFooter($desc, instance, closeFn));
71
+ }
72
+ if($desc.footerContent) {
73
+ return Footer({ class: 'drawer-footer' }, $desc.footerContent);
74
+ }
75
+ return null;
76
+ };
@@ -0,0 +1,178 @@
1
+ .drawer-container {
2
+ position: fixed;
3
+ z-index: 1000;
4
+ display: none;
5
+ inset: 0;
6
+ pointer-events: none
7
+ }
8
+
9
+ .drawer-container.is-open {
10
+ display: block;
11
+ pointer-events: auto;
12
+ }
13
+
14
+
15
+ /* Backdrop ------------------------------------------------------------------------------------------*/
16
+ .drawer-backdrop {
17
+ position: absolute;
18
+ inset: 0;
19
+ background: rgba(0, 0, 0, var(--opacity-backdrop));
20
+ transition: opacity 0.25s ease;
21
+ }
22
+
23
+ /* Panel ------------------------------------------------------------------------------------------*/
24
+ .drawer-panel {
25
+ position: absolute;
26
+ top: 0;
27
+ bottom: 0;
28
+ transition: transform 0.3s ease;
29
+ max-width: 100%;
30
+ max-height: 100%;
31
+ display: flex;
32
+ width: 450px;
33
+ }
34
+
35
+ .drawer-panel-container {
36
+ flex-direction: column;
37
+ justify-content: space-between;
38
+ flex: 1;
39
+ display: flex;
40
+ background: var(--gray-lite-4);
41
+ border-radius: var(--radius-large);
42
+ margin: var(--space-cozy);
43
+ border: 10px solid hsl(from var(--gray-lite-1) h s l / .5);
44
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
45
+ }
46
+
47
+
48
+ /* Positions ------------------------------------------------------------------------------------------*/
49
+ /* Right */
50
+ .drawer-container.at-right .drawer-panel {
51
+ top: 0;
52
+ right: 0;
53
+ bottom: 0;
54
+ transform: translateX(100%);
55
+ }
56
+ .drawer-container.at-right.is-open .drawer-panel {
57
+ transform: translateX(0);
58
+ }
59
+
60
+ /* Left */
61
+ .drawer-container.at-left .drawer-panel {
62
+ top: 0;
63
+ left: 0;
64
+ bottom: 0;
65
+ width: 360px;
66
+ transform: translateX(-100%);
67
+ }
68
+ .drawer-container.at-left.is-open .drawer-panel {
69
+ transform: translateX(0);
70
+ }
71
+
72
+ /* Top */
73
+ .drawer-container.at-top .drawer-panel {
74
+ top: 0;
75
+ left: 0;
76
+ right: 0;
77
+ bottom: unset;
78
+ /*min-height: 280px;*/
79
+ width: 100%;
80
+ transform: translateY(-100%);
81
+ }
82
+ .drawer-container.at-top.is-open .drawer-panel {
83
+ transform: translateY(0);
84
+ }
85
+
86
+ /* Bottom */
87
+ .drawer-container.at-bottom .drawer-panel {
88
+ top: unset;
89
+ left: 0;
90
+ right: 0;
91
+ bottom: 0;
92
+ width: 100%;
93
+ /*min-height: 300px;*/
94
+ transform: translateY(100%);
95
+ }
96
+ .drawer-container.at-bottom.is-open .drawer-panel {
97
+ transform: translateY(0);
98
+ }
99
+
100
+ .drawer-container.drawer-overlay .drawer-panel {
101
+ z-index: 2;
102
+ }
103
+
104
+ /* Header -----------------------------------------------------------------------------------------------*/
105
+
106
+ .drawer-header {
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: center;
110
+ gap: 12px;
111
+ padding: 16px 20px;
112
+ flex-shrink: 0;
113
+ }
114
+
115
+ .drawer-title-container {
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: 4px;
119
+ min-width: 0;
120
+ }
121
+
122
+ .drawer-title {
123
+ margin: 0;
124
+ font-size: 18px;
125
+ font-weight: 600;
126
+ color: #111;
127
+ overflow: hidden;
128
+ text-overflow: ellipsis;
129
+ white-space: nowrap;
130
+ }
131
+
132
+ .drawer-sub-title {
133
+ margin: 0;
134
+ font-size: 13px;
135
+ color: #6b7280;
136
+ }
137
+
138
+ .drawer-close {
139
+ flex-shrink: 0;
140
+ width: 32px;
141
+ height: 32px;
142
+ display: inline-flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ border: none;
146
+ background: transparent;
147
+ font-size: 16px;
148
+ line-height: 1;
149
+ cursor: pointer;
150
+ color: #6b7280;
151
+ transition: background 0.15s ease, color 0.15s ease;
152
+ border-radius: var(--radius-round);
153
+ }
154
+
155
+ .drawer-close:hover {
156
+ background: #f3f4f6;
157
+ color: #111;
158
+ }
159
+
160
+ /* Body ------------------------------------------------------------------------------------------*/
161
+ .drawer-body {
162
+ flex: 1;
163
+ overflow-y: auto;
164
+ padding: 20px;
165
+ background: var(--gray-lite-3);
166
+ margin: var(--space-cozy);
167
+ border-radius: var(--radius-large);
168
+ }
169
+
170
+ /* Footer ------------------------------------------------------------------------------------------*/
171
+ .drawer-footer {
172
+ flex-shrink: 0;
173
+ padding: 16px 20px;
174
+ display: flex;
175
+ justify-content: center;
176
+ gap: 8px;
177
+ margin: var(--space-cozy);
178
+ }
package/src/ui/index.js CHANGED
@@ -44,6 +44,7 @@ export { default as ListItemRender } from './components/list/item/ListItemRender
44
44
  export { default as ListDividerRender } from './components/list/divider/ListDividerRender';
45
45
 
46
46
  export { default as CardRender } from './components/card/CardRender';
47
+ export { default as DrawerRender } from './components/drawer/DrawerRender';
47
48
 
48
49
 
49
50
  export { default as TablerIconRender } from './components/icon/tabler/TablerIconRender';