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.
- package/README.md +9 -0
- package/package.json +2 -4
- package/template/package-lock.json +1326 -1345
- package/template/package.json +5 -9
- package/template/src/App.tsx +2 -0
- package/template/src/context/ThemeContext.tsx +14 -16
- package/template/src/pages/AboutPage.tsx +6 -1
- package/template/src/pages/CounterPage.tsx +3 -3
- package/template/src/pages/HomePage.tsx +8 -1
- package/template/src/pages/layouts/MainLayout.tsx +12 -14
- package/template/src/styles.css +316 -20
- package/template/src/widgets/Header/Header.tsx +21 -5
- package/template/src/widgets/Navbar/Navbar.tsx +5 -18
- package/template/src/widgets/NotFound/NotFound.tsx +9 -15
- package/template/docs/README.md +0 -1300
- package/template/webpack.config.js +0 -81
package/template/package.json
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev": "cross-env NODE_ENV=development
|
|
7
|
-
"build": "cross-env NODE_ENV=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.
|
|
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
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"webpack-dev-server": "^4.13.3"
|
|
27
|
+
"@rspack/cli": "^1.3.0",
|
|
28
|
+
"@rspack/core": "^1.3.0"
|
|
33
29
|
}
|
|
34
30
|
}
|
package/template/src/App.tsx
CHANGED
|
@@ -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,
|
|
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
|
-
@
|
|
8
|
-
mode: TThemeMode = "dark";
|
|
7
|
+
@state mode: TThemeMode = "dark";
|
|
9
8
|
|
|
10
|
-
@
|
|
11
|
-
toggle = () => {
|
|
9
|
+
@state toggle = () => {
|
|
12
10
|
this.mode = this.mode === "dark" ? "light" : "dark";
|
|
13
|
-
this._syncToDom();
|
|
14
11
|
};
|
|
15
12
|
|
|
16
|
-
|
|
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
|
-
@
|
|
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
|
-
@
|
|
39
|
-
private toggle!: ThemeContext[
|
|
36
|
+
@useCtx(ThemeContext, "toggle")
|
|
37
|
+
private toggle!: ThemeContext["toggle"];
|
|
40
38
|
|
|
41
|
-
@
|
|
42
|
-
private mode!: ThemeContext[
|
|
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="
|
|
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>
|
|
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
|
-
|
|
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="
|
|
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="
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
</
|
|
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>©
|
|
23
|
+
<span>© 2026</span>
|
|
26
24
|
}
|
|
27
25
|
</footer>
|
|
28
26
|
</div>
|
package/template/src/styles.css
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
background: #1e293b;
|
|
22
|
-
color: white;
|
|
93
|
+
width: 100%;
|
|
23
94
|
}
|
|
24
95
|
|
|
25
|
-
.
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
.
|
|
31
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
258
|
+
/* Buttons */
|
|
259
|
+
.btn {
|
|
51
260
|
margin-right: 0.5rem;
|
|
52
261
|
padding: 0.5rem 1rem;
|
|
53
|
-
font-size:
|
|
262
|
+
font-size: 0.9375rem;
|
|
263
|
+
font-weight: 500;
|
|
54
264
|
cursor: pointer;
|
|
55
|
-
border: 1px solid
|
|
56
|
-
border-radius:
|
|
57
|
-
background:
|
|
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
|
-
.
|
|
61
|
-
|
|
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="
|
|
11
|
+
<div class="header__inner">
|
|
12
|
+
<Link to="/" label="Helfy" class="header__logo" />
|
|
9
13
|
<nav class="nav">
|
|
10
|
-
<Link
|
|
11
|
-
|
|
12
|
-
|
|
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="
|
|
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
|
-
|
|
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
|
);
|