create-nativecore 0.1.0 → 0.2.0

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.
Files changed (175) hide show
  1. package/README.md +10 -18
  2. package/bin/index.mjs +407 -489
  3. package/package.json +4 -3
  4. package/template/.env.example +28 -0
  5. package/template/.htmlhintrc +14 -0
  6. package/template/api/data/dashboard.json +11 -0
  7. package/template/api/data/users.json +18 -0
  8. package/template/api/mockApi.js +161 -0
  9. package/template/assets/icon.svg +13 -0
  10. package/template/assets/logo.svg +25 -0
  11. package/template/eslint.config.js +94 -0
  12. package/template/index.html +137 -0
  13. package/template/manifest.json +19 -0
  14. package/template/public/.well-known/security.txt +9 -0
  15. package/template/public/_headers +24 -0
  16. package/template/public/_redirects +14 -0
  17. package/template/public/assets/icon.svg +13 -0
  18. package/template/public/assets/logo.svg +25 -0
  19. package/template/public/manifest.json +19 -0
  20. package/template/public/robots.txt +13 -0
  21. package/template/public/sitemap.xml +27 -0
  22. package/template/scripts/build-for-bots.mjs +121 -0
  23. package/template/scripts/convert-to-ts.mjs +106 -0
  24. package/template/scripts/fix-encoding.mjs +38 -0
  25. package/template/scripts/fix-svg-paths.mjs +32 -0
  26. package/template/scripts/generate-cf-router.mjs +52 -0
  27. package/template/scripts/inject-dev-tools.mjs +41 -0
  28. package/template/scripts/inject-version.mjs +65 -0
  29. package/template/scripts/make-component.mjs +445 -0
  30. package/template/scripts/make-component.mjs.backup +432 -0
  31. package/template/scripts/make-controller.mjs +119 -0
  32. package/template/scripts/make-core-component.mjs +303 -0
  33. package/template/scripts/make-view.mjs +346 -0
  34. package/template/scripts/minify.mjs +71 -0
  35. package/template/scripts/prepare-static-assets.mjs +141 -0
  36. package/template/scripts/prompt-bot-build.mjs +223 -0
  37. package/template/scripts/remove-component.mjs +170 -0
  38. package/template/scripts/remove-core-component.mjs +156 -0
  39. package/template/scripts/remove-dev.mjs +13 -0
  40. package/template/scripts/remove-view.mjs +200 -0
  41. package/template/scripts/strip-dev-blocks.mjs +30 -0
  42. package/template/scripts/watch-compile.mjs +69 -0
  43. package/template/server.js +1066 -0
  44. package/template/src/app.ts +115 -0
  45. package/template/src/components/appRegistry.ts +8 -0
  46. package/template/src/components/core/app-footer.ts +27 -0
  47. package/template/src/components/core/app-header.ts +175 -0
  48. package/template/src/components/core/app-sidebar.ts +238 -0
  49. package/template/src/components/core/loading-spinner.ts +25 -0
  50. package/template/src/components/core/nc-a.ts +313 -0
  51. package/template/src/components/core/nc-accordion.ts +186 -0
  52. package/template/src/components/core/nc-alert.ts +153 -0
  53. package/template/src/components/core/nc-animation.ts +1150 -0
  54. package/template/src/components/core/nc-autocomplete.ts +271 -0
  55. package/template/src/components/core/nc-avatar-group.ts +113 -0
  56. package/template/src/components/core/nc-avatar.ts +148 -0
  57. package/template/src/components/core/nc-badge.ts +86 -0
  58. package/template/src/components/core/nc-bottom-nav.ts +214 -0
  59. package/template/src/components/core/nc-breadcrumb.ts +96 -0
  60. package/template/src/components/core/nc-button.ts +307 -0
  61. package/template/src/components/core/nc-card.ts +160 -0
  62. package/template/src/components/core/nc-checkbox.ts +282 -0
  63. package/template/src/components/core/nc-chip.ts +115 -0
  64. package/template/src/components/core/nc-code.ts +314 -0
  65. package/template/src/components/core/nc-collapsible.ts +154 -0
  66. package/template/src/components/core/nc-color-picker.ts +268 -0
  67. package/template/src/components/core/nc-copy-button.ts +119 -0
  68. package/template/src/components/core/nc-date-picker.ts +443 -0
  69. package/template/src/components/core/nc-div.ts +280 -0
  70. package/template/src/components/core/nc-divider.ts +81 -0
  71. package/template/src/components/core/nc-drawer.ts +230 -0
  72. package/template/src/components/core/nc-dropdown.ts +178 -0
  73. package/template/src/components/core/nc-empty-state.ts +134 -0
  74. package/template/src/components/core/nc-file-upload.ts +354 -0
  75. package/template/src/components/core/nc-form.ts +312 -0
  76. package/template/src/components/core/nc-image.ts +184 -0
  77. package/template/src/components/core/nc-input.ts +383 -0
  78. package/template/src/components/core/nc-kbd.ts +48 -0
  79. package/template/src/components/core/nc-menu-item.ts +193 -0
  80. package/template/src/components/core/nc-menu.ts +376 -0
  81. package/template/src/components/core/nc-modal.ts +238 -0
  82. package/template/src/components/core/nc-nav-item.ts +151 -0
  83. package/template/src/components/core/nc-number-input.ts +350 -0
  84. package/template/src/components/core/nc-otp-input.ts +235 -0
  85. package/template/src/components/core/nc-pagination.ts +178 -0
  86. package/template/src/components/core/nc-popover.ts +260 -0
  87. package/template/src/components/core/nc-progress-circular.ts +119 -0
  88. package/template/src/components/core/nc-progress.ts +134 -0
  89. package/template/src/components/core/nc-radio.ts +235 -0
  90. package/template/src/components/core/nc-rating.ts +266 -0
  91. package/template/src/components/core/nc-rich-text.ts +283 -0
  92. package/template/src/components/core/nc-scroll-top.ts +116 -0
  93. package/template/src/components/core/nc-select.ts +452 -0
  94. package/template/src/components/core/nc-skeleton.ts +107 -0
  95. package/template/src/components/core/nc-slider.ts +285 -0
  96. package/template/src/components/core/nc-snackbar.ts +230 -0
  97. package/template/src/components/core/nc-splash.ts +343 -0
  98. package/template/src/components/core/nc-stepper.ts +247 -0
  99. package/template/src/components/core/nc-switch.ts +281 -0
  100. package/template/src/components/core/nc-tab-item.ts +138 -0
  101. package/template/src/components/core/nc-table.ts +279 -0
  102. package/template/src/components/core/nc-tabs.ts +554 -0
  103. package/template/src/components/core/nc-tag-input.ts +279 -0
  104. package/template/src/components/core/nc-textarea.ts +216 -0
  105. package/template/src/components/core/nc-time-picker.ts +438 -0
  106. package/template/src/components/core/nc-timeline.ts +186 -0
  107. package/template/src/components/core/nc-tooltip.ts +143 -0
  108. package/template/src/components/frameworkRegistry.ts +68 -0
  109. package/template/src/components/preloadRegistry.ts +28 -0
  110. package/template/src/components/registry.ts +8 -0
  111. package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
  112. package/template/src/constants/apiEndpoints.ts +27 -0
  113. package/template/src/constants/errorMessages.ts +23 -0
  114. package/template/src/constants/index.ts +8 -0
  115. package/template/src/constants/routePaths.ts +15 -0
  116. package/template/src/constants/storageKeys.ts +18 -0
  117. package/template/src/controllers/dashboard.controller.ts +200 -0
  118. package/template/src/controllers/home.controller.ts +21 -0
  119. package/template/src/controllers/index.ts +11 -0
  120. package/template/src/controllers/login.controller.ts +131 -0
  121. package/template/src/core/component.ts +354 -0
  122. package/template/src/core/errorHandler.ts +85 -0
  123. package/template/src/core/gpu-animation.ts +604 -0
  124. package/template/src/core/http.ts +173 -0
  125. package/template/src/core/lazyComponents.ts +90 -0
  126. package/template/src/core/router.ts +642 -0
  127. package/template/src/core/signals.ts +146 -0
  128. package/template/src/core/state.ts +248 -0
  129. package/template/src/dev/component-editor.ts +1363 -0
  130. package/template/src/dev/component-overlay.ts +278 -0
  131. package/template/src/dev/context-menu.ts +223 -0
  132. package/template/src/dev/denc-tools.ts +250 -0
  133. package/template/src/dev/hmr.ts +189 -0
  134. package/template/src/dev/nfbs.code-workspace +27 -0
  135. package/template/src/dev/outline-panel.ts +1247 -0
  136. package/template/src/middleware/auth.middleware.ts +23 -0
  137. package/template/src/routes/routes.ts +38 -0
  138. package/template/src/services/api.service.ts +394 -0
  139. package/template/src/services/auth.service.ts +176 -0
  140. package/template/src/services/index.ts +8 -0
  141. package/template/src/services/logger.service.ts +74 -0
  142. package/template/src/services/storage.service.ts +88 -0
  143. package/template/src/stores/appStore.ts +57 -0
  144. package/template/src/stores/uiStore.ts +36 -0
  145. package/template/src/styles/core-variables.css +219 -0
  146. package/template/src/styles/core.css +710 -0
  147. package/template/src/styles/main.css +3164 -0
  148. package/template/src/styles/variables.css +152 -0
  149. package/template/src/types/global.d.ts +47 -0
  150. package/template/src/utils/cacheBuster.ts +20 -0
  151. package/template/src/utils/dom.ts +149 -0
  152. package/template/src/utils/events.ts +203 -0
  153. package/template/src/utils/form.ts +176 -0
  154. package/template/src/utils/formatters.ts +169 -0
  155. package/template/src/utils/helpers.ts +195 -0
  156. package/template/src/utils/markdown.ts +307 -0
  157. package/template/src/utils/sidebar.ts +96 -0
  158. package/template/src/utils/smoothScroll.ts +85 -0
  159. package/template/src/utils/templates.ts +23 -0
  160. package/template/src/utils/validation.ts +73 -0
  161. package/template/src/views/protected/dashboard.html +293 -0
  162. package/template/src/views/public/home.html +150 -0
  163. package/template/src/views/public/login.html +102 -0
  164. package/template/tests/unit/component.test.ts +87 -0
  165. package/template/tests/unit/computed.test.ts +79 -0
  166. package/template/tests/unit/form.test.ts +68 -0
  167. package/template/tests/unit/formatters.test.ts +49 -0
  168. package/template/tests/unit/lazy-components.test.ts +59 -0
  169. package/template/tests/unit/markdown.test.ts +62 -0
  170. package/template/tests/unit/router.test.ts +112 -0
  171. package/template/tests/unit/signals.test.ts +54 -0
  172. package/template/tests/unit/validation.test.ts +50 -0
  173. package/template/tsconfig.build.json +21 -0
  174. package/template/tsconfig.json +51 -0
  175. package/template/vitest.config.ts +36 -0
