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,214 @@
1
+ /**
2
+ * NcBottomNav Component — mobile bottom navigation bar
3
+ *
4
+ * Container for nc-nav-bottom-item children. Handles active state management.
5
+ * Designed for mobile viewports but works on all sizes.
6
+ *
7
+ * Attributes:
8
+ * value — currently active tab value
9
+ * variant — 'default'|'labeled'|'icon-only' (default: 'labeled')
10
+ * elevated — boolean — add drop shadow / elevated appearance
11
+ * bordered — boolean — top border (default: true)
12
+ *
13
+ * Slots:
14
+ * (default) — nc-bottom-nav-item elements
15
+ *
16
+ * Events:
17
+ * change — CustomEvent<{ value: string }> — tab changed
18
+ *
19
+ * Usage:
20
+ * <nc-bottom-nav value="home">
21
+ * <nc-bottom-nav-item value="home" icon="home" label="Home"></nc-bottom-nav-item>
22
+ * <nc-bottom-nav-item value="search" icon="search" label="Search"></nc-bottom-nav-item>
23
+ * <nc-bottom-nav-item value="inbox" icon="inbox" label="Inbox" badge="3"></nc-bottom-nav-item>
24
+ * <nc-bottom-nav-item value="me" icon="users" label="Me"></nc-bottom-nav-item>
25
+ * </nc-bottom-nav>
26
+ */
27
+
28
+ /**
29
+ * NcBottomNavItem Component — individual tab in a bottom nav bar
30
+ *
31
+ * Attributes:
32
+ * value — unique identifier for this tab
33
+ * label — tab label text
34
+ * icon — icon name (same set as nc-nav-item)
35
+ * badge — numeric badge count
36
+ * disabled — boolean
37
+ * active — boolean (managed by parent nc-bottom-nav)
38
+ */
39
+ import { Component, defineComponent } from '@core/component.js';
40
+
41
+ // Shared icon paths with nc-nav-item
42
+ const NAV_ICONS: Record<string, string> = {
43
+ home: `<path d="M3 9.5L12 3l9 6.5V20a1 1 0 0 1-1 1H14v-5h-4v5H4a1 1 0 0 1-1-1V9.5z"/>`,
44
+ search: `<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>`,
45
+ inbox: `<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>`,
46
+ users: `<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>`,
47
+ heart: `<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>`,
48
+ bookmark: `<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>`,
49
+ settings: `<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>`,
50
+ chart: `<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>`,
51
+ bell: `<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>`,
52
+ };
53
+ const svgWrap = (p: string) =>
54
+ `<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${p}</svg>`;
55
+
56
+ // ── NcBottomNavItem ───────────────────────────────────────────────────────────
57
+
58
+ export class NcBottomNavItem extends Component {
59
+ static useShadowDOM = true;
60
+
61
+ static get observedAttributes() { return ['active', 'badge', 'disabled']; }
62
+
63
+ template() {
64
+ const value = this.getAttribute('value') ?? '';
65
+ const label = this.getAttribute('label') ?? '';
66
+ const iconName = this.getAttribute('icon') ?? '';
67
+ const active = this.hasAttribute('active');
68
+ const disabled = this.hasAttribute('disabled');
69
+ const badge = this.getAttribute('badge') ?? '';
70
+ const iconOnly = this.closest('nc-bottom-nav')?.getAttribute('variant') === 'icon-only';
71
+
72
+ const iconHtml = NAV_ICONS[iconName] ? svgWrap(NAV_ICONS[iconName]) : '';
73
+
74
+ return `
75
+ <style>
76
+ :host { display: flex; flex: 1; }
77
+ button {
78
+ display: flex;
79
+ flex-direction: column;
80
+ align-items: center;
81
+ justify-content: center;
82
+ gap: 2px;
83
+ flex: 1;
84
+ padding: 6px 4px 8px;
85
+ background: none;
86
+ border: none;
87
+ cursor: ${disabled ? 'not-allowed' : 'pointer'};
88
+ opacity: ${disabled ? 0.4 : 1};
89
+ font-family: var(--nc-font-family);
90
+ font-size: 10px;
91
+ font-weight: var(--nc-font-weight-medium);
92
+ color: ${active ? 'var(--nc-primary)' : 'var(--nc-text-muted)'};
93
+ outline: none;
94
+ transition: color var(--nc-transition-fast);
95
+ position: relative;
96
+ min-width: 48px;
97
+ user-select: none;
98
+ }
99
+ button:focus-visible { color: var(--nc-primary); }
100
+ .icon-wrap {
101
+ position: relative;
102
+ display: flex;
103
+ }
104
+ .icon-wrap svg {
105
+ transition: transform var(--nc-transition-fast);
106
+ ${active ? 'transform: translateY(-1px) scale(1.05);' : ''}
107
+ }
108
+ .badge {
109
+ position: absolute;
110
+ top: -5px;
111
+ right: -8px;
112
+ background: var(--nc-danger);
113
+ color: #fff;
114
+ font-size: 9px;
115
+ font-weight: 700;
116
+ min-width: 16px;
117
+ height: 16px;
118
+ border-radius: 99px;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ padding: 0 4px;
123
+ line-height: 1;
124
+ border: 1.5px solid var(--nc-bg-elevated, #fff);
125
+ }
126
+ </style>
127
+ <button type="button" ${disabled ? 'disabled' : ''} aria-label="${label}" aria-current="${active ? 'page' : 'false'}" data-value="${value}">
128
+ <span class="icon-wrap">
129
+ ${iconHtml}
130
+ <slot name="icon"></slot>
131
+ ${badge ? `<span class="badge">${badge}</span>` : ''}
132
+ </span>
133
+ ${!iconOnly && label ? `<span>${label}</span>` : ''}
134
+ </button>
135
+ `;
136
+ }
137
+
138
+ onMount() {
139
+ this.shadowRoot!.addEventListener('click', () => {
140
+ if (this.hasAttribute('disabled')) return;
141
+ this.dispatchEvent(new CustomEvent('_item-click', {
142
+ detail: { value: this.getAttribute('value') },
143
+ bubbles: true, composed: true,
144
+ }));
145
+ });
146
+ }
147
+
148
+ attributeChangedCallback(n: string, o: string, v: string) {
149
+ if (o !== v && this._mounted) this.render();
150
+ }
151
+ }
152
+
153
+ defineComponent('nc-bottom-nav-item', NcBottomNavItem);
154
+
155
+ // ── NcBottomNav ───────────────────────────────────────────────────────────────
156
+
157
+ export class NcBottomNav extends Component {
158
+ static useShadowDOM = true;
159
+
160
+ static get observedAttributes() { return ['value']; }
161
+
162
+ template() {
163
+ const elevated = this.hasAttribute('elevated');
164
+ const bordered = !this.hasAttribute('no-border');
165
+
166
+ return `
167
+ <style>
168
+ :host { display: block; }
169
+ nav {
170
+ display: flex;
171
+ align-items: stretch;
172
+ background: var(--nc-bg-elevated, var(--nc-bg));
173
+ ${bordered ? 'border-top: 1px solid var(--nc-border);' : ''}
174
+ ${elevated ? 'box-shadow: 0 -2px 12px rgba(0,0,0,.08);' : ''}
175
+ safe-area-inset-bottom: env(safe-area-inset-bottom);
176
+ padding-bottom: env(safe-area-inset-bottom);
177
+ }
178
+ </style>
179
+ <nav role="navigation" aria-label="Bottom navigation">
180
+ <slot></slot>
181
+ </nav>
182
+ `;
183
+ }
184
+
185
+ onMount() {
186
+ this.addEventListener('_item-click', (e: Event) => {
187
+ const ce = e as CustomEvent<{ value: string }>;
188
+ const newValue = ce.detail.value;
189
+ this._setActive(newValue);
190
+ this.dispatchEvent(new CustomEvent('change', {
191
+ detail: { value: newValue }, bubbles: true, composed: true,
192
+ }));
193
+ });
194
+
195
+ // Set initial active state
196
+ const initial = this.getAttribute('value');
197
+ if (initial) this._setActive(initial);
198
+ }
199
+
200
+ private _setActive(value: string) {
201
+ this.setAttribute('value', value);
202
+ const items = this.querySelectorAll<HTMLElement>('nc-bottom-nav-item');
203
+ items.forEach(item => {
204
+ if (item.getAttribute('value') === value) item.setAttribute('active', '');
205
+ else item.removeAttribute('active');
206
+ });
207
+ }
208
+
209
+ attributeChangedCallback(n: string, o: string, v: string) {
210
+ if (n === 'value' && o !== v && this._mounted) this._setActive(v);
211
+ }
212
+ }
213
+
214
+ defineComponent('nc-bottom-nav', NcBottomNav);
@@ -0,0 +1,96 @@
1
+ /**
2
+ * NcBreadcrumb Component
3
+ *
4
+ * Attributes:
5
+ * - separator: string — separator character/text (default: '/')
6
+ *
7
+ * Usage — place nc-a or plain <a> children inside:
8
+ * <nc-breadcrumb>
9
+ * <nc-a href="/">Home</nc-a>
10
+ * <nc-a href="/settings">Settings</nc-a>
11
+ * <span>Profile</span>
12
+ * </nc-breadcrumb>
13
+ */
14
+
15
+ import { Component, defineComponent } from '@core/component.js';
16
+
17
+ export class NcBreadcrumb extends Component {
18
+ static useShadowDOM = true;
19
+
20
+ static get observedAttributes() { return ['separator']; }
21
+
22
+ template() {
23
+ return `
24
+ <style>
25
+ :host { display: block; font-family: var(--nc-font-family); }
26
+
27
+ nav { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }
28
+
29
+ .sep {
30
+ color: var(--nc-text-muted);
31
+ font-size: var(--nc-font-size-sm);
32
+ padding: 0 4px;
33
+ user-select: none;
34
+ }
35
+
36
+ ::slotted(*) {
37
+ font-size: var(--nc-font-size-sm);
38
+ color: var(--nc-text-muted);
39
+ text-decoration: none;
40
+ white-space: nowrap;
41
+ }
42
+
43
+ ::slotted(*:last-child) {
44
+ color: var(--nc-text);
45
+ font-weight: var(--nc-font-weight-medium);
46
+ pointer-events: none;
47
+ }
48
+
49
+ ::slotted(*:not(:last-child):hover) {
50
+ color: var(--nc-primary);
51
+ }
52
+ </style>
53
+ <nav aria-label="Breadcrumb">
54
+ <slot></slot>
55
+ </nav>
56
+ `;
57
+ }
58
+
59
+ onMount() {
60
+ this._insertSeparators();
61
+ // Re-apply when children change
62
+ const observer = new MutationObserver(() => this._insertSeparators());
63
+ observer.observe(this, { childList: true });
64
+ (this as any)._breadcrumbObserver = observer;
65
+ }
66
+
67
+ onUnmount() {
68
+ (this as any)._breadcrumbObserver?.disconnect();
69
+ }
70
+
71
+ private _insertSeparators() {
72
+ const sep = this.getAttribute('separator') || '/';
73
+ const existing = Array.from(this.querySelectorAll('.nc-breadcrumb-sep'));
74
+ existing.forEach(s => s.remove());
75
+
76
+ const children = Array.from(this.children).filter(
77
+ el => !el.classList.contains('nc-breadcrumb-sep')
78
+ );
79
+
80
+ children.slice(0, -1).forEach(child => {
81
+ const span = document.createElement('span');
82
+ span.className = 'nc-breadcrumb-sep';
83
+ span.setAttribute('aria-hidden', 'true');
84
+ span.textContent = sep;
85
+ child.after(span);
86
+ });
87
+ }
88
+
89
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
90
+ if (oldValue !== newValue && this._mounted) {
91
+ this._insertSeparators();
92
+ }
93
+ }
94
+ }
95
+
96
+ defineComponent('nc-breadcrumb', NcBreadcrumb);
@@ -0,0 +1,307 @@
1
+ /**
2
+ * NativeCore Button Component
3
+ *
4
+ * Framework core component using Shadow DOM and --nc- variables.
5
+ *
6
+ * Attributes:
7
+ * - variant: 'primary' | 'secondary' | 'tertiary' | 'success' | 'danger' | 'outline' (default: 'primary')
8
+ * - size: 'sm' | 'md' | 'lg' (default: 'md')
9
+ * - icon: URL to icon/image (optional, e.g., '/icons/delete.svg' or 'assets/logo.png')
10
+ * - icon-position: 'left' | 'right' | 'top' | 'bottom' (default: 'left')
11
+ * - alt: Alt text for icon (optional, for accessibility)
12
+ * - disabled: boolean
13
+ * - loading: boolean
14
+ * - full-width: boolean
15
+ *
16
+ * Usage:
17
+ *
18
+ *
19
+ *
20
+ */
21
+
22
+ import { Component, defineComponent } from '@core/component.js';
23
+
24
+ export class NcButton extends Component {
25
+ static useShadowDOM = true;
26
+
27
+ // Dev tools will auto-detect these dropdown options
28
+ static attributeOptions = {
29
+ variant: ['primary', 'secondary', 'tertiary', 'success', 'danger', 'outline'],
30
+ size: ['sm', 'md', 'lg'],
31
+ 'icon-position': ['left', 'right', 'top', 'bottom']
32
+ };
33
+
34
+ // Add placeholders for text inputs in dev tools
35
+ static attributePlaceholders = {
36
+ icon: '/icons/my-icon.svg or assets/image.png',
37
+ alt: 'Icon description for accessibility'
38
+ };
39
+
40
+ static get observedAttributes() {
41
+ return ['variant', 'size', 'icon', 'icon-position', 'alt', 'disabled', 'loading', 'full-width'];
42
+ }
43
+
44
+ constructor() {
45
+ super();
46
+ }
47
+
48
+ template() {
49
+ const icon = this.getAttribute('icon');
50
+ const iconPosition = this.getAttribute('icon-position') || 'left';
51
+ const alt = this.getAttribute('alt') || '';
52
+ const disabled = this.hasAttribute('disabled');
53
+ const loading = this.hasAttribute('loading');
54
+ const fullWidth = this.hasAttribute('full-width');
55
+
56
+ const iconHTML = icon ? `<img class="nc-button-icon" src="${icon}" alt="${alt}" />` : '';
57
+
58
+ return`
59
+ <style>
60
+ :host {
61
+ display: ${fullWidth ? 'block' : 'inline-flex'};
62
+ align-items: center;
63
+ justify-content: center;
64
+ gap: var(--nc-spacing-sm);
65
+ font-family: var(--nc-font-family);
66
+ font-weight: var(--nc-font-weight-semibold);
67
+ border: none;
68
+ border-radius: var(--nc-button-radius);
69
+ cursor: ${disabled || loading ? 'not-allowed' : 'pointer'};
70
+ transition: all var(--nc-transition-fast);
71
+ text-decoration: none;
72
+ outline: none;
73
+ position: relative;
74
+ overflow: ${loading ? 'visible' : 'hidden'};
75
+ user-select: none;
76
+ box-sizing: border-box !important;
77
+ margin: 0 !important;
78
+ flex-direction: ${iconPosition === 'top' ? 'column' : iconPosition === 'bottom' ? 'column-reverse' : iconPosition === 'right' ? 'row-reverse' : 'row'};
79
+
80
+ /* Default size (md) */
81
+ padding: var(--nc-spacing-sm) var(--nc-spacing-xl) !important;
82
+ font-size: var(--nc-font-size-base);
83
+ min-height: 40px;
84
+
85
+ /* Default variant (primary) */
86
+ background: var(--nc-gradient-primary);
87
+ color: var(--nc-white);
88
+ }
89
+
90
+ .nc-button-icon {
91
+ width: 1.2em;
92
+ height: 1.2em;
93
+ object-fit: contain;
94
+ flex-shrink: 0;
95
+ }
96
+
97
+ :host(:focus-visible) {
98
+ outline: 2px solid var(--nc-primary);
99
+ outline-offset: 2px;
100
+ }
101
+
102
+ /* Size Variants */
103
+ :host([size="sm"]) {
104
+ padding: var(--nc-spacing-xs) var(--nc-spacing-lg) !important;
105
+ font-size: var(--nc-font-size-sm);
106
+ min-height: 32px;
107
+ }
108
+
109
+ :host([size="md"]) {
110
+ padding: var(--nc-spacing-sm) var(--nc-spacing-xl) !important;
111
+ font-size: var(--nc-font-size-base);
112
+ min-height: 40px;
113
+ }
114
+
115
+ :host([size="lg"]) {
116
+ padding: var(--nc-spacing-md) var(--nc-spacing-2xl) !important;
117
+ font-size: var(--nc-font-size-lg);
118
+ min-height: 48px;
119
+ }
120
+
121
+ /* Variant: Primary */
122
+ :host([variant="primary"]) {
123
+ background: #10b981;
124
+ color: white;
125
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
126
+ }
127
+
128
+ :host([variant="primary"]:hover:not([disabled]):not([loading])) {
129
+ transform: translateY(-1px);
130
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
131
+ background: #059669;
132
+ }
133
+
134
+ :host([variant="primary"]:active:not([disabled]):not([loading])) {
135
+ transform: translateY(0);
136
+ }
137
+
138
+ /* Variant: Secondary */
139
+ :host([variant="secondary"]) {
140
+ background: var(--nc-gray-200);
141
+ color: var(--nc-text);
142
+ border: none;
143
+ }
144
+
145
+ :host([variant="secondary"]:hover:not([disabled]):not([loading])) {
146
+ background: var(--nc-gray-300);
147
+ transform: translateY(-1px);
148
+ }
149
+
150
+ /* Variant: Success */
151
+ :host([variant="success"]) {
152
+ background: var(--nc-gradient-success);
153
+ color: var(--nc-white);
154
+ }
155
+
156
+ :host([variant="success"]:hover:not([disabled]):not([loading])) {
157
+ transform: translateY(-2px);
158
+ box-shadow: var(--nc-shadow-success);
159
+ }
160
+
161
+ :host([variant="success"]:active:not([disabled]):not([loading])) {
162
+ transform: translateY(0);
163
+ }
164
+
165
+ /* Variant: Danger */
166
+ :host([variant="danger"]) {
167
+ background: var(--nc-gradient-danger);
168
+ color: var(--nc-white);
169
+ }
170
+
171
+ :host([variant="danger"]:hover:not([disabled]):not([loading])) {
172
+ transform: translateY(-2px);
173
+ box-shadow: var(--nc-shadow-danger);
174
+ }
175
+
176
+ :host([variant="danger"]:active:not([disabled]):not([loading])) {
177
+ transform: translateY(0);
178
+ }
179
+
180
+ /* Variant: Tertiary (dark) */
181
+ :host([variant="tertiary"]) {
182
+ background: var(--nc-gray-900);
183
+ color: var(--nc-white);
184
+ }
185
+
186
+ :host([variant="tertiary"]:hover:not([disabled]):not([loading])) {
187
+ background: var(--nc-gray-800);
188
+ transform: translateY(-2px);
189
+ }
190
+
191
+ :host([variant="tertiary"]:active:not([disabled]):not([loading])) {
192
+ transform: translateY(0);
193
+ }
194
+
195
+ /* Variant: Outline */
196
+ :host([variant="outline"]) {
197
+ background: transparent;
198
+ color: var(--nc-primary);
199
+ border: 2px solid var(--nc-primary);
200
+ }
201
+
202
+ :host([variant="outline"]:hover:not([disabled]):not([loading])) {
203
+ background: var(--nc-primary);
204
+ color: var(--nc-white);
205
+ }
206
+
207
+ /* Full Width */
208
+ :host([full-width]) {
209
+ width: 100%;
210
+ }
211
+
212
+ /* Disabled State */
213
+ :host([disabled]) {
214
+ opacity: 0.5;
215
+ pointer-events: none;
216
+ }
217
+
218
+ /* Loading State */
219
+ :host([loading]) {
220
+ pointer-events: none;
221
+ position: relative;
222
+ }
223
+
224
+ :host([loading])::after {
225
+ content: '';
226
+ position: absolute;
227
+ width: 16px;
228
+ height: 16px;
229
+ border: 2px solid currentColor;
230
+ border-top-color: transparent;
231
+ border-radius: 50%;
232
+ animation: spin 0.6s linear infinite;
233
+ margin-left: var(--nc-spacing-sm);
234
+ }
235
+
236
+ @keyframes spin {
237
+ to { transform: rotate(360deg); }
238
+ }
239
+
240
+ /* Ripple Effect */
241
+ :host::before {
242
+ content: '';
243
+ position: absolute;
244
+ top: 50%;
245
+ left: 50%;
246
+ width: 0;
247
+ height: 0;
248
+ border-radius: 50%;
249
+ background: rgba(255, 255, 255, 0.5);
250
+ transform: translate(-50%, -50%);
251
+ transition: width 0.6s, height 0.6s;
252
+ }
253
+
254
+ :host(:active:not([disabled]):not([loading]))::before {
255
+ width: 300px;
256
+ height: 300px;
257
+ opacity: 0;
258
+ }
259
+
260
+ /* Anchor tags inside button */
261
+ ::slotted(a) {
262
+ color: white;
263
+ text-decoration: none;
264
+ display: flex;
265
+ align-items: center;
266
+ justify-content: center;
267
+ }
268
+ </style>
269
+ ${iconHTML}
270
+ <slot></slot>
271
+ `;
272
+ }
273
+
274
+ onMount() {
275
+ // Set button role and tab index for accessibility
276
+ this.setAttribute('role', 'button');
277
+ if (!this.hasAttribute('tabindex')) {
278
+ this.setAttribute('tabindex', '0');
279
+ }
280
+
281
+ // Handle keyboard navigation (Enter and Space)
282
+ this.addEventListener('keydown', (e) => {
283
+ if ((e.key === 'Enter' || e.key === ' ') &&
284
+ !this.hasAttribute('disabled') &&
285
+ !this.hasAttribute('loading')) {
286
+ e.preventDefault();
287
+ this.click();
288
+ }
289
+ });
290
+
291
+ // Handle click - prevent when disabled/loading
292
+ this.addEventListener('click', (e) => {
293
+ if (this.hasAttribute('disabled') || this.hasAttribute('loading')) {
294
+ e.stopPropagation();
295
+ e.preventDefault();
296
+ }
297
+ });
298
+ }
299
+
300
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
301
+ if (oldValue !== newValue) {
302
+ this.render();
303
+ }
304
+ }
305
+ }
306
+
307
+ defineComponent('nc-button', NcButton);