@evolution-james/evolution-theme-engine 1.0.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.
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.THEMES = void 0;
7
+ exports.ThemeProvider = ThemeProvider;
8
+ exports.registerTheme = registerTheme;
9
+ exports.useTheme = useTheme;
10
+ var _react = require("react");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
13
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
14
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
15
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
16
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
17
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } /*
18
+ * ============================================================
19
+ * ThemeContext.jsx — Evolution Theme Engine
20
+ * ============================================================
21
+ * This file is the heart of the theme engine. It provides:
22
+ *
23
+ * THEMES — A map of human-readable keys to the exact
24
+ * string values used as the `data-theme`
25
+ * attribute on <html>. These strings must
26
+ * match selectors in themes.css exactly.
27
+ *
28
+ * ThemeProvider — A React context provider that:
29
+ * • Reads the persisted theme from localStorage
30
+ * on first render (no flash on reload).
31
+ * • Sets `data-theme` on <html> whenever the
32
+ * theme changes so CSS kicks in site-wide.
33
+ * • Exposes `theme` and `setTheme` to all
34
+ * descendant components via context.
35
+ *
36
+ * useTheme — Convenience hook. Call inside any component
37
+ * wrapped by ThemeProvider to read or change
38
+ * the active theme.
39
+ *
40
+ * registerTheme — Runtime utility. Injects a new [data-theme]
41
+ * CSS block at runtime so consumers can add
42
+ * custom themes without editing themes.css.
43
+ *
44
+ * Data flow:
45
+ * User picks theme → setTheme() → React state updates
46
+ * → useEffect fires → data-theme attribute set on <html>
47
+ * → CSS [data-theme="..."] block takes effect site-wide.
48
+ * ============================================================
49
+ */ /*
50
+ * THEMES maps a friendly JS key to the exact string written
51
+ * into the HTML `data-theme` attribute. Add an entry here and
52
+ * a matching [data-theme="..."] block in themes.css to create
53
+ * a new built-in theme.
54
+ */
55
+ var THEMES = exports.THEMES = {
56
+ light: 'light',
57
+ dark: 'dark',
58
+ forest: 'forest',
59
+ tron: 'tron',
60
+ midnight: 'midnight'
61
+ };
62
+ var ThemeContext = /*#__PURE__*/(0, _react.createContext)({
63
+ theme: THEMES.light,
64
+ setTheme: function setTheme() {}
65
+ });
66
+
67
+ /*
68
+ * ThemeProvider wraps your application root (e.g. in index.jsx).
69
+ *
70
+ * It reads the user's last-saved theme from localStorage so the
71
+ * correct theme is applied before the first paint, preventing a
72
+ * flash back to the default Light theme on page refresh.
73
+ *
74
+ * Props:
75
+ * children — React subtree to receive theme context.
76
+ * defaultTheme — (optional) Override the fallback when no
77
+ * localStorage value exists. Defaults to 'light'.
78
+ * storageKey — (optional) localStorage key used to persist
79
+ * the selected theme. Defaults to 'etn-theme'.
80
+ */
81
+ function ThemeProvider(_ref) {
82
+ var children = _ref.children,
83
+ _ref$defaultTheme = _ref.defaultTheme,
84
+ defaultTheme = _ref$defaultTheme === void 0 ? THEMES.light : _ref$defaultTheme,
85
+ _ref$storageKey = _ref.storageKey,
86
+ storageKey = _ref$storageKey === void 0 ? 'etn-theme' : _ref$storageKey;
87
+ var _useState = (0, _react.useState)(function () {
88
+ return localStorage.getItem(storageKey) || defaultTheme;
89
+ }),
90
+ _useState2 = _slicedToArray(_useState, 2),
91
+ theme = _useState2[0],
92
+ setThemeState = _useState2[1];
93
+
94
+ /*
95
+ * Sync the `data-theme` attribute on <html> whenever theme changes.
96
+ * This is what triggers the CSS variable overrides in themes.css.
97
+ */
98
+ (0, _react.useEffect)(function () {
99
+ document.documentElement.setAttribute('data-theme', theme);
100
+ }, [theme]);
101
+
102
+ /*
103
+ * setTheme updates React state AND persists to localStorage so
104
+ * the selection survives page refreshes and new tabs.
105
+ */
106
+ var setTheme = function setTheme(newTheme) {
107
+ setThemeState(newTheme);
108
+ localStorage.setItem(storageKey, newTheme);
109
+ };
110
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(ThemeContext.Provider, {
111
+ value: {
112
+ theme: theme,
113
+ setTheme: setTheme
114
+ },
115
+ children: children
116
+ });
117
+ }
118
+
119
+ /*
120
+ * useTheme — convenience hook.
121
+ * Returns { theme, setTheme } from the nearest ThemeProvider.
122
+ * Must be called inside a component that is a descendant of ThemeProvider.
123
+ */
124
+ function useTheme() {
125
+ return (0, _react.useContext)(ThemeContext);
126
+ }
127
+
128
+ /*
129
+ * registerTheme — runtime theme registration.
130
+ *
131
+ * Dynamically injects a new [data-theme="name"] CSS block into the
132
+ * document <head> at runtime. This lets consumers define custom themes
133
+ * in JavaScript without modifying themes.css.
134
+ *
135
+ * Parameters:
136
+ * name (string) — The theme key, e.g. 'ocean'. This value is used
137
+ * as the data-theme attribute value.
138
+ * vars (object) — A plain object mapping CSS variable names to values.
139
+ * Keys should NOT include the leading '--'.
140
+ *
141
+ * Example:
142
+ * registerTheme('ocean', {
143
+ * 'color-bg': '#0a1628',
144
+ * 'color-text': '#e0f0ff',
145
+ * 'color-primary': '#00b4d8',
146
+ * 'color-on-primary': '#0a1628',
147
+ * 'color-card-bg': '#0d2137',
148
+ * 'color-card-border': '#1a3a5c',
149
+ * 'color-divider': '#1a3a5c',
150
+ * 'color-link': '#90e0ef',
151
+ * 'color-hover-bg': 'rgba(0,180,216,0.1)',
152
+ * 'color-code-bg': '#070f1a',
153
+ * 'color-code-text': '#e0f0ff',
154
+ * });
155
+ *
156
+ * After calling registerTheme, add the key to your own THEMES-like object
157
+ * and pass it to <ThemeSelector> via the `themes` prop to surface it in
158
+ * the UI.
159
+ */
160
+ function registerTheme(name, vars) {
161
+ var existingId = "etn-theme-".concat(name);
162
+ var existing = document.getElementById(existingId);
163
+ if (existing) existing.remove();
164
+ var declarations = Object.entries(vars).map(function (_ref2) {
165
+ var _ref3 = _slicedToArray(_ref2, 2),
166
+ key = _ref3[0],
167
+ value = _ref3[1];
168
+ return " --".concat(key, ": ").concat(value, ";");
169
+ }).join('\n');
170
+ var css = "[data-theme=\"".concat(name, "\"] {\n").concat(declarations, "\n}");
171
+ var styleEl = document.createElement('style');
172
+ styleEl.id = existingId;
173
+ styleEl.textContent = css;
174
+ document.head.appendChild(styleEl);
175
+ }
package/dist/index.js ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "THEMES", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _ThemeContext.THEMES;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "ThemeNavBar", {
13
+ enumerable: true,
14
+ get: function get() {
15
+ return _ThemeNavBar.ThemeNavBar;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "ThemeProvider", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _ThemeContext.ThemeProvider;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "ThemeSelector", {
25
+ enumerable: true,
26
+ get: function get() {
27
+ return _ThemeSelector.ThemeSelector;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "registerTheme", {
31
+ enumerable: true,
32
+ get: function get() {
33
+ return _ThemeContext.registerTheme;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "useTheme", {
37
+ enumerable: true,
38
+ get: function get() {
39
+ return _ThemeContext.useTheme;
40
+ }
41
+ });
42
+ var _ThemeContext = require("./context/ThemeContext.js");
43
+ var _ThemeSelector = require("./components/ThemeSelector.js");
44
+ var _ThemeNavBar = require("./components/ThemeNavBar.js");
@@ -0,0 +1,278 @@
1
+ /*
2
+ * ============================================================
3
+ * themes.css — Evolution Theme Engine
4
+ * ============================================================
5
+ * This file contains:
6
+ * 1. All built-in theme variable blocks.
7
+ * 2. Base html/body reset.
8
+ * 3. Component styles for .etn-navbar and .etn-theme-selector.
9
+ *
10
+ * HOW THEMES WORK
11
+ * ───────────────
12
+ * Each theme is a CSS block that overrides the CSS custom
13
+ * properties declared in :root. The active theme is selected
14
+ * by setting a `data-theme` attribute on the <html> element:
15
+ *
16
+ * document.documentElement.setAttribute('data-theme', 'dark');
17
+ *
18
+ * ThemeProvider does this automatically whenever the theme changes.
19
+ *
20
+ * WHY :root MUST COME FIRST
21
+ * ─────────────────────────
22
+ * Both `:root` and `[data-theme="..."]` selectors have equal
23
+ * CSS specificity (0,1,0). When both selectors match the same
24
+ * element, the one declared LATER in the file wins. Therefore
25
+ * :root (the Light/default theme) MUST appear before all
26
+ * [data-theme] blocks so that any active theme can override it.
27
+ *
28
+ * CSS VARIABLE REFERENCE
29
+ * ──────────────────────
30
+ * --color-bg Page / app background
31
+ * --color-text Primary body text
32
+ * --color-card-bg Card / panel surface background
33
+ * --color-card-border Card / panel border colour
34
+ * --color-btn-dark-bg Background for "dark" style buttons
35
+ * --color-btn-dark-text Text on "dark" style buttons
36
+ * --color-btn-light-bg Background for "light" style buttons
37
+ * --color-btn-light-text Text on "light" style buttons
38
+ * --color-divider Horizontal rules / separators
39
+ * --color-primary Primary accent / brand colour
40
+ * --color-on-primary Text drawn on top of --color-primary
41
+ * --color-link Hyperlink colour
42
+ * --color-hover-bg Subtle hover-state background tint
43
+ * --color-code-bg Code block background
44
+ * --color-code-text Code block text colour
45
+ *
46
+ * ADDING A CUSTOM THEME (CSS approach)
47
+ * ─────────────────────────────────────
48
+ * Copy the block below, change the selector to your theme name,
49
+ * and update the variable values. Then add the name to your
50
+ * themes object and pass it to <ThemeSelector>.
51
+ *
52
+ * [data-theme="ocean"] {
53
+ * --color-bg: #0a1628;
54
+ * --color-text: #e0f0ff;
55
+ * --color-primary: #00b4d8;
56
+ * ... etc.
57
+ * }
58
+ *
59
+ * Alternatively, use registerTheme() from ThemeContext.jsx to
60
+ * inject a theme at runtime without touching this file at all.
61
+ * ============================================================
62
+ */
63
+
64
+
65
+ /* ============================================================
66
+ * 1. BUILT-IN THEMES
67
+ * ============================================================ */
68
+
69
+ /* --- Light Theme (default) ---
70
+ * :root MUST appear first in this file so that every
71
+ * [data-theme] block below can override it.
72
+ */
73
+ :root {
74
+ --color-bg: #f8f9fa;
75
+ --color-text: #212529;
76
+ --color-card-bg: #ffffff;
77
+ --color-card-border: #dee2e6;
78
+ --color-btn-dark-bg: #f8f9fa;
79
+ --color-btn-dark-text: #212529;
80
+ --color-btn-light-bg: #ffffff;
81
+ --color-btn-light-text: #212529;
82
+ --color-divider: #dee2e6;
83
+
84
+ --color-primary: #1976d2;
85
+ --color-on-primary: #ffffff;
86
+ --color-link: #a435f0;
87
+ --color-hover-bg: rgba(25, 118, 210, 0.08);
88
+ --color-code-bg: #282c34;
89
+ --color-code-text: #ffffff;
90
+ }
91
+
92
+ /* --- Dark Theme --- */
93
+ [data-theme="dark"] {
94
+ --color-bg: #212529;
95
+ --color-text: #f8f9fa;
96
+ --color-card-bg: #343a40;
97
+ --color-card-border: #444444;
98
+ --color-btn-dark-bg: #f8f9fa;
99
+ --color-btn-dark-text: #212529;
100
+ --color-btn-light-bg: #343a40;
101
+ --color-btn-light-text: #f8f9fa;
102
+ --color-divider: #444444;
103
+
104
+ --color-primary: #90caf9;
105
+ --color-on-primary: #212529;
106
+ --color-link: #a435f0;
107
+ --color-hover-bg: rgba(144, 202, 249, 0.08);
108
+ --color-code-bg: #232b36;
109
+ --color-code-text: #f8f9fa;
110
+ }
111
+
112
+ /* --- Forest Theme ---
113
+ * A comforting deep-green palette with sage text and
114
+ * a natural green primary accent.
115
+ */
116
+ [data-theme="forest"] {
117
+ --color-bg: #1b2e22;
118
+ --color-text: #cde8d4;
119
+ --color-card-bg: #243c2b;
120
+ --color-card-border: #3a5c44;
121
+ --color-btn-dark-bg: #3a5c44;
122
+ --color-btn-dark-text: #cde8d4;
123
+ --color-btn-light-bg: #243c2b;
124
+ --color-btn-light-text: #cde8d4;
125
+ --color-divider: #3a5c44;
126
+
127
+ --color-primary: #4caf70;
128
+ --color-on-primary: #1b2e22;
129
+ --color-link: #7dd8a0;
130
+ --color-hover-bg: rgba(76, 175, 112, 0.12);
131
+ --color-code-bg: #111e17;
132
+ --color-code-text: #cde8d4;
133
+ }
134
+
135
+ /* --- Tron Theme ---
136
+ * Inspired by the Tron: Legacy aesthetic — dark navy background
137
+ * with electric cyan text and sky-blue accents.
138
+ */
139
+ [data-theme="tron"] {
140
+ --color-bg: #0f172a;
141
+ --color-text: #67e8f9;
142
+ --color-card-bg: #1e293b;
143
+ --color-card-border: #67e8f9;
144
+ --color-btn-dark-bg: #0ea5e9;
145
+ --color-btn-dark-text: #0f172a;
146
+ --color-btn-light-bg: #1e293b;
147
+ --color-btn-light-text: #67e8f9;
148
+ --color-divider: #0ea5e9;
149
+
150
+ --color-primary: #0ea5e9;
151
+ --color-on-primary: #0f172a;
152
+ --color-link: #67e8f9;
153
+ --color-hover-bg: rgba(14, 165, 233, 0.08);
154
+ --color-code-bg: #232b36;
155
+ --color-code-text: #67e8f9;
156
+ }
157
+
158
+ /* --- Midnight Theme ---
159
+ * A deep night-sky palette: near-black background, muted
160
+ * blue-grey text, teal (#5ce1b5) primary accent, and
161
+ * light-blue (#8bd4ff) link colour.
162
+ */
163
+ [data-theme="midnight"] {
164
+ --color-bg: #0b1016;
165
+ --color-text: #e7edf2;
166
+ --color-card-bg: #131c28;
167
+ --color-card-border: #263141;
168
+ --color-btn-dark-bg: #263141;
169
+ --color-btn-dark-text: #e7edf2;
170
+ --color-btn-light-bg: #0f151e;
171
+ --color-btn-light-text: #e7edf2;
172
+ --color-divider: #263141;
173
+
174
+ --color-primary: #5ce1b5;
175
+ --color-on-primary: #0b1016;
176
+ --color-link: #8bd4ff;
177
+ --color-hover-bg: rgba(92, 225, 181, 0.12);
178
+ --color-code-bg: #0f151e;
179
+ --color-code-text: #e7edf2;
180
+ }
181
+
182
+
183
+ /* ============================================================
184
+ * 2. BASE RESET
185
+ * Sets the theme background on <html> to prevent a flash of
186
+ * white before React mounts and ThemeProvider runs.
187
+ * ============================================================ */
188
+
189
+ html {
190
+ background-color: var(--color-bg);
191
+ color: var(--color-text);
192
+ transition: background-color 0.3s ease, color 0.3s ease;
193
+ }
194
+
195
+
196
+ /* ============================================================
197
+ * 3. COMPONENT STYLES
198
+ * All classes are prefixed with 'etn-' (Evolution Theme eNgine)
199
+ * to avoid collisions with the consumer application's CSS.
200
+ * ============================================================ */
201
+
202
+ /* --- ThemeNavBar (.etn-navbar) --- */
203
+ .etn-navbar {
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: space-between;
207
+ padding: 0 24px;
208
+ height: 56px;
209
+ background-color: var(--color-card-bg);
210
+ border-bottom: 1px solid var(--color-card-border);
211
+ gap: 16px;
212
+ /* Stays at the top when used as a sticky header */
213
+ position: sticky;
214
+ top: 0;
215
+ z-index: 1000;
216
+ }
217
+
218
+ .etn-navbar-brand {
219
+ font-size: 1.1rem;
220
+ font-weight: 700;
221
+ color: var(--color-text);
222
+ white-space: nowrap;
223
+ flex-shrink: 0;
224
+ }
225
+
226
+ .etn-navbar-links {
227
+ display: flex;
228
+ align-items: center;
229
+ gap: 20px;
230
+ list-style: none;
231
+ margin: 0;
232
+ padding: 0;
233
+ flex: 1;
234
+ }
235
+
236
+ .etn-navbar-link {
237
+ color: var(--color-text);
238
+ text-decoration: none;
239
+ font-size: 0.95rem;
240
+ opacity: 0.85;
241
+ transition: opacity 0.15s ease, color 0.15s ease;
242
+ }
243
+
244
+ .etn-navbar-link:hover {
245
+ opacity: 1;
246
+ color: var(--color-primary);
247
+ }
248
+
249
+ /* --- ThemeSelector (.etn-theme-selector) --- */
250
+ .etn-theme-selector {
251
+ padding: 6px 10px;
252
+ background-color: var(--color-card-bg);
253
+ color: var(--color-text);
254
+ border: 1px solid var(--color-card-border);
255
+ border-radius: 4px;
256
+ font-size: 0.875rem;
257
+ cursor: pointer;
258
+ /* Prevent the select from shrinking inside a flex navbar */
259
+ flex-shrink: 0;
260
+ transition: border-color 0.15s ease;
261
+ }
262
+
263
+ .etn-theme-selector:hover {
264
+ border-color: var(--color-primary);
265
+ }
266
+
267
+ .etn-theme-selector:focus {
268
+ outline: none;
269
+ border-color: var(--color-primary);
270
+ box-shadow: 0 0 0 3px var(--color-hover-bg);
271
+ }
272
+
273
+ /* Responsive: hide nav links on small screens */
274
+ @media (max-width: 600px) {
275
+ .etn-navbar-links {
276
+ display: none;
277
+ }
278
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@evolution-james/evolution-theme-engine",
3
+ "version": "1.0.0",
4
+ "description": "A plug-and-play React theme engine with CSS variable-based theming, localStorage persistence, and optional navbar component.",
5
+ "keywords": [
6
+ "react",
7
+ "theme",
8
+ "dark-mode",
9
+ "css-variables",
10
+ "theme-engine",
11
+ "evolution"
12
+ ],
13
+ "author": {
14
+ "name": "James Evolution (Evolution Coding Academy)",
15
+ "url": "https://github.com/james-evolution"
16
+ },
17
+ "license": "SEE LICENSE.txt",
18
+ "//": "BUILD NOTES: Source files are JSX (src/). The build script (build.js) uses Babel to pre-compile them into plain CommonJS JS (dist/) so consumers never need to transpile this package themselves. @babel/preset-react is configured with { runtime: 'automatic' } which uses the new JSX transform — it imports from react/jsx-runtime automatically instead of requiring React to be in scope (the classic runtime would inject React.createElement() calls that fail if React is not globally imported). @babel/preset-env compiles ESM imports/exports and modern syntax to CommonJS-compatible output. The 'exports' field below takes strict precedence over 'main' in webpack 5 and Node 12+. Without it, npm link + webpack symlink resolution can bypass 'main' and resolve to raw JSX source files the consumer's bundler cannot parse.",
19
+ "peerDependencies": {
20
+ "react": ">=17.0.0",
21
+ "react-dom": ">=17.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "react": "^18.0.0",
25
+ "react-dom": "^18.0.0",
26
+ "@babel/cli": "^7.0.0",
27
+ "@babel/core": "^7.0.0",
28
+ "@babel/preset-env": "^7.0.0",
29
+ "@babel/preset-react": "^7.0.0"
30
+ },
31
+ "files": [
32
+ "src/",
33
+ "dist/"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/james-evolution/evolution-theme-engine.git"
38
+ },
39
+ "main": "dist/index.js",
40
+ "exports": {
41
+ ".": "./dist/index.js"
42
+ },
43
+ "scripts": {
44
+ "build": "node build.js"
45
+ }
46
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+ * ============================================================
3
+ * ThemeNavBar.jsx — Evolution Theme Engine
4
+ * ============================================================
5
+ * An optional, barebones navigation bar with the ThemeSelector
6
+ * component already rendered inside it.
7
+ *
8
+ * This is the "batteries included" option — import ThemeNavBar
9
+ * if you want a ready-made header without wiring up ThemeSelector
10
+ * yourself. Under the hood it simply renders <ThemeSelector />,
11
+ * so everything still flows through ThemeContext.
12
+ *
13
+ * The navbar is intentionally minimal and styled purely via
14
+ * CSS variables (no Bootstrap, no third-party UI libraries).
15
+ * Customize it by passing props or overriding .etn-navbar CSS
16
+ * classes in your own stylesheet.
17
+ *
18
+ * Props:
19
+ * title (string) — Brand/title text shown on the
20
+ * left side of the navbar.
21
+ * Defaults to 'My App'.
22
+ * links (Array<object>) — Navigation links rendered to
23
+ * the right of the title. Each
24
+ * entry: { label, href }.
25
+ * Defaults to a few placeholder
26
+ * links.
27
+ * themes (object) — Forwarded to <ThemeSelector>.
28
+ * Defaults to all 5 built-in themes.
29
+ * className (string) — Extra class(es) added to the
30
+ * root <nav> element alongside
31
+ * 'etn-navbar'.
32
+ * style (object) — Inline styles for the root <nav>.
33
+ *
34
+ * Usage:
35
+ * import { ThemeNavBar } from 'evolution-theme-engine';
36
+ *
37
+ * <ThemeNavBar
38
+ * title="My Cool App"
39
+ * links={[
40
+ * { label: 'Home', href: '/' },
41
+ * { label: 'About', href: '/about' },
42
+ * ]}
43
+ * />
44
+ * ============================================================
45
+ */
46
+
47
+ import { ThemeSelector } from './ThemeSelector.jsx';
48
+ import '../styles/themes.css';
49
+
50
+ const DEFAULT_LINKS = [
51
+ { label: 'Home', href: '#' },
52
+ { label: 'About', href: '#' },
53
+ { label: 'Docs', href: '#' },
54
+ ];
55
+
56
+ export function ThemeNavBar({
57
+ title = 'My App',
58
+ links = DEFAULT_LINKS,
59
+ themes,
60
+ className = '',
61
+ style = {},
62
+ }) {
63
+ return (
64
+ <nav
65
+ className={`etn-navbar${className ? ` ${className}` : ''}`}
66
+ style={style}
67
+ >
68
+ {/* Left side: brand/title */}
69
+ <span className="etn-navbar-brand">{title}</span>
70
+
71
+ {/* Center: navigation links */}
72
+ <ul className="etn-navbar-links">
73
+ {links.map(({ label, href }) => (
74
+ <li key={label}>
75
+ <a href={href} className="etn-navbar-link">
76
+ {label}
77
+ </a>
78
+ </li>
79
+ ))}
80
+ </ul>
81
+
82
+ {/* Right side: ThemeSelector dropdown */}
83
+ <ThemeSelector themes={themes} />
84
+ </nav>
85
+ );
86
+ }
@@ -0,0 +1,68 @@
1
+ /*
2
+ * ============================================================
3
+ * ThemeSelector.jsx — Evolution Theme Engine
4
+ * ============================================================
5
+ * A standalone, dependency-free <select> dropdown that lets
6
+ * users switch between themes.
7
+ *
8
+ * This component has NO required props — it reads the current
9
+ * theme and setter from ThemeContext via useTheme(). All you
10
+ * need to do is render it anywhere inside a <ThemeProvider>.
11
+ *
12
+ * Props:
13
+ * themes (object) — Map of { label: themeKey } entries
14
+ * displayed in the dropdown.
15
+ * Defaults to all 5 built-in themes.
16
+ * className (string) — Extra CSS class(es) added to the
17
+ * <select> element alongside the
18
+ * default 'etn-theme-selector' class.
19
+ * style (object) — Inline styles applied to the select.
20
+ *
21
+ * Usage:
22
+ * import { ThemeSelector } from 'evolution-theme-engine';
23
+ *
24
+ * // Standalone — render anywhere inside ThemeProvider:
25
+ * <ThemeSelector />
26
+ *
27
+ * // With custom theme list:
28
+ * <ThemeSelector
29
+ * themes={{ 'Light': 'light', 'Dark': 'dark', 'Ocean': 'ocean' }}
30
+ * />
31
+ * ============================================================
32
+ */
33
+
34
+ import { useTheme } from '../context/ThemeContext.jsx';
35
+ import '../styles/themes.css';
36
+
37
+ /*
38
+ * DEFAULT_THEMES is what shows up in the dropdown when no
39
+ * `themes` prop is passed. Keys are display labels; values
40
+ * are the data-theme attribute strings defined in themes.css.
41
+ */
42
+ const DEFAULT_THEMES = {
43
+ 'Light Theme': 'light',
44
+ 'Dark Theme': 'dark',
45
+ 'Forest': 'forest',
46
+ 'Tron': 'tron',
47
+ 'Midnight': 'midnight',
48
+ };
49
+
50
+ export function ThemeSelector({ themes = DEFAULT_THEMES, className = '', style = {} }) {
51
+ const { theme, setTheme } = useTheme();
52
+
53
+ return (
54
+ <select
55
+ className={`etn-theme-selector${className ? ` ${className}` : ''}`}
56
+ value={theme}
57
+ onChange={(e) => setTheme(e.target.value)}
58
+ aria-label="Select theme"
59
+ style={style}
60
+ >
61
+ {Object.entries(themes).map(([label, value]) => (
62
+ <option key={value} value={value}>
63
+ {label}
64
+ </option>
65
+ ))}
66
+ </select>
67
+ );
68
+ }