@evolution-james/evolution-theme-engine 1.0.0 → 1.1.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.
- package/README.md +49 -1
- package/dist/components/ThemeNavBar.js +55 -9
- package/dist/styles/themes.css +31 -6
- package/package.json +1 -1
- package/src/components/ThemeNavBar.jsx +56 -11
- package/src/styles/themes.css +31 -6
package/README.md
CHANGED
|
@@ -74,6 +74,8 @@ function MyHeader() {
|
|
|
74
74
|
|
|
75
75
|
Use this when you want a complete, ready-made navbar with the theme selector pre-rendered inside it.
|
|
76
76
|
|
|
77
|
+
**Multi-page app** (standard `<a>` tags, full navigation):
|
|
78
|
+
|
|
77
79
|
```jsx
|
|
78
80
|
import { ThemeNavBar } from '@evolution-james/evolution-theme-engine';
|
|
79
81
|
|
|
@@ -82,6 +84,7 @@ function App() {
|
|
|
82
84
|
<>
|
|
83
85
|
<ThemeNavBar
|
|
84
86
|
title="My App"
|
|
87
|
+
titleHref="/"
|
|
85
88
|
links={[
|
|
86
89
|
{ label: 'Home', href: '/' },
|
|
87
90
|
{ label: 'About', href: '/about' },
|
|
@@ -94,6 +97,33 @@ function App() {
|
|
|
94
97
|
}
|
|
95
98
|
```
|
|
96
99
|
|
|
100
|
+
**Single-page app with React Router** (no page refreshes):
|
|
101
|
+
|
|
102
|
+
```jsx
|
|
103
|
+
import { Link } from 'react-router-dom';
|
|
104
|
+
import { ThemeNavBar } from '@evolution-james/evolution-theme-engine';
|
|
105
|
+
|
|
106
|
+
function App() {
|
|
107
|
+
return (
|
|
108
|
+
<>
|
|
109
|
+
<ThemeNavBar
|
|
110
|
+
title="My App"
|
|
111
|
+
titleHref="/"
|
|
112
|
+
links={[
|
|
113
|
+
{ label: 'Home', href: '/' },
|
|
114
|
+
{ label: 'About', href: '/about' },
|
|
115
|
+
{ label: 'Contact', href: '/contact' },
|
|
116
|
+
]}
|
|
117
|
+
renderLink={(props) => (
|
|
118
|
+
<Link to={props.href} className={props.className}>{props.children}</Link>
|
|
119
|
+
)}
|
|
120
|
+
/>
|
|
121
|
+
{/* rest of your app */}
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
97
127
|
`ThemeNavBar` is intentionally barebones — it applies `var(--color-*)` variables for all colors, which means it automatically adapts to whichever theme is active.
|
|
98
128
|
|
|
99
129
|
---
|
|
@@ -142,6 +172,12 @@ From there, use the CSS variables anywhere in your own component styles:
|
|
|
142
172
|
background-color: var(--color-card-bg);
|
|
143
173
|
color: var(--color-text);
|
|
144
174
|
border: 1px solid var(--color-card-border);
|
|
175
|
+
box-shadow: 0 2px 8px var(--color-shadow);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.my-card-subtitle {
|
|
179
|
+
color: var(--color-text-muted);
|
|
180
|
+
font-size: 0.875rem;
|
|
145
181
|
}
|
|
146
182
|
|
|
147
183
|
.my-button {
|
|
@@ -153,6 +189,12 @@ From there, use the CSS variables anywhere in your own component styles:
|
|
|
153
189
|
color: var(--color-link);
|
|
154
190
|
}
|
|
155
191
|
|
|
192
|
+
.my-input {
|
|
193
|
+
background-color: var(--color-input-bg);
|
|
194
|
+
border: 1px solid var(--color-input-border);
|
|
195
|
+
color: var(--color-text);
|
|
196
|
+
}
|
|
197
|
+
|
|
156
198
|
hr {
|
|
157
199
|
border-color: var(--color-divider);
|
|
158
200
|
}
|
|
@@ -168,6 +210,7 @@ Because the variables are set on `<html>`, they cascade down to every element on
|
|
|
168
210
|
|---|---|
|
|
169
211
|
| `--color-bg` | Page / app background |
|
|
170
212
|
| `--color-text` | Primary body text |
|
|
213
|
+
| `--color-text-muted` | Secondary / subtitle / caption text |
|
|
171
214
|
| `--color-card-bg` | Card / panel surface background |
|
|
172
215
|
| `--color-card-border` | Card / panel border color |
|
|
173
216
|
| `--color-btn-dark-bg` | Background for "dark" style buttons |
|
|
@@ -175,6 +218,9 @@ Because the variables are set on `<html>`, they cascade down to every element on
|
|
|
175
218
|
| `--color-btn-light-bg` | Background for "light" style buttons |
|
|
176
219
|
| `--color-btn-light-text` | Text on "light" style buttons |
|
|
177
220
|
| `--color-divider` | Horizontal rules / separators |
|
|
221
|
+
| `--color-input-bg` | Form input / textarea background |
|
|
222
|
+
| `--color-input-border` | Form input / textarea border |
|
|
223
|
+
| `--color-shadow` | Box shadow color |
|
|
178
224
|
| `--color-primary` | Primary accent / brand color |
|
|
179
225
|
| `--color-on-primary` | Text rendered on top of `--color-primary` |
|
|
180
226
|
| `--color-link` | Hyperlink color |
|
|
@@ -318,7 +364,9 @@ registerTheme('ocean', {
|
|
|
318
364
|
| Prop | Type | Default | Description |
|
|
319
365
|
|---|---|---|---|
|
|
320
366
|
| `title` | `string` | `'My App'` | Brand text shown on the left of the navbar. |
|
|
321
|
-
| `
|
|
367
|
+
| `titleHref` | `string` | `undefined` | If provided, the brand text becomes a link pointing to this URL. Omit to render a plain `<span>`. |
|
|
368
|
+
| `links` | `Array<{ label, href, onClick? }>` | Placeholder links | Navigation links rendered in the center. If an entry includes `onClick`, the default browser navigation is prevented and `onClick` is called instead — use this for SPA navigation (e.g. React Router's `navigate()`). |
|
|
369
|
+
| `renderLink` | `(props) => ReactNode` | `undefined` | Render prop for full control over link rendering. `props` contains `href`, `children`, and `className`. Applied to both the title link and all nav links. Use this for React Router: return `<Link to={props.href} className={props.className}>{props.children}</Link>`. Takes precedence over individual `onClick` handlers. |
|
|
322
370
|
| `themes` | `object` | All 5 built-in themes | Forwarded to the internal `<ThemeSelector>`. |
|
|
323
371
|
| `className` | `string` | `''` | Extra CSS classes added to the `<nav>` (alongside `etn-navbar`). |
|
|
324
372
|
| `style` | `object` | `{}` | Inline styles for the `<nav>`. |
|
|
@@ -28,11 +28,24 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
28
28
|
* title (string) — Brand/title text shown on the
|
|
29
29
|
* left side of the navbar.
|
|
30
30
|
* Defaults to 'My App'.
|
|
31
|
+
* titleHref (string) — If provided, wraps the brand
|
|
32
|
+
* text in a link pointing to this
|
|
33
|
+
* URL. Omit to render a plain <span>.
|
|
31
34
|
* links (Array<object>) — Navigation links rendered to
|
|
32
35
|
* the right of the title. Each
|
|
33
|
-
* entry: { label, href }.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
+
* entry: { label, href, onClick? }.
|
|
37
|
+
* If onClick is provided, the default
|
|
38
|
+
* browser navigation is prevented and
|
|
39
|
+
* onClick is called instead — use this
|
|
40
|
+
* for programmatic SPA navigation.
|
|
41
|
+
* Defaults to a few placeholder links.
|
|
42
|
+
* renderLink (function) — Optional render prop for full
|
|
43
|
+
* control over link rendering. Useful
|
|
44
|
+
* for React Router: pass a function
|
|
45
|
+
* ({ href, children, className }) =>
|
|
46
|
+
* ReactNode and return a <Link>
|
|
47
|
+
* component. Applied to both the
|
|
48
|
+
* title link and all nav links.
|
|
36
49
|
* themes (object) — Forwarded to <ThemeSelector>.
|
|
37
50
|
* Defaults to all 5 built-in themes.
|
|
38
51
|
* className (string) — Extra class(es) added to the
|
|
@@ -40,15 +53,29 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
40
53
|
* 'etn-navbar'.
|
|
41
54
|
* style (object) — Inline styles for the root <nav>.
|
|
42
55
|
*
|
|
43
|
-
* Usage:
|
|
44
|
-
*
|
|
56
|
+
* Usage — plain HTML (multi-page app):
|
|
57
|
+
* <ThemeNavBar
|
|
58
|
+
* title="My Cool App"
|
|
59
|
+
* titleHref="/"
|
|
60
|
+
* links={[
|
|
61
|
+
* { label: 'Home', href: '/' },
|
|
62
|
+
* { label: 'About', href: '/about' },
|
|
63
|
+
* ]}
|
|
64
|
+
* />
|
|
65
|
+
*
|
|
66
|
+
* Usage — React Router (SPA, no page refreshes):
|
|
67
|
+
* import { Link } from 'react-router-dom';
|
|
45
68
|
*
|
|
46
69
|
* <ThemeNavBar
|
|
47
70
|
* title="My Cool App"
|
|
71
|
+
* titleHref="/"
|
|
48
72
|
* links={[
|
|
49
73
|
* { label: 'Home', href: '/' },
|
|
50
74
|
* { label: 'About', href: '/about' },
|
|
51
75
|
* ]}
|
|
76
|
+
* renderLink={(props) => (
|
|
77
|
+
* <Link to={props.href} className={props.className}>{props.children}</Link>
|
|
78
|
+
* )}
|
|
52
79
|
* />
|
|
53
80
|
* ============================================================
|
|
54
81
|
*/
|
|
@@ -66,28 +93,47 @@ var DEFAULT_LINKS = [{
|
|
|
66
93
|
function ThemeNavBar(_ref) {
|
|
67
94
|
var _ref$title = _ref.title,
|
|
68
95
|
title = _ref$title === void 0 ? 'My App' : _ref$title,
|
|
96
|
+
titleHref = _ref.titleHref,
|
|
69
97
|
_ref$links = _ref.links,
|
|
70
98
|
links = _ref$links === void 0 ? DEFAULT_LINKS : _ref$links,
|
|
71
99
|
themes = _ref.themes,
|
|
72
100
|
_ref$className = _ref.className,
|
|
73
101
|
className = _ref$className === void 0 ? '' : _ref$className,
|
|
74
102
|
_ref$style = _ref.style,
|
|
75
|
-
style = _ref$style === void 0 ? {} : _ref$style
|
|
103
|
+
style = _ref$style === void 0 ? {} : _ref$style,
|
|
104
|
+
renderLink = _ref.renderLink;
|
|
76
105
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("nav", {
|
|
77
106
|
className: "etn-navbar".concat(className ? " ".concat(className) : ''),
|
|
78
107
|
style: style,
|
|
79
|
-
children: [
|
|
108
|
+
children: [titleHref ? renderLink ? renderLink({
|
|
109
|
+
href: titleHref,
|
|
110
|
+
children: title,
|
|
111
|
+
className: 'etn-navbar-brand'
|
|
112
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)("a", {
|
|
113
|
+
href: titleHref,
|
|
114
|
+
className: "etn-navbar-brand",
|
|
115
|
+
children: title
|
|
116
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
80
117
|
className: "etn-navbar-brand",
|
|
81
118
|
children: title
|
|
82
119
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("ul", {
|
|
83
120
|
className: "etn-navbar-links",
|
|
84
121
|
children: links.map(function (_ref2) {
|
|
85
122
|
var label = _ref2.label,
|
|
86
|
-
href = _ref2.href
|
|
123
|
+
href = _ref2.href,
|
|
124
|
+
onClick = _ref2.onClick;
|
|
87
125
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)("li", {
|
|
88
|
-
children:
|
|
126
|
+
children: renderLink ? renderLink({
|
|
127
|
+
href: href,
|
|
128
|
+
children: label,
|
|
129
|
+
className: 'etn-navbar-link'
|
|
130
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)("a", {
|
|
89
131
|
href: href,
|
|
90
132
|
className: "etn-navbar-link",
|
|
133
|
+
onClick: onClick ? function (e) {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
onClick(e);
|
|
136
|
+
} : undefined,
|
|
91
137
|
children: label
|
|
92
138
|
})
|
|
93
139
|
}, label);
|
package/dist/styles/themes.css
CHANGED
|
@@ -29,19 +29,23 @@
|
|
|
29
29
|
* ──────────────────────
|
|
30
30
|
* --color-bg Page / app background
|
|
31
31
|
* --color-text Primary body text
|
|
32
|
+
* --color-text-muted Secondary / subtitle / caption text
|
|
32
33
|
* --color-card-bg Card / panel surface background
|
|
33
|
-
* --color-card-border Card / panel border
|
|
34
|
+
* --color-card-border Card / panel border color
|
|
34
35
|
* --color-btn-dark-bg Background for "dark" style buttons
|
|
35
36
|
* --color-btn-dark-text Text on "dark" style buttons
|
|
36
37
|
* --color-btn-light-bg Background for "light" style buttons
|
|
37
38
|
* --color-btn-light-text Text on "light" style buttons
|
|
38
39
|
* --color-divider Horizontal rules / separators
|
|
39
|
-
* --color-
|
|
40
|
+
* --color-input-bg Form input / textarea background
|
|
41
|
+
* --color-input-border Form input / textarea border
|
|
42
|
+
* --color-shadow Box shadow color
|
|
43
|
+
* --color-primary Primary accent / brand color
|
|
40
44
|
* --color-on-primary Text drawn on top of --color-primary
|
|
41
|
-
* --color-link Hyperlink
|
|
45
|
+
* --color-link Hyperlink color
|
|
42
46
|
* --color-hover-bg Subtle hover-state background tint
|
|
43
47
|
* --color-code-bg Code block background
|
|
44
|
-
* --color-code-text Code block text
|
|
48
|
+
* --color-code-text Code block text color
|
|
45
49
|
*
|
|
46
50
|
* ADDING A CUSTOM THEME (CSS approach)
|
|
47
51
|
* ─────────────────────────────────────
|
|
@@ -73,6 +77,7 @@
|
|
|
73
77
|
:root {
|
|
74
78
|
--color-bg: #f8f9fa;
|
|
75
79
|
--color-text: #212529;
|
|
80
|
+
--color-text-muted: #6c757d;
|
|
76
81
|
--color-card-bg: #ffffff;
|
|
77
82
|
--color-card-border: #dee2e6;
|
|
78
83
|
--color-btn-dark-bg: #f8f9fa;
|
|
@@ -80,10 +85,13 @@
|
|
|
80
85
|
--color-btn-light-bg: #ffffff;
|
|
81
86
|
--color-btn-light-text: #212529;
|
|
82
87
|
--color-divider: #dee2e6;
|
|
88
|
+
--color-input-bg: #ffffff;
|
|
89
|
+
--color-input-border: #ced4da;
|
|
90
|
+
--color-shadow: rgba(0, 0, 0, 0.10);
|
|
83
91
|
|
|
84
92
|
--color-primary: #1976d2;
|
|
85
93
|
--color-on-primary: #ffffff;
|
|
86
|
-
--color-link: #
|
|
94
|
+
--color-link: #4a90e2;
|
|
87
95
|
--color-hover-bg: rgba(25, 118, 210, 0.08);
|
|
88
96
|
--color-code-bg: #282c34;
|
|
89
97
|
--color-code-text: #ffffff;
|
|
@@ -93,6 +101,7 @@
|
|
|
93
101
|
[data-theme="dark"] {
|
|
94
102
|
--color-bg: #212529;
|
|
95
103
|
--color-text: #f8f9fa;
|
|
104
|
+
--color-text-muted: #9ca3af;
|
|
96
105
|
--color-card-bg: #343a40;
|
|
97
106
|
--color-card-border: #444444;
|
|
98
107
|
--color-btn-dark-bg: #f8f9fa;
|
|
@@ -100,6 +109,9 @@
|
|
|
100
109
|
--color-btn-light-bg: #343a40;
|
|
101
110
|
--color-btn-light-text: #f8f9fa;
|
|
102
111
|
--color-divider: #444444;
|
|
112
|
+
--color-input-bg: #2d3238;
|
|
113
|
+
--color-input-border: #555555;
|
|
114
|
+
--color-shadow: rgba(0, 0, 0, 0.40);
|
|
103
115
|
|
|
104
116
|
--color-primary: #90caf9;
|
|
105
117
|
--color-on-primary: #212529;
|
|
@@ -116,6 +128,7 @@
|
|
|
116
128
|
[data-theme="forest"] {
|
|
117
129
|
--color-bg: #1b2e22;
|
|
118
130
|
--color-text: #cde8d4;
|
|
131
|
+
--color-text-muted: #8aad95;
|
|
119
132
|
--color-card-bg: #243c2b;
|
|
120
133
|
--color-card-border: #3a5c44;
|
|
121
134
|
--color-btn-dark-bg: #3a5c44;
|
|
@@ -123,6 +136,9 @@
|
|
|
123
136
|
--color-btn-light-bg: #243c2b;
|
|
124
137
|
--color-btn-light-text: #cde8d4;
|
|
125
138
|
--color-divider: #3a5c44;
|
|
139
|
+
--color-input-bg: #1f3526;
|
|
140
|
+
--color-input-border: #3a5c44;
|
|
141
|
+
--color-shadow: rgba(0, 0, 0, 0.35);
|
|
126
142
|
|
|
127
143
|
--color-primary: #4caf70;
|
|
128
144
|
--color-on-primary: #1b2e22;
|
|
@@ -139,6 +155,7 @@
|
|
|
139
155
|
[data-theme="tron"] {
|
|
140
156
|
--color-bg: #0f172a;
|
|
141
157
|
--color-text: #67e8f9;
|
|
158
|
+
--color-text-muted: #38bcd4;
|
|
142
159
|
--color-card-bg: #1e293b;
|
|
143
160
|
--color-card-border: #67e8f9;
|
|
144
161
|
--color-btn-dark-bg: #0ea5e9;
|
|
@@ -146,6 +163,9 @@
|
|
|
146
163
|
--color-btn-light-bg: #1e293b;
|
|
147
164
|
--color-btn-light-text: #67e8f9;
|
|
148
165
|
--color-divider: #0ea5e9;
|
|
166
|
+
--color-input-bg: #162236;
|
|
167
|
+
--color-input-border: #38bcd4;
|
|
168
|
+
--color-shadow: rgba(14, 165, 233, 0.15);
|
|
149
169
|
|
|
150
170
|
--color-primary: #0ea5e9;
|
|
151
171
|
--color-on-primary: #0f172a;
|
|
@@ -158,11 +178,12 @@
|
|
|
158
178
|
/* --- Midnight Theme ---
|
|
159
179
|
* A deep night-sky palette: near-black background, muted
|
|
160
180
|
* blue-grey text, teal (#5ce1b5) primary accent, and
|
|
161
|
-
* light-blue (#8bd4ff) link
|
|
181
|
+
* light-blue (#8bd4ff) link color.
|
|
162
182
|
*/
|
|
163
183
|
[data-theme="midnight"] {
|
|
164
184
|
--color-bg: #0b1016;
|
|
165
185
|
--color-text: #e7edf2;
|
|
186
|
+
--color-text-muted: #8fa9be;
|
|
166
187
|
--color-card-bg: #131c28;
|
|
167
188
|
--color-card-border: #263141;
|
|
168
189
|
--color-btn-dark-bg: #263141;
|
|
@@ -170,6 +191,9 @@
|
|
|
170
191
|
--color-btn-light-bg: #0f151e;
|
|
171
192
|
--color-btn-light-text: #e7edf2;
|
|
172
193
|
--color-divider: #263141;
|
|
194
|
+
--color-input-bg: #0f1a26;
|
|
195
|
+
--color-input-border: #2a3e52;
|
|
196
|
+
--color-shadow: rgba(0, 0, 0, 0.45);
|
|
173
197
|
|
|
174
198
|
--color-primary: #5ce1b5;
|
|
175
199
|
--color-on-primary: #0b1016;
|
|
@@ -219,6 +243,7 @@ html {
|
|
|
219
243
|
font-size: 1.1rem;
|
|
220
244
|
font-weight: 700;
|
|
221
245
|
color: var(--color-text);
|
|
246
|
+
text-decoration: none;
|
|
222
247
|
white-space: nowrap;
|
|
223
248
|
flex-shrink: 0;
|
|
224
249
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolution-james/evolution-theme-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A plug-and-play React theme engine with CSS variable-based theming, localStorage persistence, and optional navbar component.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -19,11 +19,24 @@
|
|
|
19
19
|
* title (string) — Brand/title text shown on the
|
|
20
20
|
* left side of the navbar.
|
|
21
21
|
* Defaults to 'My App'.
|
|
22
|
+
* titleHref (string) — If provided, wraps the brand
|
|
23
|
+
* text in a link pointing to this
|
|
24
|
+
* URL. Omit to render a plain <span>.
|
|
22
25
|
* links (Array<object>) — Navigation links rendered to
|
|
23
26
|
* the right of the title. Each
|
|
24
|
-
* entry: { label, href }.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
+
* entry: { label, href, onClick? }.
|
|
28
|
+
* If onClick is provided, the default
|
|
29
|
+
* browser navigation is prevented and
|
|
30
|
+
* onClick is called instead — use this
|
|
31
|
+
* for programmatic SPA navigation.
|
|
32
|
+
* Defaults to a few placeholder links.
|
|
33
|
+
* renderLink (function) — Optional render prop for full
|
|
34
|
+
* control over link rendering. Useful
|
|
35
|
+
* for React Router: pass a function
|
|
36
|
+
* ({ href, children, className }) =>
|
|
37
|
+
* ReactNode and return a <Link>
|
|
38
|
+
* component. Applied to both the
|
|
39
|
+
* title link and all nav links.
|
|
27
40
|
* themes (object) — Forwarded to <ThemeSelector>.
|
|
28
41
|
* Defaults to all 5 built-in themes.
|
|
29
42
|
* className (string) — Extra class(es) added to the
|
|
@@ -31,15 +44,29 @@
|
|
|
31
44
|
* 'etn-navbar'.
|
|
32
45
|
* style (object) — Inline styles for the root <nav>.
|
|
33
46
|
*
|
|
34
|
-
* Usage:
|
|
35
|
-
*
|
|
47
|
+
* Usage — plain HTML (multi-page app):
|
|
48
|
+
* <ThemeNavBar
|
|
49
|
+
* title="My Cool App"
|
|
50
|
+
* titleHref="/"
|
|
51
|
+
* links={[
|
|
52
|
+
* { label: 'Home', href: '/' },
|
|
53
|
+
* { label: 'About', href: '/about' },
|
|
54
|
+
* ]}
|
|
55
|
+
* />
|
|
56
|
+
*
|
|
57
|
+
* Usage — React Router (SPA, no page refreshes):
|
|
58
|
+
* import { Link } from 'react-router-dom';
|
|
36
59
|
*
|
|
37
60
|
* <ThemeNavBar
|
|
38
61
|
* title="My Cool App"
|
|
62
|
+
* titleHref="/"
|
|
39
63
|
* links={[
|
|
40
64
|
* { label: 'Home', href: '/' },
|
|
41
65
|
* { label: 'About', href: '/about' },
|
|
42
66
|
* ]}
|
|
67
|
+
* renderLink={(props) => (
|
|
68
|
+
* <Link to={props.href} className={props.className}>{props.children}</Link>
|
|
69
|
+
* )}
|
|
43
70
|
* />
|
|
44
71
|
* ============================================================
|
|
45
72
|
*/
|
|
@@ -55,26 +82,44 @@ const DEFAULT_LINKS = [
|
|
|
55
82
|
|
|
56
83
|
export function ThemeNavBar({
|
|
57
84
|
title = 'My App',
|
|
85
|
+
titleHref,
|
|
58
86
|
links = DEFAULT_LINKS,
|
|
59
87
|
themes,
|
|
60
88
|
className = '',
|
|
61
89
|
style = {},
|
|
90
|
+
renderLink,
|
|
62
91
|
}) {
|
|
63
92
|
return (
|
|
64
93
|
<nav
|
|
65
94
|
className={`etn-navbar${className ? ` ${className}` : ''}`}
|
|
66
95
|
style={style}
|
|
67
96
|
>
|
|
68
|
-
{/* Left side: brand/title */}
|
|
69
|
-
|
|
97
|
+
{/* Left side: brand/title — link if titleHref is set, plain span otherwise */}
|
|
98
|
+
{titleHref ? (
|
|
99
|
+
renderLink ? (
|
|
100
|
+
renderLink({ href: titleHref, children: title, className: 'etn-navbar-brand' })
|
|
101
|
+
) : (
|
|
102
|
+
<a href={titleHref} className="etn-navbar-brand">{title}</a>
|
|
103
|
+
)
|
|
104
|
+
) : (
|
|
105
|
+
<span className="etn-navbar-brand">{title}</span>
|
|
106
|
+
)}
|
|
70
107
|
|
|
71
108
|
{/* Center: navigation links */}
|
|
72
109
|
<ul className="etn-navbar-links">
|
|
73
|
-
{links.map(({ label, href }) => (
|
|
110
|
+
{links.map(({ label, href, onClick }) => (
|
|
74
111
|
<li key={label}>
|
|
75
|
-
|
|
76
|
-
{label}
|
|
77
|
-
|
|
112
|
+
{renderLink ? (
|
|
113
|
+
renderLink({ href, children: label, className: 'etn-navbar-link' })
|
|
114
|
+
) : (
|
|
115
|
+
<a
|
|
116
|
+
href={href}
|
|
117
|
+
className="etn-navbar-link"
|
|
118
|
+
onClick={onClick ? (e) => { e.preventDefault(); onClick(e); } : undefined}
|
|
119
|
+
>
|
|
120
|
+
{label}
|
|
121
|
+
</a>
|
|
122
|
+
)}
|
|
78
123
|
</li>
|
|
79
124
|
))}
|
|
80
125
|
</ul>
|
package/src/styles/themes.css
CHANGED
|
@@ -29,19 +29,23 @@
|
|
|
29
29
|
* ──────────────────────
|
|
30
30
|
* --color-bg Page / app background
|
|
31
31
|
* --color-text Primary body text
|
|
32
|
+
* --color-text-muted Secondary / subtitle / caption text
|
|
32
33
|
* --color-card-bg Card / panel surface background
|
|
33
|
-
* --color-card-border Card / panel border
|
|
34
|
+
* --color-card-border Card / panel border color
|
|
34
35
|
* --color-btn-dark-bg Background for "dark" style buttons
|
|
35
36
|
* --color-btn-dark-text Text on "dark" style buttons
|
|
36
37
|
* --color-btn-light-bg Background for "light" style buttons
|
|
37
38
|
* --color-btn-light-text Text on "light" style buttons
|
|
38
39
|
* --color-divider Horizontal rules / separators
|
|
39
|
-
* --color-
|
|
40
|
+
* --color-input-bg Form input / textarea background
|
|
41
|
+
* --color-input-border Form input / textarea border
|
|
42
|
+
* --color-shadow Box shadow color
|
|
43
|
+
* --color-primary Primary accent / brand color
|
|
40
44
|
* --color-on-primary Text drawn on top of --color-primary
|
|
41
|
-
* --color-link Hyperlink
|
|
45
|
+
* --color-link Hyperlink color
|
|
42
46
|
* --color-hover-bg Subtle hover-state background tint
|
|
43
47
|
* --color-code-bg Code block background
|
|
44
|
-
* --color-code-text Code block text
|
|
48
|
+
* --color-code-text Code block text color
|
|
45
49
|
*
|
|
46
50
|
* ADDING A CUSTOM THEME (CSS approach)
|
|
47
51
|
* ─────────────────────────────────────
|
|
@@ -73,6 +77,7 @@
|
|
|
73
77
|
:root {
|
|
74
78
|
--color-bg: #f8f9fa;
|
|
75
79
|
--color-text: #212529;
|
|
80
|
+
--color-text-muted: #6c757d;
|
|
76
81
|
--color-card-bg: #ffffff;
|
|
77
82
|
--color-card-border: #dee2e6;
|
|
78
83
|
--color-btn-dark-bg: #f8f9fa;
|
|
@@ -80,10 +85,13 @@
|
|
|
80
85
|
--color-btn-light-bg: #ffffff;
|
|
81
86
|
--color-btn-light-text: #212529;
|
|
82
87
|
--color-divider: #dee2e6;
|
|
88
|
+
--color-input-bg: #ffffff;
|
|
89
|
+
--color-input-border: #ced4da;
|
|
90
|
+
--color-shadow: rgba(0, 0, 0, 0.10);
|
|
83
91
|
|
|
84
92
|
--color-primary: #1976d2;
|
|
85
93
|
--color-on-primary: #ffffff;
|
|
86
|
-
--color-link: #
|
|
94
|
+
--color-link: #4a90e2;
|
|
87
95
|
--color-hover-bg: rgba(25, 118, 210, 0.08);
|
|
88
96
|
--color-code-bg: #282c34;
|
|
89
97
|
--color-code-text: #ffffff;
|
|
@@ -93,6 +101,7 @@
|
|
|
93
101
|
[data-theme="dark"] {
|
|
94
102
|
--color-bg: #212529;
|
|
95
103
|
--color-text: #f8f9fa;
|
|
104
|
+
--color-text-muted: #9ca3af;
|
|
96
105
|
--color-card-bg: #343a40;
|
|
97
106
|
--color-card-border: #444444;
|
|
98
107
|
--color-btn-dark-bg: #f8f9fa;
|
|
@@ -100,6 +109,9 @@
|
|
|
100
109
|
--color-btn-light-bg: #343a40;
|
|
101
110
|
--color-btn-light-text: #f8f9fa;
|
|
102
111
|
--color-divider: #444444;
|
|
112
|
+
--color-input-bg: #2d3238;
|
|
113
|
+
--color-input-border: #555555;
|
|
114
|
+
--color-shadow: rgba(0, 0, 0, 0.40);
|
|
103
115
|
|
|
104
116
|
--color-primary: #90caf9;
|
|
105
117
|
--color-on-primary: #212529;
|
|
@@ -116,6 +128,7 @@
|
|
|
116
128
|
[data-theme="forest"] {
|
|
117
129
|
--color-bg: #1b2e22;
|
|
118
130
|
--color-text: #cde8d4;
|
|
131
|
+
--color-text-muted: #8aad95;
|
|
119
132
|
--color-card-bg: #243c2b;
|
|
120
133
|
--color-card-border: #3a5c44;
|
|
121
134
|
--color-btn-dark-bg: #3a5c44;
|
|
@@ -123,6 +136,9 @@
|
|
|
123
136
|
--color-btn-light-bg: #243c2b;
|
|
124
137
|
--color-btn-light-text: #cde8d4;
|
|
125
138
|
--color-divider: #3a5c44;
|
|
139
|
+
--color-input-bg: #1f3526;
|
|
140
|
+
--color-input-border: #3a5c44;
|
|
141
|
+
--color-shadow: rgba(0, 0, 0, 0.35);
|
|
126
142
|
|
|
127
143
|
--color-primary: #4caf70;
|
|
128
144
|
--color-on-primary: #1b2e22;
|
|
@@ -139,6 +155,7 @@
|
|
|
139
155
|
[data-theme="tron"] {
|
|
140
156
|
--color-bg: #0f172a;
|
|
141
157
|
--color-text: #67e8f9;
|
|
158
|
+
--color-text-muted: #38bcd4;
|
|
142
159
|
--color-card-bg: #1e293b;
|
|
143
160
|
--color-card-border: #67e8f9;
|
|
144
161
|
--color-btn-dark-bg: #0ea5e9;
|
|
@@ -146,6 +163,9 @@
|
|
|
146
163
|
--color-btn-light-bg: #1e293b;
|
|
147
164
|
--color-btn-light-text: #67e8f9;
|
|
148
165
|
--color-divider: #0ea5e9;
|
|
166
|
+
--color-input-bg: #162236;
|
|
167
|
+
--color-input-border: #38bcd4;
|
|
168
|
+
--color-shadow: rgba(14, 165, 233, 0.15);
|
|
149
169
|
|
|
150
170
|
--color-primary: #0ea5e9;
|
|
151
171
|
--color-on-primary: #0f172a;
|
|
@@ -158,11 +178,12 @@
|
|
|
158
178
|
/* --- Midnight Theme ---
|
|
159
179
|
* A deep night-sky palette: near-black background, muted
|
|
160
180
|
* blue-grey text, teal (#5ce1b5) primary accent, and
|
|
161
|
-
* light-blue (#8bd4ff) link
|
|
181
|
+
* light-blue (#8bd4ff) link color.
|
|
162
182
|
*/
|
|
163
183
|
[data-theme="midnight"] {
|
|
164
184
|
--color-bg: #0b1016;
|
|
165
185
|
--color-text: #e7edf2;
|
|
186
|
+
--color-text-muted: #8fa9be;
|
|
166
187
|
--color-card-bg: #131c28;
|
|
167
188
|
--color-card-border: #263141;
|
|
168
189
|
--color-btn-dark-bg: #263141;
|
|
@@ -170,6 +191,9 @@
|
|
|
170
191
|
--color-btn-light-bg: #0f151e;
|
|
171
192
|
--color-btn-light-text: #e7edf2;
|
|
172
193
|
--color-divider: #263141;
|
|
194
|
+
--color-input-bg: #0f1a26;
|
|
195
|
+
--color-input-border: #2a3e52;
|
|
196
|
+
--color-shadow: rgba(0, 0, 0, 0.45);
|
|
173
197
|
|
|
174
198
|
--color-primary: #5ce1b5;
|
|
175
199
|
--color-on-primary: #0b1016;
|
|
@@ -219,6 +243,7 @@ html {
|
|
|
219
243
|
font-size: 1.1rem;
|
|
220
244
|
font-weight: 700;
|
|
221
245
|
color: var(--color-text);
|
|
246
|
+
text-decoration: none;
|
|
222
247
|
white-space: nowrap;
|
|
223
248
|
flex-shrink: 0;
|
|
224
249
|
}
|