helfy-create 0.0.3 → 0.0.5

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.
@@ -3,11 +3,11 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "cross-env NODE_ENV=development webpack serve --mode development",
7
- "build": "cross-env NODE_ENV=production webpack --mode production"
6
+ "dev": "cross-env NODE_ENV=development rspack serve",
7
+ "build": "cross-env NODE_ENV=production rspack build"
8
8
  },
9
9
  "dependencies": {
10
- "@helfy/helfy": "^0.0.6"
10
+ "@helfy/helfy": "^0.0.9"
11
11
  },
12
12
  "devDependencies": {
13
13
  "@babel/core": "^7.20.0",
@@ -21,14 +21,10 @@
21
21
  "@helfy/helfy-ts-plugin": "^0.0.1",
22
22
  "babel-loader": "^9.1.2",
23
23
  "babel-plugin-transform-typescript-metadata": "^0.4.0",
24
- "clean-webpack-plugin": "^4.0.0",
25
24
  "cross-env": "^7.0.3",
26
25
  "css-loader": "^6.7.3",
27
- "html-webpack-plugin": "^5.5.0",
28
- "mini-css-extract-plugin": "^2.7.5",
29
26
  "typescript": "^5.9.3",
30
- "webpack": "^5.76.0",
31
- "webpack-cli": "^5.0.1",
32
- "webpack-dev-server": "^4.13.3"
27
+ "@rspack/cli": "^1.3.0",
28
+ "@rspack/core": "^1.3.0"
33
29
  }
34
30
  }
@@ -2,6 +2,7 @@ import { View } from "@helfy/helfy";
2
2
  import { ThemeContext, ThemeSync } from "@context/ThemeContext";
3
3
  import { MainLayout } from "@pages/layouts/MainLayout";
4
4
  import { Header } from "@widgets/Header/Header";
5
+ import { NavBar } from "@widgets/Navbar/Navbar";
5
6
  import { Footer } from "@widgets/Footer/Footer";
6
7
  import { AppRouter } from "@pages/_router";
7
8
 