@@ -0,0 +1,280 @@
1
+ /**
2
+ * NcDiv Component - Responsive Container
3
+ *
4
+ * A flexible container component with built-in responsive grid/flex layouts
5
+ *
6
+ * Attributes:
7
+ * - layout: 'grid' | 'flex' | 'grid-auto' | 'block' (default: 'grid-auto')
8
+ * - cols: '1' | '2' | '3' | '4' (for grid layout)
9
+ * - direction: 'row' | 'column' (for flex layout)
10
+ * - gap: 'sm' | 'md' | 'lg' | 'xl' (default: 'md')
11
+ * - width: 'full' | 'three-quarters' | 'half' | 'quarter' (default: 'full')
12
+ * - justify: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly' (for flex/grid)
13
+ * - align: 'start' | 'center' | 'end' | 'stretch' | 'baseline' (for flex/grid)
14
+ *
15
+ * Usage:
16
+ * <nc-div layout="grid-auto">
17
+ * Card 1</nc-card>
18
+ * Card 2</nc-card>
19
+ * </nc-div>
20
+ *
21
+ * <nc-div layout="grid" cols="3">
22
+ * Card 1</nc-card>
23
+ * Card 2</nc-card>
24
+ * Card 3</nc-card>
25
+ * </nc-div>
26
+ *
27
+ * <nc-div layout="flex" direction="row" gap="lg" justify="center" align="center">
28
+ * Flexbox Item 1</nc-card>
29
+ * Flexbox Item 2</nc-card>
30
+ * </nc-div>
31
+ */
32
+
33
+ import { Component, defineComponent } from '@core/component.js';
34
+
35
+ export class NcDiv extends Component {
36
+ static useShadowDOM = true;
37
+
38
+ // ═══ Define dropdown options for dev tools ═══
39
+ static attributeOptions = {
40
+ layout: ['grid-auto', 'grid', 'flex', 'block'],
41
+ cols: ['1', '2', '3', '4'],
42
+ direction: ['row', 'column'],
43
+ gap: ['sm', 'md', 'lg', 'xl'],
44
+ width: ['full', 'three-quarters', 'half', 'quarter'],
45
+ justify: ['start', 'center', 'end', 'between', 'around', 'evenly'],
46
+ align: ['start', 'center', 'end', 'stretch', 'baseline']
47
+ };
48
+
49
+ // ═══ Conditional visibility for attributes based on layout ═══
50
+ static attributeConditions = {
51
+ cols: (element: HTMLElement) => {
52
+ const layout = element.getAttribute('layout') || 'grid-auto';
53
+ return layout === 'grid';
54
+ },
55
+ direction: (element: HTMLElement) => {
56
+ const layout = element.getAttribute('layout') || 'grid-auto';
57
+ return layout === 'flex';
58
+ }
59
+ };
60
+
61
+ // ═══ Attributes become editable in dev tools sidebar ═══
62
+ static get observedAttributes() {
63
+ return ['layout', 'cols', 'direction', 'gap', 'width', 'justify', 'align'];
64
+ }
65
+
66
+ constructor() {
67
+ super();
68
+ }
69
+
70
+ template() {
71
+ return `
72
+ <style>
73
+ :host {
74
+ display: block;
75
+ width: 100%;
76
+ box-sizing: border-box;
77
+ margin: var(--nc-spacing-lg) 0 !important;
78
+ }
79
+
80
+ /* Layout: Auto-fit Grid (Recommended) */
81
+ :host([layout="grid-auto"]) {
82
+ display: grid;
83
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
84
+ }
85
+
86
+ /* Layout: Fixed Grid */
87
+ :host([layout="grid"]) {
88
+ display: grid;
89
+ }
90
+
91
+ :host([layout="grid"][cols="1"]) {
92
+ grid-template-columns: repeat(1, minmax(0, 1fr));
93
+ }
94
+
95
+ :host([layout="grid"][cols="2"]) {
96
+ grid-template-columns: repeat(2, minmax(0, 1fr));
97
+ }
98
+
99
+ :host([layout="grid"][cols="3"]) {
100
+ grid-template-columns: repeat(3, minmax(0, 1fr));
101
+ }
102
+
103
+ :host([layout="grid"][cols="4"]) {
104
+ grid-template-columns: repeat(4, minmax(0, 1fr));
105
+ }
106
+
107
+ /* Layout: Flexbox */
108
+ :host([layout="flex"]) {
109
+ display: flex;
110
+ flex-wrap: wrap;
111
+ flex-direction: column; /* Default to column (vertical stack) */
112
+ }
113
+
114
+ /* Flex Direction */
115
+ :host([layout="flex"][direction="row"]) {
116
+ flex-direction: row;
117
+ }
118
+
119
+ :host([layout="flex"][direction="column"]) {
120
+ flex-direction: column;
121
+ }
122
+
123
+ /* Row layout: children can grow with 300px basis */
124
+ :host([layout="flex"][direction="row"]) ::slotted(*) {
125
+ flex: 1 1 300px;
126
+ min-width: 0;
127
+ }
128
+
129
+ /* Column layout (default): children size to content */
130
+ :host([layout="flex"]) ::slotted(*),
131
+ :host([layout="flex"][direction="column"]) ::slotted(*) {
132
+ flex: 0 1 auto;
133
+ min-width: 0;
134
+ }
135
+
136
+ /* Layout: Block */
137
+ :host([layout="block"]) {
138
+ display: block;
139
+ }
140
+
141
+ /* Gap Sizes */
142
+ :host([gap="sm"]) {
143
+ gap: var(--nc-spacing-sm);
144
+ }
145
+
146
+ :host([gap="md"]) {
147
+ gap: var(--nc-spacing-md);
148
+ }
149
+
150
+ :host([gap="lg"]) {
151
+ gap: var(--nc-spacing-lg);
152
+ }
153
+
154
+ :host([gap="xl"]) {
155
+ gap: var(--nc-spacing-xl);
156
+ }
157
+
158
+ /* Justify Content - Works for both Flex & Grid */
159
+ :host([justify="start"]) {
160
+ justify-content: flex-start;
161
+ justify-items: start;
162
+ }
163
+
164
+ :host([justify="center"]) {
165
+ justify-content: center;
166
+ justify-items: center;
167
+ }
168
+
169
+ :host([justify="end"]) {
170
+ justify-content: flex-end;
171
+ justify-items: end;
172
+ }
173
+
174
+ :host([justify="between"]) {
175
+ justify-content: space-between;
176
+ }
177
+
178
+ :host([justify="around"]) {
179
+ justify-content: space-around;
180
+ }
181
+
182
+ :host([justify="evenly"]) {
183
+ justify-content: space-evenly;
184
+ }
185
+
186
+ /* Align Items - Works for both Flex & Grid */
187
+ :host([align="start"]) {
188
+ align-items: flex-start;
189
+ align-content: flex-start;
190
+ }
191
+
192
+ :host([align="center"]) {
193
+ align-items: center;
194
+ align-content: center;
195
+ }
196
+
197
+ :host([align="end"]) {
198
+ align-items: flex-end;
199
+ align-content: flex-end;
200
+ }
201
+
202
+ :host([align="stretch"]) {
203
+ align-items: stretch;
204
+ align-content: stretch;
205
+ }
206
+
207
+ :host([align="baseline"]) {
208
+ align-items: baseline;
209
+ align-content: baseline;
210
+ }
211
+
212
+ /* Width Options */
213
+ :host([width="full"]) {
214
+ width: 100%;
215
+ }
216
+
217
+ :host([width="three-quarters"]) {
218
+ width: 75%;
219
+ margin-left: auto;
220
+ margin-right: auto;
221
+ }
222
+
223
+ :host([width="half"]) {
224
+ width: 50%;
225
+ margin-left: auto;
226
+ margin-right: auto;
227
+ }
228
+
229
+ :host([width="quarter"]) {
230
+ width: 25%;
231
+ margin-left: auto;
232
+ margin-right: auto;
233
+ }
234
+
235
+ /* Responsive: Mobile */
236
+ @media (max-width: 768px) {
237
+ :host([layout="grid"][cols="2"]),
238
+ :host([layout="grid"][cols="3"]),
239
+ :host([layout="grid"][cols="4"]) {
240
+ grid-template-columns: repeat(1, minmax(0, 1fr));
241
+ }
242
+
243
+ :host([layout="grid-auto"]) {
244
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
245
+ }
246
+
247
+ /* Full width on mobile for better UX */
248
+ :host([width="three-quarters"]),
249
+ :host([width="half"]),
250
+ :host([width="quarter"]) {
251
+ width: 100% !important;
252
+ }
253
+ }
254
+
255
+ /* Responsive: Tablet */
256
+ @media (min-width: 769px) and (max-width: 1024px) {
257
+ :host([layout="grid"][cols="3"]),
258
+ :host([layout="grid"][cols="4"]) {
259
+ grid-template-columns: repeat(2, minmax(0, 1fr));
260
+ }
261
+ }
262
+ </style>
263
+
264
+ <slot></slot>
265
+ `;
266
+ }
267
+
268
+ onMount() {
269
+ // Component logic here
270
+ }
271
+
272
+ // ═══ Makes changes instant in dev tools preview ═══
273
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
274
+ if (oldValue !== newValue && this._mounted) {
275
+ this.render();
276
+ }
277
+ }
278
+ }
279
+
280
+ defineComponent('nc-div', NcDiv);
@@ -0,0 +1,81 @@
1
+ /**
2
+ * NcDivider Component
3
+ *
4
+ * Attributes:
5
+ * - orientation: 'horizontal'|'vertical' (default: 'horizontal')
6
+ * - variant: 'solid'|'dashed'|'dotted' (default: 'solid')
7
+ * - label: string — optional centered label text
8
+ * - thickness: string — CSS border-width (default: '1px')
9
+ * - color: string — CSS color override
10
+ * - spacing: string — CSS margin (default: 'var(--nc-spacing-md) 0')
11
+ *
12
+ * Usage:
13
+ * <nc-divider></nc-divider>
14
+ * <nc-divider label="or"></nc-divider>
15
+ * <nc-divider orientation="vertical"></nc-divider>
16
+ * <nc-divider variant="dashed" label="Settings"></nc-divider>
17
+ */
18
+
19
+ import { Component, defineComponent } from '@core/component.js';
20
+
21
+ export class NcDivider extends Component {
22
+ static useShadowDOM = true;
23
+
24
+ static get observedAttributes() {
25
+ return ['orientation', 'variant', 'label', 'thickness', 'color', 'spacing'];
26
+ }
27
+
28
+ template() {
29
+ const orientation = this.getAttribute('orientation') || 'horizontal';
30
+ const variant = this.getAttribute('variant') || 'solid';
31
+ const label = this.getAttribute('label') || '';
32
+ const thickness = this.getAttribute('thickness') || '1px';
33
+ const color = this.getAttribute('color') || 'var(--nc-border)';
34
+ const spacing = this.getAttribute('spacing') || (orientation === 'horizontal' ? 'var(--nc-spacing-md) 0' : '0 var(--nc-spacing-md)');
35
+
36
+ const isHorizontal = orientation === 'horizontal';
37
+
38
+ return `
39
+ <style>
40
+ :host {
41
+ display: ${isHorizontal ? 'block' : 'inline-flex'};
42
+ align-self: ${isHorizontal ? 'auto' : 'stretch'};
43
+ }
44
+
45
+ .divider {
46
+ display: flex;
47
+ align-items: center;
48
+ margin: ${spacing};
49
+ ${isHorizontal ? 'width: 100%;' : 'flex-direction: column; height: 100%;'}
50
+ font-family: var(--nc-font-family);
51
+ }
52
+
53
+ .line {
54
+ ${isHorizontal ? 'flex: 1; height: 0;' : 'flex: 1; width: 0;'}
55
+ border: none;
56
+ border-${isHorizontal ? 'top' : 'left'}: ${thickness} ${variant} ${color};
57
+ }
58
+
59
+ .label {
60
+ padding: ${isHorizontal ? '0 var(--nc-spacing-sm)' : 'var(--nc-spacing-sm) 0'};
61
+ font-size: var(--nc-font-size-xs);
62
+ color: var(--nc-text-muted);
63
+ white-space: nowrap;
64
+ font-weight: var(--nc-font-weight-medium);
65
+ text-transform: uppercase;
66
+ letter-spacing: 0.05em;
67
+ }
68
+ </style>
69
+ <div class="divider" role="separator" aria-orientation="${orientation}">
70
+ <span class="line"></span>
71
+ ${label ? `<span class="label">${label}</span><span class="line"></span>` : ''}
72
+ </div>
73
+ `;
74
+ }
75
+
76
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
77
+ if (oldValue !== newValue && this._mounted) this.render();
78
+ }
79
+ }
80
+
81
+ defineComponent('nc-divider', NcDivider);
@@ -0,0 +1,230 @@
1
+ /**
2
+ * NcDrawer Component
3
+ *
4
+ * Attributes:
5
+ * - open: boolean — visible state
6
+ * - placement: 'left'|'right'|'top'|'bottom' (default: 'right')
7
+ * - size: string — CSS width/height of the panel (default: '320px')
8
+ * - overlay: boolean — show backdrop overlay (default: true)
9
+ * - close-on-overlay: boolean — click overlay to close (default: true)
10
+ * - no-close-btn: boolean — hide the built-in close button
11
+ *
12
+ * Slots:
13
+ * - header — drawer header area
14
+ * - (default) — drawer body content
15
+ * - footer — drawer footer area
16
+ *
17
+ * Events:
18
+ * - open: CustomEvent — after drawer opens
19
+ * - close: CustomEvent — after drawer closes
20
+ *
21
+ * Usage:
22
+ * <nc-drawer id="nav-drawer" placement="left">
23
+ * <span slot="header">Navigation</span>
24
+ * <p>Links go here</p>
25
+ * </nc-drawer>
26
+ *
27
+ * document.getElementById('nav-drawer').setAttribute('open', '');
28
+ */
29
+
30
+ import { Component, defineComponent } from '@core/component.js';
31
+
32
+ export class NcDrawer extends Component {
33
+ static useShadowDOM = true;
34
+
35
+ static get observedAttributes() {
36
+ return ['open', 'placement', 'size', 'overlay', 'close-on-overlay', 'no-close-btn'];
37
+ }
38
+
39
+ template() {
40
+ const open = this.hasAttribute('open');
41
+ const placement = this.getAttribute('placement') || 'right';
42
+ const size = this.getAttribute('size') || '320px';
43
+ const showOverlay = !this.hasAttribute('overlay') || this.getAttribute('overlay') !== 'false';
44
+ const noCloseBtn = this.hasAttribute('no-close-btn');
45
+
46
+ const isHorizontal = placement === 'left' || placement === 'right';
47
+ const panelSize = isHorizontal
48
+ ? `width: ${size}; height: 100%;`
49
+ : `height: ${size}; width: 100%;`;
50
+
51
+ const translateClosed = {
52
+ left: 'translateX(-100%)',
53
+ right: 'translateX(100%)',
54
+ top: 'translateY(-100%)',
55
+ bottom: 'translateY(100%)',
56
+ }[placement];
57
+
58
+ return `
59
+ <style>
60
+ :host { display: contents; }
61
+
62
+ .overlay {
63
+ position: fixed;
64
+ inset: 0;
65
+ background: rgba(0,0,0,.45);
66
+ z-index: 900;
67
+ opacity: ${open ? '1' : '0'};
68
+ pointer-events: ${open && showOverlay ? 'auto' : 'none'};
69
+ transition: opacity var(--nc-transition-base);
70
+ display: ${showOverlay ? 'block' : 'none'};
71
+ }
72
+
73
+ .panel {
74
+ position: fixed;
75
+ ${placement}: 0;
76
+ ${placement === 'left' || placement === 'right' ? 'top: 0;' : 'left: 0;'}
77
+ ${panelSize}
78
+ background: var(--nc-bg);
79
+ box-shadow: var(--nc-shadow-xl, 0 20px 60px rgba(0,0,0,.3));
80
+ z-index: 901;
81
+ display: flex;
82
+ flex-direction: column;
83
+ transform: ${open ? 'none' : translateClosed};
84
+ transition: transform var(--nc-transition-base);
85
+ overflow: hidden;
86
+ }
87
+
88
+ .panel__header {
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: space-between;
92
+ padding: var(--nc-spacing-md) var(--nc-spacing-lg);
93
+ border-bottom: 1px solid var(--nc-border);
94
+ font-family: var(--nc-font-family);
95
+ font-weight: var(--nc-font-weight-semibold);
96
+ font-size: var(--nc-font-size-lg);
97
+ color: var(--nc-text);
98
+ flex-shrink: 0;
99
+ }
100
+
101
+ .panel__body {
102
+ flex: 1;
103
+ overflow-y: auto;
104
+ padding: var(--nc-spacing-lg);
105
+ }
106
+
107
+ .panel__footer {
108
+ padding: var(--nc-spacing-md) var(--nc-spacing-lg);
109
+ border-top: 1px solid var(--nc-border);
110
+ flex-shrink: 0;
111
+ }
112
+
113
+ .panel__footer:empty { display: none; }
114
+
115
+ .close-btn {
116
+ background: none;
117
+ border: none;
118
+ cursor: pointer;
119
+ padding: 4px;
120
+ color: var(--nc-text-muted);
121
+ border-radius: var(--nc-radius-sm, 4px);
122
+ display: flex;
123
+ transition: color var(--nc-transition-fast), background var(--nc-transition-fast);
124
+ }
125
+ .close-btn:hover { color: var(--nc-text); background: var(--nc-bg-secondary); }
126
+ </style>
127
+
128
+ ${showOverlay ? `<div class="overlay" aria-hidden="true"></div>` : ''}
129
+
130
+ <div
131
+ class="panel"
132
+ role="dialog"
133
+ aria-modal="true"
134
+ aria-hidden="${!open}"
135
+ tabindex="-1"
136
+ >
137
+ <div class="panel__header">
138
+ <slot name="header"></slot>
139
+ ${!noCloseBtn ? `
140
+ <button class="close-btn" type="button" aria-label="Close drawer">
141
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" width="18" height="18">
142
+ <path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
143
+ </svg>
144
+ </button>` : ''}
145
+ </div>
146
+ <div class="panel__body">
147
+ <slot></slot>
148
+ </div>
149
+ <div class="panel__footer">
150
+ <slot name="footer"></slot>
151
+ </div>
152
+ </div>
153
+ `;
154
+ }
155
+
156
+ onMount() {
157
+ this._bindEvents();
158
+ }
159
+
160
+ private _bindEvents() {
161
+ const closeBtn = this.$<HTMLButtonElement>('.close-btn');
162
+ if (closeBtn) {
163
+ closeBtn.addEventListener('click', () => this._close());
164
+ }
165
+
166
+ const overlay = this.$<HTMLElement>('.overlay');
167
+ if (overlay) {
168
+ overlay.addEventListener('click', () => {
169
+ if (!this.hasAttribute('close-on-overlay') || this.getAttribute('close-on-overlay') !== 'false') {
170
+ this._close();
171
+ }
172
+ });
173
+ }
174
+
175
+ // Close on Escape
176
+ this._onKeydown = (e: KeyboardEvent) => {
177
+ if (e.key === 'Escape' && this.hasAttribute('open')) this._close();
178
+ };
179
+ document.addEventListener('keydown', this._onKeydown);
180
+ }
181
+
182
+ private _onKeydown: ((e: KeyboardEvent) => void) | null = null;
183
+
184
+ private _close() {
185
+ this.removeAttribute('open');
186
+ this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
187
+ }
188
+
189
+ onUnmount() {
190
+ if (this._onKeydown) document.removeEventListener('keydown', this._onKeydown);
191
+ }
192
+
193
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
194
+ if (oldValue === newValue) return;
195
+ if (name === 'open' && this._mounted) {
196
+ const open = this.hasAttribute('open');
197
+ const panel = this.$<HTMLElement>('.panel');
198
+ const overlay = this.$<HTMLElement>('.overlay');
199
+ const placement = this.getAttribute('placement') || 'right';
200
+
201
+ const translateClosed = {
202
+ left: 'translateX(-100%)',
203
+ right: 'translateX(100%)',
204
+ top: 'translateY(-100%)',
205
+ bottom: 'translateY(100%)',
206
+ }[placement as 'left' | 'right' | 'top' | 'bottom'];
207
+
208
+ if (panel) {
209
+ panel.style.transform = open ? 'none' : (translateClosed ?? 'translateX(100%)');
210
+ panel.setAttribute('aria-hidden', String(!open));
211
+ if (open) panel.focus();
212
+ }
213
+ if (overlay) {
214
+ overlay.style.opacity = open ? '1' : '0';
215
+ overlay.style.pointerEvents = open ? 'auto' : 'none';
216
+ }
217
+
218
+ // Lock body scroll while open
219
+ document.body.style.overflow = open ? 'hidden' : '';
220
+
221
+ this.dispatchEvent(new CustomEvent(open ? 'open' : 'close', {
222
+ bubbles: true, composed: true
223
+ }));
224
+ return;
225
+ }
226
+ if (this._mounted) { this.render(); this._bindEvents(); }
227
+ }
228
+ }
229
+
230
+ defineComponent('nc-drawer', NcDrawer);