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,143 @@
1
+ /**
2
+ * NcTooltip Component
3
+ *
4
+ * Wraps any element and shows a tooltip on hover/focus.
5
+ *
6
+ * Attributes:
7
+ * - tip: string — tooltip text (required)
8
+ * - placement: 'top'|'bottom'|'left'|'right' (default: 'top')
9
+ * - delay: number — ms before showing (default: 200)
10
+ * - variant: 'default'|'light' (default: 'default')
11
+ *
12
+ * Usage:
13
+ * <nc-tooltip tip="Save your changes">
14
+ * <nc-button>Save</nc-button>
15
+ * </nc-tooltip>
16
+ */
17
+
18
+ import { Component, defineComponent } from '@core/component.js';
19
+
20
+ export class NcTooltip extends Component {
21
+ static useShadowDOM = true;
22
+
23
+ static get observedAttributes() {
24
+ return ['tip', 'placement', 'delay', 'variant'];
25
+ }
26
+
27
+ private _showTimer: ReturnType<typeof setTimeout> | null = null;
28
+ private _visible = false;
29
+
30
+ template() {
31
+ const placement = this.getAttribute('placement') || 'top';
32
+ const variant = this.getAttribute('variant') || 'default';
33
+ const tip = this.getAttribute('tip') || '';
34
+
35
+ return `
36
+ <style>
37
+ :host { display: inline-flex; position: relative; }
38
+
39
+ .tip {
40
+ position: absolute;
41
+ z-index: 9999;
42
+ padding: 5px 10px;
43
+ border-radius: var(--nc-radius-sm, 4px);
44
+ font-family: var(--nc-font-family);
45
+ font-size: var(--nc-font-size-xs);
46
+ line-height: 1.4;
47
+ white-space: nowrap;
48
+ pointer-events: none;
49
+ opacity: 0;
50
+ transform: ${placement === 'top' ? 'translateY(4px)' : placement === 'bottom' ? 'translateY(-4px)' : placement === 'left' ? 'translateX(4px)' : 'translateX(-4px)'};
51
+ transition: opacity var(--nc-transition-fast), transform var(--nc-transition-fast);
52
+ max-width: 220px;
53
+ overflow-wrap: break-word;
54
+ white-space: normal;
55
+ text-align: center;
56
+ }
57
+
58
+ .tip--default { background: var(--nc-gray-900, #111); color: #fff; }
59
+ .tip--light { background: var(--nc-bg); color: var(--nc-text); border: 1px solid var(--nc-border); box-shadow: var(--nc-shadow-md); }
60
+
61
+ /* Placement */
62
+ .tip[data-placement="top"] { bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%) translateY(4px); }
63
+ .tip[data-placement="bottom"] { top: calc(100% + 8px); left: 50%; transform: translateX(-50%) translateY(-4px); }
64
+ .tip[data-placement="left"] { right: calc(100% + 8px); top: 50%; transform: translateY(-50%) translateX(4px); }
65
+ .tip[data-placement="right"] { left: calc(100% + 8px); top: 50%; transform: translateY(-50%) translateX(-4px); }
66
+
67
+ .tip.visible {
68
+ opacity: 1;
69
+ }
70
+ .tip[data-placement="top"].visible { transform: translateX(-50%) translateY(0); }
71
+ .tip[data-placement="bottom"].visible { transform: translateX(-50%) translateY(0); }
72
+ .tip[data-placement="left"].visible { transform: translateY(-50%) translateX(0); }
73
+ .tip[data-placement="right"].visible { transform: translateY(-50%) translateX(0); }
74
+
75
+ /* Arrow */
76
+ .tip::after {
77
+ content: '';
78
+ position: absolute;
79
+ border: 5px solid transparent;
80
+ }
81
+ .tip--default[data-placement="top"]::after { top: 100%; left: 50%; transform: translateX(-50%); border-top-color: var(--nc-gray-900, #111); }
82
+ .tip--default[data-placement="bottom"]::after { bottom: 100%; left: 50%; transform: translateX(-50%); border-bottom-color: var(--nc-gray-900, #111); }
83
+ .tip--default[data-placement="left"]::after { left: 100%; top: 50%; transform: translateY(-50%); border-left-color: var(--nc-gray-900, #111); }
84
+ .tip--default[data-placement="right"]::after { right: 100%; top: 50%; transform: translateY(-50%); border-right-color: var(--nc-gray-900, #111); }
85
+
86
+ .tip--light[data-placement="top"]::after { display: none; }
87
+
88
+ ::slotted(*) { display: inline-flex; }
89
+ </style>
90
+ <slot></slot>
91
+ <div
92
+ class="tip tip--${variant}"
93
+ data-placement="${placement}"
94
+ role="tooltip"
95
+ aria-hidden="true"
96
+ >${tip}</div>
97
+ `;
98
+ }
99
+
100
+ onMount() {
101
+ const tip = this.$<HTMLElement>('.tip')!;
102
+ const delay = Number(this.getAttribute('delay') ?? 200);
103
+
104
+ const show = () => {
105
+ if (this._showTimer) clearTimeout(this._showTimer);
106
+ this._showTimer = setTimeout(() => {
107
+ tip.classList.add('visible');
108
+ tip.setAttribute('aria-hidden', 'false');
109
+ this._visible = true;
110
+ }, delay);
111
+ };
112
+
113
+ const hide = () => {
114
+ if (this._showTimer) { clearTimeout(this._showTimer); this._showTimer = null; }
115
+ tip.classList.remove('visible');
116
+ tip.setAttribute('aria-hidden', 'true');
117
+ this._visible = false;
118
+ };
119
+
120
+ this.addEventListener('mouseenter', show);
121
+ this.addEventListener('mouseleave', hide);
122
+ this.addEventListener('focusin', show);
123
+ this.addEventListener('focusout', hide);
124
+
125
+ // Hide on Escape
126
+ this.addEventListener('keydown', (e: KeyboardEvent) => {
127
+ if (e.key === 'Escape' && this._visible) hide();
128
+ });
129
+ }
130
+
131
+ onUnmount() {
132
+ if (this._showTimer) clearTimeout(this._showTimer);
133
+ }
134
+
135
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
136
+ if (oldValue !== newValue && this._mounted) {
137
+ this.render();
138
+ this.onMount();
139
+ }
140
+ }
141
+ }
142
+
143
+ defineComponent('nc-tooltip', NcTooltip);
@@ -0,0 +1,68 @@
1
+ import { componentRegistry } from '@core/lazyComponents.js';
2
+
3
+ export function registerFrameworkComponents(): void {
4
+ componentRegistry.register('loading-spinner', './core/loading-spinner.js');
5
+ componentRegistry.register('nc-scroll-top', './core/nc-scroll-top.js');
6
+ componentRegistry.register('nc-snackbar', './core/nc-snackbar.js');
7
+ componentRegistry.register('nc-a', './core/nc-a.js');
8
+ componentRegistry.register('nc-accordion-item', './core/nc-accordion.js');
9
+ componentRegistry.register('nc-accordion', './core/nc-accordion.js');
10
+ componentRegistry.register('nc-alert', './core/nc-alert.js');
11
+ componentRegistry.register('nc-animation', './core/nc-animation.js');
12
+ componentRegistry.register('nc-autocomplete', './core/nc-autocomplete.js');
13
+ componentRegistry.register('nc-avatar-group', './core/nc-avatar-group.js');
14
+ componentRegistry.register('nc-avatar', './core/nc-avatar.js');
15
+ componentRegistry.register('nc-badge', './core/nc-badge.js');
16
+ componentRegistry.register('nc-bottom-nav-item', './core/nc-bottom-nav.js');
17
+ componentRegistry.register('nc-bottom-nav', './core/nc-bottom-nav.js');
18
+ componentRegistry.register('nc-breadcrumb', './core/nc-breadcrumb.js');
19
+ componentRegistry.register('nc-button', './core/nc-button.js');
20
+ componentRegistry.register('nc-card', './core/nc-card.js');
21
+ componentRegistry.register('nc-checkbox', './core/nc-checkbox.js');
22
+ componentRegistry.register('nc-chip', './core/nc-chip.js');
23
+ componentRegistry.register('nc-code', './core/nc-code.js');
24
+ componentRegistry.register('nc-collapsible', './core/nc-collapsible.js');
25
+ componentRegistry.register('nc-color-picker', './core/nc-color-picker.js');
26
+ componentRegistry.register('nc-copy-button', './core/nc-copy-button.js');
27
+ componentRegistry.register('nc-date-picker', './core/nc-date-picker.js');
28
+ componentRegistry.register('nc-div', './core/nc-div.js');
29
+ componentRegistry.register('nc-divider', './core/nc-divider.js');
30
+ componentRegistry.register('nc-drawer', './core/nc-drawer.js');
31
+ componentRegistry.register('nc-dropdown', './core/nc-dropdown.js');
32
+ componentRegistry.register('nc-empty-state', './core/nc-empty-state.js');
33
+ componentRegistry.register('nc-file-upload', './core/nc-file-upload.js');
34
+ componentRegistry.register('nc-field', './core/nc-form.js');
35
+ componentRegistry.register('nc-form', './core/nc-form.js');
36
+ componentRegistry.register('nc-image', './core/nc-image.js');
37
+ componentRegistry.register('nc-input', './core/nc-input.js');
38
+ componentRegistry.register('nc-kbd', './core/nc-kbd.js');
39
+ componentRegistry.register('nc-menu-item', './core/nc-menu-item.js');
40
+ componentRegistry.register('nc-menu', './core/nc-menu.js');
41
+ componentRegistry.register('nc-modal', './core/nc-modal.js');
42
+ componentRegistry.register('nc-nav-item', './core/nc-nav-item.js');
43
+ componentRegistry.register('nc-number-input', './core/nc-number-input.js');
44
+ componentRegistry.register('nc-otp-input', './core/nc-otp-input.js');
45
+ componentRegistry.register('nc-pagination', './core/nc-pagination.js');
46
+ componentRegistry.register('nc-popover', './core/nc-popover.js');
47
+ componentRegistry.register('nc-progress-circular', './core/nc-progress-circular.js');
48
+ componentRegistry.register('nc-progress', './core/nc-progress.js');
49
+ componentRegistry.register('nc-radio', './core/nc-radio.js');
50
+ componentRegistry.register('nc-rating', './core/nc-rating.js');
51
+ componentRegistry.register('nc-rich-text', './core/nc-rich-text.js');
52
+ componentRegistry.register('nc-select', './core/nc-select.js');
53
+ componentRegistry.register('nc-skeleton', './core/nc-skeleton.js');
54
+ componentRegistry.register('nc-slider', './core/nc-slider.js');
55
+ componentRegistry.register('nc-splash', './core/nc-splash.js');
56
+ componentRegistry.register('nc-step', './core/nc-stepper.js');
57
+ componentRegistry.register('nc-stepper', './core/nc-stepper.js');
58
+ componentRegistry.register('nc-switch', './core/nc-switch.js');
59
+ componentRegistry.register('nc-tab-item', './core/nc-tab-item.js');
60
+ componentRegistry.register('nc-table', './core/nc-table.js');
61
+ componentRegistry.register('nc-tabs', './core/nc-tabs.js');
62
+ componentRegistry.register('nc-tag-input', './core/nc-tag-input.js');
63
+ componentRegistry.register('nc-textarea', './core/nc-textarea.js');
64
+ componentRegistry.register('nc-time-picker', './core/nc-time-picker.js');
65
+ componentRegistry.register('nc-timeline-item', './core/nc-timeline.js');
66
+ componentRegistry.register('nc-timeline', './core/nc-timeline.js');
67
+ componentRegistry.register('nc-tooltip', './core/nc-tooltip.js');
68
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Preload Registry
3
+ *
4
+ * Components imported here are loaded immediately when the app starts,
5
+ * rather than lazy-loading on first use.
6
+ *
7
+ * Only import critical components that are needed on initial page load:
8
+ * - Layout components (header, footer, sidebar)
9
+ * - Components used on the home/landing page
10
+ * - Core UI components used everywhere
11
+ *
12
+ * Generated components with preload=Y will be added here automatically.
13
+ */
14
+
15
+ // Core layout components used directly in the HTML shells.
16
+ import './core/app-header.js';
17
+ import './core/app-sidebar.js';
18
+ import './core/app-footer.js';
19
+
20
+ // Initial route components used on first paint.
21
+ import './core/loading-spinner.js';
22
+ import './core/nc-a.js';
23
+ import './core/nc-splash.js';
24
+ import './core/nc-scroll-top.js';
25
+ import './core/nc-snackbar.js';
26
+
27
+ // Other critical components
28
+ // Add your preloaded components here
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Component Registry
3
+ */
4
+ import { registerAppComponents } from './appRegistry.js';
5
+ import { registerFrameworkComponents } from './frameworkRegistry.js';
6
+
7
+ registerFrameworkComponents();
8
+ registerAppComponents();
@@ -0,0 +1,284 @@
1
+ import { Component, defineComponent } from '@core/component.js';
2
+ import { useState, computed } from '@core/state.js';
3
+ import type { State, ComputedState } from '@core/state.js';
4
+ import { html } from '@utils/templates.js';
5
+
6
+ export class DashboardSignalLab extends Component {
7
+ static useShadowDOM = true;
8
+ completedItems: State<number>;
9
+ totalItems: State<number>;
10
+ completionPercent: ComputedState<number>;
11
+ remainingItems: ComputedState<number>;
12
+ statusLabel: ComputedState<string>;
13
+
14
+ private _unwatchCompleted?: () => void;
15
+ private _unwatchTotal?: () => void;
16
+ private _unwatchCompletionPercent?: () => void;
17
+ private _unwatchRemainingItems?: () => void;
18
+ private _unwatchStatusLabel?: () => void;
19
+ private readonly _handleClick = (event: Event) => {
20
+ const target = event.target as HTMLElement;
21
+
22
+ if (target.closest('[data-action="add-item"]')) {
23
+ this.totalItems.value += 1;
24
+ }
25
+
26
+ if (target.closest('[data-action="complete-item"]') && this.completedItems.value < this.totalItems.value) {
27
+ this.completedItems.value += 1;
28
+ }
29
+
30
+ if (target.closest('[data-action="reset-items"]')) {
31
+ this.totalItems.value = 18;
32
+ this.completedItems.value = 14;
33
+ }
34
+ };
35
+
36
+ constructor() {
37
+ super();
38
+
39
+ this.completedItems = useState(14);
40
+ this.totalItems = useState(18);
41
+ this.completionPercent = computed(() => Math.round((this.completedItems.value / this.totalItems.value) * 100));
42
+ this.remainingItems = computed(() => Math.max(this.totalItems.value - this.completedItems.value, 0));
43
+ this.statusLabel = computed(() => {
44
+ if (this.completionPercent.value >= 90) return 'Release ready';
45
+ if (this.completionPercent.value >= 70) return 'On track';
46
+ return 'Needs focus';
47
+ });
48
+ }
49
+
50
+ template() {
51
+ return html`
52
+ <style>
53
+ :host {
54
+ display: block;
55
+ }
56
+
57
+ .signal-lab {
58
+ padding: 1.4rem;
59
+ border-radius: 24px;
60
+ background: linear-gradient(180deg, #0f172a, #111827);
61
+ color: #f8fafc;
62
+ border: 1px solid rgba(148, 163, 184, 0.22);
63
+ box-shadow: 0 18px 40px rgba(15, 23, 42, 0.22);
64
+ }
65
+
66
+ .signal-lab__eyebrow {
67
+ display: inline-flex;
68
+ align-items: center;
69
+ padding: 0.35rem 0.7rem;
70
+ border-radius: 999px;
71
+ background: rgba(16, 185, 129, 0.18);
72
+ color: #86efac;
73
+ font-size: 0.72rem;
74
+ font-weight: 700;
75
+ letter-spacing: 0.08em;
76
+ text-transform: uppercase;
77
+ }
78
+
79
+ .signal-lab__title {
80
+ margin: 1rem 0 0.4rem;
81
+ font-size: 1.35rem;
82
+ line-height: 1.15;
83
+ }
84
+
85
+ .signal-lab__copy {
86
+ margin: 0;
87
+ color: rgba(226, 232, 240, 0.76);
88
+ line-height: 1.65;
89
+ }
90
+
91
+ .signal-lab__stats {
92
+ display: grid;
93
+ grid-template-columns: repeat(3, minmax(0, 1fr));
94
+ gap: 0.85rem;
95
+ margin: 1.25rem 0;
96
+ }
97
+
98
+ .signal-lab__stat {
99
+ padding: 0.9rem;
100
+ border-radius: 18px;
101
+ background: rgba(15, 23, 42, 0.6);
102
+ border: 1px solid rgba(148, 163, 184, 0.16);
103
+ }
104
+
105
+ .signal-lab__stat-label {
106
+ display: block;
107
+ color: rgba(148, 163, 184, 0.9);
108
+ font-size: 0.74rem;
109
+ font-weight: 700;
110
+ text-transform: uppercase;
111
+ letter-spacing: 0.08em;
112
+ margin-bottom: 0.45rem;
113
+ }
114
+
115
+ .signal-lab__stat-value {
116
+ display: block;
117
+ font-size: 1.7rem;
118
+ font-weight: 700;
119
+ color: #ffffff;
120
+ }
121
+
122
+ .signal-lab__bar {
123
+ position: relative;
124
+ height: 10px;
125
+ border-radius: 999px;
126
+ background: rgba(148, 163, 184, 0.18);
127
+ overflow: hidden;
128
+ }
129
+
130
+ .signal-lab__bar-fill {
131
+ position: absolute;
132
+ inset: 0 auto 0 0;
133
+ width: ${this.completionPercent.value}%;
134
+ background: linear-gradient(90deg, #10b981, #34d399);
135
+ border-radius: inherit;
136
+ transition: width 180ms ease;
137
+ }
138
+
139
+ .signal-lab__status {
140
+ margin: 0.85rem 0 0;
141
+ color: #e2e8f0;
142
+ font-size: 0.94rem;
143
+ }
144
+
145
+ .signal-lab__actions {
146
+ display: flex;
147
+ flex-wrap: wrap;
148
+ gap: 0.75rem;
149
+ margin-top: 1rem;
150
+ }
151
+
152
+ .signal-lab__button {
153
+ border: none;
154
+ border-radius: 12px;
155
+ padding: 0.75rem 1rem;
156
+ font: inherit;
157
+ font-weight: 600;
158
+ cursor: pointer;
159
+ transition: transform 0.15s ease, opacity 0.15s ease, background 0.15s ease;
160
+ }
161
+
162
+ .signal-lab__button:hover {
163
+ transform: translateY(-1px);
164
+ }
165
+
166
+ .signal-lab__button--primary {
167
+ background: #10b981;
168
+ color: #052e16;
169
+ }
170
+
171
+ .signal-lab__button--secondary {
172
+ background: rgba(255, 255, 255, 0.08);
173
+ color: #f8fafc;
174
+ border: 1px solid rgba(148, 163, 184, 0.24);
175
+ }
176
+
177
+ .signal-lab__code {
178
+ margin-top: 1.25rem;
179
+ padding: 0.95rem 1rem;
180
+ border-radius: 18px;
181
+ background: rgba(2, 6, 23, 0.88);
182
+ border: 1px solid rgba(148, 163, 184, 0.14);
183
+ overflow-x: auto;
184
+ color: #cbd5e1;
185
+ font-size: 0.82rem;
186
+ line-height: 1.7;
187
+ white-space: pre;
188
+ }
189
+
190
+ @media (max-width: 640px) {
191
+ .signal-lab__stats {
192
+ grid-template-columns: 1fr;
193
+ }
194
+
195
+ .signal-lab__code {
196
+ white-space: pre-wrap;
197
+ }
198
+ }
199
+ </style>
200
+
201
+ <div class="signal-lab">
202
+ <span class="signal-lab__eyebrow">Live Signals</span>
203
+ <h3 class="signal-lab__title">useState + computed without a view-layer rerender loop</h3>
204
+ <p class="signal-lab__copy">Update scoped values and derived metrics in place to show how NativeCore keeps interaction logic simple.</p>
205
+
206
+ <div class="signal-lab__stats">
207
+ <div class="signal-lab__stat">
208
+ <span class="signal-lab__stat-label">Completed</span>
209
+ <span class="signal-lab__stat-value" id="completed-count">${this.completedItems.value}</span>
210
+ </div>
211
+ <div class="signal-lab__stat">
212
+ <span class="signal-lab__stat-label">Remaining</span>
213
+ <span class="signal-lab__stat-value" id="remaining-count">${this.remainingItems.value}</span>
214
+ </div>
215
+ <div class="signal-lab__stat">
216
+ <span class="signal-lab__stat-label">Completion</span>
217
+ <span class="signal-lab__stat-value" id="completion-percent">${this.completionPercent.value}%</span>
218
+ </div>
219
+ </div>
220
+
221
+ <div class="signal-lab__bar" aria-hidden="true">
222
+ <div class="signal-lab__bar-fill" id="completion-bar"></div>
223
+ </div>
224
+
225
+ <p class="signal-lab__status">Current status: <strong id="status-label">${this.statusLabel.value}</strong></p>
226
+
227
+ <div class="signal-lab__actions">
228
+ <button class="signal-lab__button signal-lab__button--primary" type="button" data-action="complete-item">Complete item</button>
229
+ <button class="signal-lab__button signal-lab__button--secondary" type="button" data-action="add-item">Add scope</button>
230
+ <button class="signal-lab__button signal-lab__button--secondary" type="button" data-action="reset-items">Reset demo</button>
231
+ </div>
232
+
233
+ <div class="signal-lab__code">const completed = useState(14);
234
+ const total = useState(18);
235
+ const completion = computed(() =&gt; Math.round((completed.value / total.value) * 100));</div>
236
+ </div>
237
+ `;
238
+ }
239
+
240
+ onMount() {
241
+ this.shadowRoot.addEventListener('click', this._handleClick);
242
+
243
+ this._unwatchCompleted = this.completedItems.watch(value => {
244
+ const target = this.shadowRoot.querySelector('#completed-count');
245
+ if (target) target.textContent = String(value);
246
+ });
247
+
248
+ this._unwatchTotal = this.totalItems.watch(() => {
249
+ const percent = this.shadowRoot.querySelector('#completion-percent');
250
+ if (percent) percent.textContent = `${this.completionPercent.value}%`;
251
+ });
252
+
253
+ this._unwatchCompletionPercent = this.completionPercent.watch(value => {
254
+ const percent = this.shadowRoot.querySelector('#completion-percent');
255
+ const bar = this.shadowRoot.querySelector<HTMLElement>('#completion-bar');
256
+ if (percent) percent.textContent = `${value}%`;
257
+ if (bar) bar.style.width = `${value}%`;
258
+ });
259
+
260
+ this._unwatchRemainingItems = this.remainingItems.watch(value => {
261
+ const target = this.shadowRoot.querySelector('#remaining-count');
262
+ if (target) target.textContent = String(value);
263
+ });
264
+
265
+ this._unwatchStatusLabel = this.statusLabel.watch(value => {
266
+ const target = this.shadowRoot.querySelector('#status-label');
267
+ if (target) target.textContent = value;
268
+ });
269
+ }
270
+
271
+ onUnmount() {
272
+ this.shadowRoot?.removeEventListener('click', this._handleClick);
273
+ this._unwatchCompleted?.();
274
+ this._unwatchTotal?.();
275
+ this._unwatchCompletionPercent?.();
276
+ this._unwatchRemainingItems?.();
277
+ this._unwatchStatusLabel?.();
278
+ this.completionPercent.dispose();
279
+ this.remainingItems.dispose();
280
+ this.statusLabel.dispose();
281
+ }
282
+ }
283
+
284
+ defineComponent('dashboard-signal-lab', DashboardSignalLab);
@@ -0,0 +1,27 @@
1
+ /**
2
+ * API Endpoint Constants
3
+ */
4
+
5
+ export const API_ENDPOINTS = {
6
+ AUTH: {
7
+ LOGIN: '/auth/login',
8
+ LOGOUT: '/auth/logout',
9
+ REFRESH: '/auth/refresh',
10
+ VERIFY: '/auth/verify',
11
+ },
12
+
13
+ DASHBOARD: {
14
+ STATS: '/dashboard/stats',
15
+ ACTIVITY: '/dashboard/activity',
16
+ },
17
+
18
+ USERS: {
19
+ LIST: '/users',
20
+ DETAIL: (id: string | number) => `/users/${id}`,
21
+ CREATE: '/users',
22
+ UPDATE: (id: string | number) => `/users/${id}`,
23
+ DELETE: (id: string | number) => `/users/${id}`,
24
+ },
25
+ } as const;
26
+
27
+ export default API_ENDPOINTS;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Error Message Constants
3
+ */
4
+
5
+ export const ERROR_MESSAGES = {
6
+ NETWORK_ERROR: 'Unable to connect to the server. Please check your internet connection.',
7
+ TIMEOUT: 'Request timed out. Please try again.',
8
+ SERVER_ERROR: 'Something went wrong on our end. Please try again later.',
9
+
10
+ UNAUTHORIZED: 'Your session has expired. Please log in again.',
11
+ FORBIDDEN: 'You do not have permission to access this resource.',
12
+ INVALID_CREDENTIALS: 'Invalid email or password.',
13
+
14
+ REQUIRED_FIELD: (field: string) => `${field} is required.`,
15
+ INVALID_EMAIL: 'Please enter a valid email address.',
16
+ PASSWORD_TOO_SHORT: 'Password must be at least 8 characters long.',
17
+ PASSWORDS_DONT_MATCH: 'Passwords do not match.',
18
+
19
+ NOT_FOUND: 'The requested resource was not found.',
20
+ UNKNOWN_ERROR: 'An unexpected error occurred. Please try again.',
21
+ } as const;
22
+
23
+ export default ERROR_MESSAGES;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Constants Index
3
+ */
4
+
5
+ export { ROUTES } from './routePaths.js';
6
+ export { API_ENDPOINTS } from './apiEndpoints.js';
7
+ export { STORAGE_KEYS } from './storageKeys.js';
8
+ export { ERROR_MESSAGES } from './errorMessages.js';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Application Routes
3
+ */
4
+
5
+ export const ROUTES = {
6
+ HOME: '/',
7
+ ABOUT: '/about',
8
+ LOGIN: '/login',
9
+
10
+ DASHBOARD: '/dashboard',
11
+ USER_DETAIL: '/users/:id',
12
+ PROFILE: '/profile',
13
+ } as const;
14
+
15
+ export default ROUTES;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Storage Key Constants
3
+ */
4
+
5
+ export const STORAGE_KEYS = {
6
+ ACCESS_TOKEN: 'access_token',
7
+ REFRESH_TOKEN: 'refresh_token',
8
+ USER_DATA: 'user_data',
9
+
10
+ SIDEBAR_COLLAPSED: 'sidebar-collapsed',
11
+ THEME: 'theme',
12
+ LANGUAGE: 'language',
13
+
14
+ CACHE_PREFIX: 'cache_',
15
+ CACHE_TIMESTAMP: 'cache_timestamp',
16
+ } as const;
17
+
18
+ export default STORAGE_KEYS;