@@ -14,6 +15,7 @@ export class App {
14
15
  <div class="app">
15
16
  <MainLayout>
16
17
  @slot.header() {<Header />}
18
+ @slot.sidebar() {<NavBar />}
17
19
  @slot.content() {<AppRouter />}
18
20
  @slot.footer() {<Footer />}
19
21
  </MainLayout>
@@ -1,26 +1,24 @@
1
- import { View, Context, provide, ctx } from "@helfy/helfy";
1
+ import { View, Context, state, useCtx, computed, effect } from "@helfy/helfy";
2
2
 
3
3
  export type TThemeMode = "light" | "dark";
4
4
 
5
5
  @Context
6
6
  export class ThemeContext {
7
- @provide({ reactive: true })
8
- mode: TThemeMode = "dark";
7
+ @state mode: TThemeMode = "dark";
9
8
 
10
- @provide()
11
- toggle = () => {
9
+ @state toggle = () => {
12
10
  this.mode = this.mode === "dark" ? "light" : "dark";
13
- this._syncToDom();
14
11
  };
15
12
 
16
- private _syncToDom() {
13
+ @effect
14
+ syncToDom() {
17
15
  document.documentElement.classList.toggle("dark", this.mode === "dark");
18
16
  }
19
17
  }
20
18
 
21
19
  @View
22
20
  export class ThemeSync {
23
- @ctx(ThemeContext)
21
+ @useCtx(ThemeContext)
24
22
  private theme!: ThemeContext;
25
23
 
26
24
  onMount() {
@@ -29,27 +27,27 @@ export class ThemeSync {
29
27
 
30
28
  render() {
31
29
  document.documentElement.classList.toggle("dark", this.theme.mode === "dark");
32
- return <span class="hidden" aria-hidden="true" />;
30
+ return <span class="hidden" aria-hidden="true"></span>;
33
31
  }
34
32
  }
35
33
 
36
34
  @View
37
35
  export class ThemeToggle {
38
- @ctx(ThemeContext, 'toggle')
39
- private toggle!: ThemeContext['toggle'];
36
+ @useCtx(ThemeContext, "toggle")
37
+ private toggle!: ThemeContext["toggle"];
40
38
 
41
- @ctx(ThemeContext, "mode")
42
- private mode!: ThemeContext['mode'];
39
+ @useCtx(ThemeContext, "mode")
40
+ private mode!: ThemeContext["mode"];
43
41
 
44
42
  render() {
45
43
  const isDark = this.mode === "dark";
46
44
  return (
47
45
  <button
48
46
  type="button"
49
- class="flex items-center justify-center h-9 rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-700 transition-colors px-4"
47
+ class="theme-toggle"
50
48
  onclick={this.toggle}
51
- title={isDark ? "Светлая тема" : "Тёмная тема"}
52
- aria-label={isDark ? "Переключить на светлую тему" : "Переключить на тёмную тему"}
49
+ title={isDark ? "Light theme" : "Dark theme"}
50
+ aria-label={isDark ? "Switch to light theme" : "Switch to dark theme"}
53
51
  >
54
52
  {isDark ? (
55
53
  <span>Светлая</span>
@@ -6,7 +6,12 @@ export class AboutPage {
6
6
  return (
7
7
  <div class="page">
8
8
  <h1>About</h1>
9
- <p>This is a Helfy application with routing.</p>
9
+ <p>
10
+ This is a Helfy application with routing, slots, and theme context.
11
+ </p>
12
+ <p>
13
+ Helfy is a declarative framework with decorators, reactive state, and built-in TypeScript support.
14
+ </p>
10
15
  </div>
11
16
  );
12
17
  }
@@ -8,9 +8,9 @@ export class CounterPage {
8
8
  return (
9
9
  <div class="page">
10
10
  <h1>Counter</h1>
11
- <p>Count: {this.count}</p>
12
- <button onclick={() => this.count++}>Increment</button>
13
- <button onclick={() => this.count--}>Decrement</button>
11
+ <p>Count: <strong>{this.count}</strong></p>
12
+ <button class="btn" onclick={() => this.count++}>Increment</button>
13
+ <button class="btn" onclick={() => this.count--}>Decrement</button>
14
14
  </div>
15
15
  );
16
16
  }
@@ -7,7 +7,14 @@ export class HomePage {
7
7
  <div class="page">
8
8
  <h1>Welcome to Helfy</h1>
9
9
  <p>
10
- Edit <code>src/App.tsx</code> and save to see your changes.
10
+ Welcome to Helfy — a modern TypeScript framework for web applications.
11
+ </p>
12
+ <p>
13
+ Edit <code>src/App.tsx</code> and save the file — changes will appear automatically.
14
+ </p>
15
+ <h2>Quick Start</h2>
16
+ <p>
17
+ Use the sidebar for navigation. Try the Counter page to see reactivity in action and toggle the theme in the top-right corner.
11
18
  </p>
12
19
  </div>
13
20
  );
@@ -4,25 +4,23 @@ import { View } from "@helfy/helfy";
4
4
  export class MainLayout {
5
5
  render() {
6
6
  return (
7
- <div class="flex flex-col min-h-screen">
8
- <header
9
- class="flex items-center justify-between px-4 py-3 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm border-b border-gray-200 dark:border-gray-700"
10
- >
7
+ <div class="layout">
8
+ <header class="header">
11
9
  @slot:header()
12
10
  </header>
13
- <div class="flex flex-1 min-h-0">
14
- <aside class="w-60 shrink-0 border-r border-gray-200 dark:border-gray-800 bg-white/70 dark:bg-gray-900/60 backdrop-blur-sm px-4 py-6 flex flex-col gap-4">
15
- @slot:sidebar()
16
- </aside>
17
- <main class="flex flex-1 flex-col w-full">
11
+ <div class="layout__body">
12
+ <aside class="sidebar">
13
+ @slot:sidebar()
14
+ </aside>
15
+ <main class="main">
16
+ <div class="main__content">
18
17
  @slot:content()
19
- </main>
18
+ </div>
19
+ </main>
20
20
  </div>
21
- <footer
22
- class="px-4 py-2 text-center text-sm text-gray-500 dark:text-gray-400 border-t border-gray-200 dark:border-gray-700"
23
- >
21
+ <footer class="footer">
24
22
  @slot:footer() fallback {
25
- <span>© 2025</span>
23
+ <span>© 2026</span>
26
24
  }
27
25
  </footer>
28
26
  </div>
@@ -1,62 +1,358 @@
1
+ /* Helfy Starter — Design System */
2
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&display=swap');
3
+
4
+ :root {
5
+ /* Light theme */
6
+ --color-bg: #fafbfc;
7
+ --color-bg-elevated: #ffffff;
8
+ --color-bg-muted: #f1f5f9;
9
+ --color-border: #e2e8f0;
10
+ --color-border-muted: #f1f5f9;
11
+ --color-text: #1e293b;
12
+ --color-text-muted: #64748b;
13
+ --color-text-subtle: #94a3b8;
14
+ --color-accent: #6366f1;
15
+ --color-accent-hover: #4f46e5;
16
+ --color-accent-muted: #e0e7ff;
17
+ --color-accent-text: #4338ca;
18
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
19
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
20
+ --radius: 8px;
21
+ --radius-lg: 12px;
22
+ }
23
+
24
+ .dark {
25
+ --color-bg: #0f172a;
26
+ --color-bg-elevated: #1e293b;
27
+ --color-bg-muted: #334155;
28
+ --color-border: #334155;
29
+ --color-border-muted: #1e293b;
30
+ --color-text: #f1f5f9;
31
+ --color-text-muted: #94a3b8;
32
+ --color-text-subtle: #64748b;
33
+ --color-accent: #818cf8;
34
+ --color-accent-hover: #a5b4fc;
35
+ --color-accent-muted: rgba(129, 140, 248, 0.2);
36
+ --color-accent-text: #a5b4fc;
37
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
38
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
39
+ }
40
+
1
41
  * {
2
42
  box-sizing: border-box;
3
43
  }
4
44
 
5
45
  body {
6
46
  margin: 0;
7
- font-family: system-ui, -apple-system, sans-serif;
8
- line-height: 1.5;
47
+ font-family: 'DM Sans', system-ui, -apple-system, sans-serif;
48
+ font-size: 15px;
49
+ line-height: 1.6;
50
+ color: var(--color-text);
51
+ background: var(--color-bg);
52
+ -webkit-font-smoothing: antialiased;
9
53
  }
10
54
 
55
+ /* App shell */
11
56
  .app {
12
57
  min-height: 100vh;
13
58
  display: flex;
14
59
  flex-direction: column;
15
60
  }
16
61
 
17
- .nav {
62
+ /* Layout */
63
+ .layout {
18
64
  display: flex;
65
+ flex-direction: column;
66
+ min-height: 100vh;
67
+ }
68
+
69
+ .layout__body {
70
+ display: flex;
71
+ flex: 1;
72
+ min-height: 0;
73
+ }
74
+
75
+ /* Header */
76
+ .header {
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: space-between;
80
+ padding: 0.75rem 1.5rem;
81
+ background: var(--color-bg-elevated);
82
+ border-bottom: 1px solid var(--color-border);
83
+ backdrop-filter: blur(12px);
84
+ box-shadow: var(--shadow-sm);
85
+ }
86
+
87
+ .header__inner {
88
+ display: flex;
89
+ flex: 1;
90
+ align-items: center;
91
+ justify-content: space-between;
19
92
  gap: 1rem;
20
- padding: 1rem;
21
- background: #1e293b;
22
- color: white;
93
+ width: 100%;
23
94
  }
24
95
 
25
- .nav a {
26
- color: #93c5fd;
27
- text-decoration: none;
96
+ .header__logo {
97
+ font-weight: 700;
98
+ font-size: 1.125rem;
99
+ color: var(--color-text) !important;
100
+ text-decoration: none !important;
28
101
  }
29
102
 
30
- .nav a:hover {
31
- text-decoration: underline;
103
+ .header__logo:hover {
104
+ color: var(--color-accent) !important;
105
+ background: transparent !important;
32
106
  }
33
107
 
108
+ /* Sidebar */
109
+ .sidebar {
110
+ width: 240px;
111
+ flex-shrink: 0;
112
+ padding: 1.5rem 1rem;
113
+ border-right: 1px solid var(--color-border);
114
+ background: var(--color-bg-elevated);
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: 0.5rem;
118
+ }
119
+
120
+ /* Main content */
34
121
  .main {
122
+ flex: 1;
123
+ display: flex;
124
+ flex-direction: column;
125
+ width: 100%;
126
+ min-width: 0;
127
+ }
128
+
129
+ .main__content {
35
130
  flex: 1;
36
131
  padding: 2rem;
37
132
  }
38
133
 
134
+ /* Footer */
135
+ .footer {
136
+ padding: 0.75rem 1.5rem;
137
+ text-align: center;
138
+ font-size: 0.875rem;
139
+ color: var(--color-text-muted);
140
+ border-top: 1px solid var(--color-border);
141
+ }
142
+
143
+ /* Navigation */
144
+ .nav {
145
+ display: flex;
146
+ gap: 0.25rem;
147
+ align-items: center;
148
+ }
149
+
150
+ .nav a {
151
+ color: var(--color-text-muted);
152
+ text-decoration: none;
153
+ padding: 0.5rem 0.75rem;
154
+ border-radius: var(--radius);
155
+ font-weight: 500;
156
+ transition: color 0.15s, background 0.15s;
157
+ }
158
+
159
+ .nav a:hover {
160
+ color: var(--color-accent);
161
+ background: var(--color-accent-muted);
162
+ }
163
+
164
+ .nav a.nav-link--active {
165
+ color: var(--color-accent-text);
166
+ background: var(--color-accent-muted);
167
+ font-weight: 600;
168
+ }
169
+
170
+ /* Sidebar navigation */
171
+ .sidebar-nav {
172
+ display: flex;
173
+ flex-direction: column;
174
+ gap: 0.25rem;
175
+ font-size: 0.875rem;
176
+ }
177
+
178
+ .sidebar-nav a {
179
+ display: block;
180
+ padding: 0.5rem 0.75rem;
181
+ border-radius: var(--radius);
182
+ color: var(--color-text-muted);
183
+ text-decoration: none;
184
+ font-weight: 500;
185
+ transition: color 0.15s, background 0.15s;
186
+ }
187
+
188
+ .sidebar-nav a:hover {
189
+ color: var(--color-accent);
190
+ background: var(--color-accent-muted);
191
+ }
192
+
193
+ .sidebar-nav a.nav-link--active {
194
+ color: var(--color-accent-text);
195
+ background: var(--color-accent-muted);
196
+ font-weight: 600;
197
+ }
198
+
199
+ /* Theme toggle */
200
+ .theme-toggle {
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ height: 36px;
205
+ padding: 0 1rem;
206
+ border: none;
207
+ border-radius: var(--radius);
208
+ background: transparent;
209
+ color: var(--color-text-muted);
210
+ font-size: 0.875rem;
211
+ font-weight: 500;
212
+ cursor: pointer;
213
+ transition: color 0.15s, background 0.15s;
214
+ }
215
+
216
+ .theme-toggle:hover {
217
+ color: var(--color-text);
218
+ background: var(--color-bg-muted);
219
+ }
220
+
221
+ /* Page content */
222
+ .page {
223
+ max-width: 42rem;
224
+ }
225
+
39
226
  .page h1 {
40
- margin-top: 0;
227
+ margin: 0 0 1rem;
228
+ font-size: 1.75rem;
229
+ font-weight: 700;
230
+ color: var(--color-text);
231
+ letter-spacing: -0.02em;
232
+ }
233
+
234
+ .page h2 {
235
+ margin: 1.5rem 0 0.5rem;
236
+ font-size: 1.125rem;
237
+ font-weight: 600;
238
+ }
239
+
240
+ .page p {
241
+ margin: 0 0 1rem;
242
+ color: var(--color-text-muted);
243
+ }
244
+
245
+ .page p:last-child {
246
+ margin-bottom: 0;
41
247
  }
42
248
 
43
249
  .page code {
44
- background: #e2e8f0;
250
+ background: var(--color-bg-muted);
45
251
  padding: 0.2em 0.4em;
46
252
  border-radius: 4px;
47
253
  font-size: 0.9em;
254
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
255
+ color: var(--color-accent-text);
48
256
  }
49
257
 
50
- .page button {
258
+ /* Buttons */
259
+ .btn {
51
260
  margin-right: 0.5rem;
52
261
  padding: 0.5rem 1rem;
53
- font-size: 1rem;
262
+ font-size: 0.9375rem;
263
+ font-weight: 500;
54
264
  cursor: pointer;
55
- border: 1px solid #cbd5e1;
56
- border-radius: 6px;
57
- background: white;
265
+ border: 1px solid var(--color-border);
266
+ border-radius: var(--radius);
267
+ background: var(--color-bg-elevated);
268
+ color: var(--color-text);
269
+ transition: border-color 0.15s, background 0.15s, color 0.15s;
270
+ }
271
+
272
+ .btn:hover {
273
+ background: var(--color-bg-muted);
274
+ border-color: var(--color-text-subtle);
275
+ }
276
+
277
+ .btn--primary {
278
+ background: var(--color-accent);
279
+ border-color: var(--color-accent);
280
+ color: white;
281
+ }
282
+
283
+ .btn--primary:hover {
284
+ background: var(--color-accent-hover);
285
+ border-color: var(--color-accent-hover);
286
+ }
287
+
288
+ /* 404 page */
289
+ .not-found {
290
+ display: flex;
291
+ flex-direction: column;
292
+ flex: 1;
293
+ align-items: center;
294
+ justify-content: center;
295
+ padding: 4rem 2rem;
296
+ text-align: center;
297
+ background: linear-gradient(180deg, var(--color-bg) 0%, var(--color-bg-muted) 100%);
298
+ }
299
+
300
+ .not-found__code {
301
+ font-size: 6rem;
302
+ font-weight: 800;
303
+ letter-spacing: -0.05em;
304
+ color: var(--color-accent-muted);
305
+ user-select: none;
306
+ line-height: 1;
307
+ }
308
+
309
+ .not-found__title {
310
+ margin: 1rem 0 0;
311
+ font-size: 1.25rem;
312
+ font-weight: 600;
313
+ color: var(--color-text);
314
+ }
315
+
316
+ .not-found__text {
317
+ margin: 0.5rem 0 0;
318
+ font-size: 0.875rem;
319
+ color: var(--color-text-muted);
320
+ max-width: 24rem;
321
+ }
322
+
323
+ .not-found__path {
324
+ margin: 0.5rem 0 0;
325
+ font-size: 0.75rem;
326
+ color: var(--color-text-subtle);
327
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
328
+ word-break: break-all;
329
+ max-width: 100%;
330
+ }
331
+
332
+ .not-found .btn--primary {
333
+ margin: 2rem 0 0;
334
+ display: inline-flex;
335
+ align-items: center;
336
+ gap: 0.5rem;
337
+ padding: 0.625rem 1.25rem;
338
+ font-size: 0.875rem;
339
+ text-decoration: none;
340
+ box-shadow: var(--shadow-md);
341
+ }
342
+
343
+ /* Utility */
344
+ .hidden {
345
+ display: none !important;
58
346
  }
59
347
 
60
- .page button:hover {
61
- background: #f1f5f9;
348
+ .visually-hidden {
349
+ position: absolute;
350
+ width: 1px;
351
+ height: 1px;
352
+ padding: 0;
353
+ margin: -1px;
354
+ overflow: hidden;
355
+ clip: rect(0, 0, 0, 0);
356
+ white-space: nowrap;
357
+ border: 0;
62
358
  }
@@ -1,15 +1,31 @@
1
1
  import { ThemeToggle } from "@context/ThemeContext";
2
- import { Link, View } from "@helfy/helfy";
2
+ import { Link, path, View } from "@helfy/helfy";
3
3
 
4
4
  @View
5
5
  export class Header {
6
+ @path()
7
+ private pathname?: string;
8
+
6
9
  render() {
7
10
  return (
8
- <div class="flex flex-1 items-center gap-4 justify-between w-full">
11
+ <div class="header__inner">
12
+ <Link to="/" label="Helfy" class="header__logo" />
9
13
  <nav class="nav">
10
- <Link to="/" label="Home" />
11
- <Link to="/counter" label="Counter" />
12
- <Link to="/about" label="About" />
14
+ <Link
15
+ to="/"
16
+ label="Home"
17
+ class={this.pathname === "/" ? "nav-link--active" : ""}
18
+ />
19
+ <Link
20
+ to="/counter"
21
+ label="Counter"
22
+ class={this.pathname === "/counter" ? "nav-link--active" : ""}
23
+ />
24
+ <Link
25
+ to="/about"
26
+ label="About"
27
+ class={this.pathname === "/about" ? "nav-link--active" : ""}
28
+ />
13
29
  </nav>
14
30
  <ThemeToggle />
15
31
  </div>
@@ -7,34 +7,21 @@ export class NavBar {
7
7
 
8
8
  render() {
9
9
  return (
10
- <nav class="flex flex-col gap-1 text-sm text-gray-700 dark:text-gray-200">
10
+ <nav class="sidebar-nav">
11
11
  <Link
12
12
  to="/"
13
13
  label="Home"
14
- class={
15
- this.pathname === "/"
16
- ? "px-2 py-1.5 rounded-md bg-indigo-50 dark:bg-indigo-900/40 text-indigo-700 dark:text-indigo-200 font-semibold"
17
- : "px-2 py-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
18
- }
14
+ class={this.pathname === "/" ? "nav-link nav-link--active" : "nav-link"}
19
15
  />
20
16
  <Link
21
17
  to="/counter"
22
18
  label="Counter"
23
- class={
24
- this.pathname === "/counter"
25
- ? "px-2 py-1.5 rounded-md bg-indigo-50 dark:bg-indigo-900/40 text-indigo-700 dark:text-indigo-200 font-semibold"
26
- : "px-2 py-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
27
- }
28
- >
29
- </Link>
19
+ class={this.pathname === "/counter" ? "nav-link nav-link--active" : "nav-link"}
20
+ />
30
21
  <Link
31
22
  to="/about"
32
23
  label="About"
33
- class={
34
- this.pathname === "/about"
35
- ? "px-2 py-1.5 rounded-md bg-indigo-50 dark:bg-indigo-900/40 text-indigo-700 dark:text-indigo-200 font-semibold"
36
- : "px-2 py-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
37
- }
24
+ class={this.pathname === "/about" ? "nav-link nav-link--active" : "nav-link"}
38
25
  />
39
26
  </nav>
40
27
  );