bertui 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/bin/bertui.js +7 -7
- package/index.js +35 -35
- package/package.json +5 -4
- package/src/build/css-builder.js +83 -83
- package/src/build.js +122 -122
- package/src/cli.js +65 -65
- package/src/client/compiler.js +227 -90
- package/src/config/defaultConfig.js +15 -15
- package/src/config/loadConfig.js +32 -32
- package/src/dev.js +22 -22
- package/src/logger/logger.js +17 -17
- package/src/logger/notes.md +19 -19
- package/src/router/Router.js +129 -0
- package/src/server/dev-server.js +279 -198
- package/src/styles/bertui.css +209 -209
- package/src/router/router.js +0 -216
package/src/styles/bertui.css
CHANGED
|
@@ -1,210 +1,210 @@
|
|
|
1
|
-
/* BertUI Built-in Utilities v0.1.0 */
|
|
2
|
-
|
|
3
|
-
/* Split Text Animation */
|
|
4
|
-
.split {
|
|
5
|
-
display: inline-block;
|
|
6
|
-
position: relative;
|
|
7
|
-
overflow: hidden;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.split::before {
|
|
11
|
-
content: attr(data-text);
|
|
12
|
-
position: absolute;
|
|
13
|
-
top: 0;
|
|
14
|
-
left: 0;
|
|
15
|
-
width: 50%;
|
|
16
|
-
overflow: hidden;
|
|
17
|
-
animation: split-left 0.6s ease-out;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.split::after {
|
|
21
|
-
content: attr(data-text);
|
|
22
|
-
position: absolute;
|
|
23
|
-
top: 0;
|
|
24
|
-
right: 0;
|
|
25
|
-
width: 50%;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
animation: split-right 0.6s ease-out;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
@keyframes split-left {
|
|
31
|
-
from {
|
|
32
|
-
transform: translateX(-100%);
|
|
33
|
-
opacity: 0;
|
|
34
|
-
}
|
|
35
|
-
to {
|
|
36
|
-
transform: translateX(0);
|
|
37
|
-
opacity: 1;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@keyframes split-right {
|
|
42
|
-
from {
|
|
43
|
-
transform: translateX(100%);
|
|
44
|
-
opacity: 0;
|
|
45
|
-
}
|
|
46
|
-
to {
|
|
47
|
-
transform: translateX(0);
|
|
48
|
-
opacity: 1;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/* Move Right Animation */
|
|
53
|
-
.moveright {
|
|
54
|
-
animation: moveright 0.5s ease-out;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@keyframes moveright {
|
|
58
|
-
from {
|
|
59
|
-
transform: translateX(-20px);
|
|
60
|
-
opacity: 0;
|
|
61
|
-
}
|
|
62
|
-
to {
|
|
63
|
-
transform: translateX(0);
|
|
64
|
-
opacity: 1;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/* Move Left Animation */
|
|
69
|
-
.moveleft {
|
|
70
|
-
animation: moveleft 0.5s ease-out;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@keyframes moveleft {
|
|
74
|
-
from {
|
|
75
|
-
transform: translateX(20px);
|
|
76
|
-
opacity: 0;
|
|
77
|
-
}
|
|
78
|
-
to {
|
|
79
|
-
transform: translateX(0);
|
|
80
|
-
opacity: 1;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* Fade In */
|
|
85
|
-
.fadein {
|
|
86
|
-
animation: fadein 0.5s ease-out;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@keyframes fadein {
|
|
90
|
-
from {
|
|
91
|
-
opacity: 0;
|
|
92
|
-
}
|
|
93
|
-
to {
|
|
94
|
-
opacity: 1;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/* Scale In */
|
|
99
|
-
.scalein {
|
|
100
|
-
animation: scalein 0.4s ease-out;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@keyframes scalein {
|
|
104
|
-
from {
|
|
105
|
-
transform: scale(0.8);
|
|
106
|
-
opacity: 0;
|
|
107
|
-
}
|
|
108
|
-
to {
|
|
109
|
-
transform: scale(1);
|
|
110
|
-
opacity: 1;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/* Bounce In */
|
|
115
|
-
.bouncein {
|
|
116
|
-
animation: bouncein 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
@keyframes bouncein {
|
|
120
|
-
0% {
|
|
121
|
-
transform: scale(0);
|
|
122
|
-
opacity: 0;
|
|
123
|
-
}
|
|
124
|
-
50% {
|
|
125
|
-
transform: scale(1.1);
|
|
126
|
-
}
|
|
127
|
-
100% {
|
|
128
|
-
transform: scale(1);
|
|
129
|
-
opacity: 1;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/* Slide Up */
|
|
134
|
-
.slideup {
|
|
135
|
-
animation: slideup 0.5s ease-out;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@keyframes slideup {
|
|
139
|
-
from {
|
|
140
|
-
transform: translateY(20px);
|
|
141
|
-
opacity: 0;
|
|
142
|
-
}
|
|
143
|
-
to {
|
|
144
|
-
transform: translateY(0);
|
|
145
|
-
opacity: 1;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/* Slide Down */
|
|
150
|
-
.slidedown {
|
|
151
|
-
animation: slidedown 0.5s ease-out;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
@keyframes slidedown {
|
|
155
|
-
from {
|
|
156
|
-
transform: translateY(-20px);
|
|
157
|
-
opacity: 0;
|
|
158
|
-
}
|
|
159
|
-
to {
|
|
160
|
-
transform: translateY(0);
|
|
161
|
-
opacity: 1;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/* Rotate In */
|
|
166
|
-
.rotatein {
|
|
167
|
-
animation: rotatein 0.6s ease-out;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
@keyframes rotatein {
|
|
171
|
-
from {
|
|
172
|
-
transform: rotate(-180deg) scale(0);
|
|
173
|
-
opacity: 0;
|
|
174
|
-
}
|
|
175
|
-
to {
|
|
176
|
-
transform: rotate(0) scale(1);
|
|
177
|
-
opacity: 1;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/* Pulse */
|
|
182
|
-
.pulse {
|
|
183
|
-
animation: pulse 1.5s ease-in-out infinite;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
@keyframes pulse {
|
|
187
|
-
0%, 100% {
|
|
188
|
-
transform: scale(1);
|
|
189
|
-
}
|
|
190
|
-
50% {
|
|
191
|
-
transform: scale(1.05);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/* Shake */
|
|
196
|
-
.shake {
|
|
197
|
-
animation: shake 0.5s ease-in-out;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
@keyframes shake {
|
|
201
|
-
0%, 100% {
|
|
202
|
-
transform: translateX(0);
|
|
203
|
-
}
|
|
204
|
-
10%, 30%, 50%, 70%, 90% {
|
|
205
|
-
transform: translateX(-5px);
|
|
206
|
-
}
|
|
207
|
-
20%, 40%, 60%, 80% {
|
|
208
|
-
transform: translateX(5px);
|
|
209
|
-
}
|
|
1
|
+
/* BertUI Built-in Utilities v0.1.0 */
|
|
2
|
+
|
|
3
|
+
/* Split Text Animation */
|
|
4
|
+
.split {
|
|
5
|
+
display: inline-block;
|
|
6
|
+
position: relative;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.split::before {
|
|
11
|
+
content: attr(data-text);
|
|
12
|
+
position: absolute;
|
|
13
|
+
top: 0;
|
|
14
|
+
left: 0;
|
|
15
|
+
width: 50%;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
animation: split-left 0.6s ease-out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.split::after {
|
|
21
|
+
content: attr(data-text);
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: 0;
|
|
24
|
+
right: 0;
|
|
25
|
+
width: 50%;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
animation: split-right 0.6s ease-out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@keyframes split-left {
|
|
31
|
+
from {
|
|
32
|
+
transform: translateX(-100%);
|
|
33
|
+
opacity: 0;
|
|
34
|
+
}
|
|
35
|
+
to {
|
|
36
|
+
transform: translateX(0);
|
|
37
|
+
opacity: 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@keyframes split-right {
|
|
42
|
+
from {
|
|
43
|
+
transform: translateX(100%);
|
|
44
|
+
opacity: 0;
|
|
45
|
+
}
|
|
46
|
+
to {
|
|
47
|
+
transform: translateX(0);
|
|
48
|
+
opacity: 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Move Right Animation */
|
|
53
|
+
.moveright {
|
|
54
|
+
animation: moveright 0.5s ease-out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes moveright {
|
|
58
|
+
from {
|
|
59
|
+
transform: translateX(-20px);
|
|
60
|
+
opacity: 0;
|
|
61
|
+
}
|
|
62
|
+
to {
|
|
63
|
+
transform: translateX(0);
|
|
64
|
+
opacity: 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Move Left Animation */
|
|
69
|
+
.moveleft {
|
|
70
|
+
animation: moveleft 0.5s ease-out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@keyframes moveleft {
|
|
74
|
+
from {
|
|
75
|
+
transform: translateX(20px);
|
|
76
|
+
opacity: 0;
|
|
77
|
+
}
|
|
78
|
+
to {
|
|
79
|
+
transform: translateX(0);
|
|
80
|
+
opacity: 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Fade In */
|
|
85
|
+
.fadein {
|
|
86
|
+
animation: fadein 0.5s ease-out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes fadein {
|
|
90
|
+
from {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
}
|
|
93
|
+
to {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Scale In */
|
|
99
|
+
.scalein {
|
|
100
|
+
animation: scalein 0.4s ease-out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@keyframes scalein {
|
|
104
|
+
from {
|
|
105
|
+
transform: scale(0.8);
|
|
106
|
+
opacity: 0;
|
|
107
|
+
}
|
|
108
|
+
to {
|
|
109
|
+
transform: scale(1);
|
|
110
|
+
opacity: 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Bounce In */
|
|
115
|
+
.bouncein {
|
|
116
|
+
animation: bouncein 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@keyframes bouncein {
|
|
120
|
+
0% {
|
|
121
|
+
transform: scale(0);
|
|
122
|
+
opacity: 0;
|
|
123
|
+
}
|
|
124
|
+
50% {
|
|
125
|
+
transform: scale(1.1);
|
|
126
|
+
}
|
|
127
|
+
100% {
|
|
128
|
+
transform: scale(1);
|
|
129
|
+
opacity: 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Slide Up */
|
|
134
|
+
.slideup {
|
|
135
|
+
animation: slideup 0.5s ease-out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@keyframes slideup {
|
|
139
|
+
from {
|
|
140
|
+
transform: translateY(20px);
|
|
141
|
+
opacity: 0;
|
|
142
|
+
}
|
|
143
|
+
to {
|
|
144
|
+
transform: translateY(0);
|
|
145
|
+
opacity: 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Slide Down */
|
|
150
|
+
.slidedown {
|
|
151
|
+
animation: slidedown 0.5s ease-out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@keyframes slidedown {
|
|
155
|
+
from {
|
|
156
|
+
transform: translateY(-20px);
|
|
157
|
+
opacity: 0;
|
|
158
|
+
}
|
|
159
|
+
to {
|
|
160
|
+
transform: translateY(0);
|
|
161
|
+
opacity: 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Rotate In */
|
|
166
|
+
.rotatein {
|
|
167
|
+
animation: rotatein 0.6s ease-out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@keyframes rotatein {
|
|
171
|
+
from {
|
|
172
|
+
transform: rotate(-180deg) scale(0);
|
|
173
|
+
opacity: 0;
|
|
174
|
+
}
|
|
175
|
+
to {
|
|
176
|
+
transform: rotate(0) scale(1);
|
|
177
|
+
opacity: 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Pulse */
|
|
182
|
+
.pulse {
|
|
183
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@keyframes pulse {
|
|
187
|
+
0%, 100% {
|
|
188
|
+
transform: scale(1);
|
|
189
|
+
}
|
|
190
|
+
50% {
|
|
191
|
+
transform: scale(1.05);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Shake */
|
|
196
|
+
.shake {
|
|
197
|
+
animation: shake 0.5s ease-in-out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@keyframes shake {
|
|
201
|
+
0%, 100% {
|
|
202
|
+
transform: translateX(0);
|
|
203
|
+
}
|
|
204
|
+
10%, 30%, 50%, 70%, 90% {
|
|
205
|
+
transform: translateX(-5px);
|
|
206
|
+
}
|
|
207
|
+
20%, 40%, 60%, 80% {
|
|
208
|
+
transform: translateX(5px);
|
|
209
|
+
}
|
|
210
210
|
}
|
package/src/router/router.js
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
// src/router/router.js
|
|
2
|
-
import { join, relative, parse } from 'path';
|
|
3
|
-
import { readdirSync, statSync, existsSync } from 'fs';
|
|
4
|
-
import logger from 'ernest-logger'; // Assuming Ernest Logger is used
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Scans the pages directory and generates route definitions
|
|
8
|
-
*/
|
|
9
|
-
export function generateRoutes(root) {
|
|
10
|
-
const pagesDir = join(root, 'src', 'pages');
|
|
11
|
-
|
|
12
|
-
if (!existsSync(pagesDir)) {
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const routes = [];
|
|
17
|
-
|
|
18
|
-
function scanDirectory(dir, basePath = '') {
|
|
19
|
-
const entries = readdirSync(dir);
|
|
20
|
-
|
|
21
|
-
for (const entry of entries) {
|
|
22
|
-
const fullPath = join(dir, entry);
|
|
23
|
-
const stat = statSync(fullPath);
|
|
24
|
-
|
|
25
|
-
if (stat.isDirectory()) {
|
|
26
|
-
// Recursively scan subdirectories
|
|
27
|
-
scanDirectory(fullPath, join(basePath, entry));
|
|
28
|
-
} else if (stat.isFile() && /\.(jsx?|tsx?)$/.test(entry)) {
|
|
29
|
-
const parsed = parse(entry);
|
|
30
|
-
let fileName = parsed.name;
|
|
31
|
-
|
|
32
|
-
// Skip non-page files (e.g., those starting with a dot)
|
|
33
|
-
if (fileName.startsWith('.') || fileName.startsWith('~')) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// --- ROUTE PATH GENERATION ---
|
|
38
|
-
|
|
39
|
-
// Handle dynamic routes: _filename -> :filename
|
|
40
|
-
let isDynamic = false;
|
|
41
|
-
if (fileName.startsWith('_')) {
|
|
42
|
-
fileName = fileName.slice(1); // Remove the underscore
|
|
43
|
-
isDynamic = true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Generate route path
|
|
47
|
-
let routePath = join(basePath, fileName === 'index' ? '' : fileName);
|
|
48
|
-
routePath = '/' + routePath.replace(/\\/g, '/'); // Use forward slashes
|
|
49
|
-
|
|
50
|
-
// Apply dynamic parameter if detected
|
|
51
|
-
if (isDynamic) {
|
|
52
|
-
// Replace the last part of the route path with the dynamic parameter (e.g., /blog/slug -> /blog/:slug)
|
|
53
|
-
routePath = routePath.replace(new RegExp(`/${fileName}$`), `/:${fileName}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Handle the root path, ensuring it's just '/'
|
|
57
|
-
if (routePath === '//') {
|
|
58
|
-
routePath = '/';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Get relative path from pages dir (used for imports in generated code)
|
|
62
|
-
const relativePath = relative(pagesDir, fullPath);
|
|
63
|
-
|
|
64
|
-
routes.push({
|
|
65
|
-
path: routePath,
|
|
66
|
-
file: relativePath,
|
|
67
|
-
component: fullPath,
|
|
68
|
-
isDynamic: isDynamic || routePath.includes(':')
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
scanDirectory(pagesDir);
|
|
75
|
-
|
|
76
|
-
// Sort routes: Root path first, then static, then dynamic
|
|
77
|
-
routes.sort((a, b) => {
|
|
78
|
-
if (a.path === '/') return -1;
|
|
79
|
-
if (b.path === '/') return 1;
|
|
80
|
-
if (a.isDynamic && !b.isDynamic) return 1;
|
|
81
|
-
if (!a.isDynamic && b.isDynamic) return -1;
|
|
82
|
-
return a.path.localeCompare(b.path);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return routes;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Generates the router client code
|
|
90
|
-
*/
|
|
91
|
-
export function generateRouterCode(routes) {
|
|
92
|
-
const imports = routes.map((route, index) =>
|
|
93
|
-
`import Page${index} from '../src/pages/${route.file.replace(/\\/g, '/')}';`
|
|
94
|
-
).join('\n');
|
|
95
|
-
|
|
96
|
-
const routeConfigs = routes.map((route, index) => ({
|
|
97
|
-
path: route.path,
|
|
98
|
-
component: `Page${index}`
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
return `
|
|
102
|
-
// Auto-generated router code - DO NOT EDIT MANUALLY
|
|
103
|
-
import React, { useState, useEffect } from 'react';
|
|
104
|
-
${imports}
|
|
105
|
-
|
|
106
|
-
const routes = ${JSON.stringify(routeConfigs, null, 2).replace(/"Page(\d+)"/g, 'Page$1')};
|
|
107
|
-
|
|
108
|
-
export function Router() {
|
|
109
|
-
const [currentPath, setCurrentPath] = useState(window.location.pathname);
|
|
110
|
-
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
const handlePopState = () => {
|
|
113
|
-
setCurrentPath(window.location.pathname);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
window.addEventListener('popstate', handlePopState);
|
|
117
|
-
return () => window.removeEventListener('popstate', handlePopState);
|
|
118
|
-
}, []);
|
|
119
|
-
|
|
120
|
-
// Match current path to route
|
|
121
|
-
const matchedRoute = routes.find(route => {
|
|
122
|
-
if (route.path === currentPath) return true;
|
|
123
|
-
|
|
124
|
-
// Handle dynamic routes (simple static matching for now)
|
|
125
|
-
const routeParts = route.path.split('/');
|
|
126
|
-
const pathParts = currentPath.split('/');
|
|
127
|
-
|
|
128
|
-
if (routeParts.length !== pathParts.length) return false;
|
|
129
|
-
|
|
130
|
-
return routeParts.every((part, i) => {
|
|
131
|
-
if (part.startsWith(':')) return true; // Match any value for dynamic part
|
|
132
|
-
return part === pathParts[i];
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
if (!matchedRoute) {
|
|
137
|
-
return <div style={{ padding: '2rem', textAlign: 'center' }}>
|
|
138
|
-
<h1>404 - Page Not Found</h1>
|
|
139
|
-
<p>The page "{currentPath}" does not exist.</p>
|
|
140
|
-
<a href="/" onClick={(e) => { e.preventDefault(); navigate('/'); }}>Go Home</a>
|
|
141
|
-
</div>;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Extract params from dynamic routes
|
|
145
|
-
const params = {};
|
|
146
|
-
if (matchedRoute.path.includes(':')) {
|
|
147
|
-
const routeParts = matchedRoute.path.split('/');
|
|
148
|
-
const pathParts = currentPath.split('/');
|
|
149
|
-
|
|
150
|
-
routeParts.forEach((part, i) => {
|
|
151
|
-
if (part.startsWith(':')) {
|
|
152
|
-
const paramName = part.slice(1);
|
|
153
|
-
params[paramName] = pathParts[i];
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const Component = matchedRoute.component;
|
|
159
|
-
return <Component params={params} />;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Client-side navigation
|
|
163
|
-
export function navigate(path) {
|
|
164
|
-
window.history.pushState({}, '', path);
|
|
165
|
-
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Link component for navigation
|
|
169
|
-
export function Link({ href, children, ...props }) {
|
|
170
|
-
const handleClick = (e) => {
|
|
171
|
-
e.preventDefault();
|
|
172
|
-
navigate(href);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<a href={href} onClick={handleClick} {...props}>
|
|
177
|
-
{children}
|
|
178
|
-
</a>
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Generates the main entry point with router
|
|
186
|
-
*/
|
|
187
|
-
export function generateMainWithRouter(routes) {
|
|
188
|
-
return `
|
|
189
|
-
import 'bertui/styles';
|
|
190
|
-
import React from 'react';
|
|
191
|
-
import ReactDOM from 'react-dom/client';
|
|
192
|
-
import { Router } from './.bertui/router.js';
|
|
193
|
-
|
|
194
|
-
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
195
|
-
<Router />
|
|
196
|
-
);
|
|
197
|
-
`;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function logRoutes(routes) {
|
|
201
|
-
if (routes.length === 0) {
|
|
202
|
-
logger.warn('No routes found in src/pages/');
|
|
203
|
-
logger.info('Create files in src/pages/ to define routes:');
|
|
204
|
-
logger.info(' src/pages/index.jsx → /');
|
|
205
|
-
logger.info(' src/pages/about.jsx → /about');
|
|
206
|
-
logger.info(' src/pages/user/_id.jsx → /user/:id'); // Updated tip
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
logger.bigLog('ROUTES DISCOVERED', { color: 'cyan' });
|
|
211
|
-
logger.table(routes.map(r => ({
|
|
212
|
-
route: r.path,
|
|
213
|
-
file: r.file,
|
|
214
|
-
type: r.isDynamic ? 'dynamic' : 'static'
|
|
215
|
-
})));
|
|
216
|
-
}
|