juxscript 1.0.2 → 1.0.4

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.
Files changed (71) hide show
  1. package/README.md +37 -92
  2. package/bin/cli.js +57 -56
  3. package/lib/components/alert.ts +240 -0
  4. package/lib/components/app.ts +216 -82
  5. package/lib/components/badge.ts +164 -0
  6. package/lib/components/button.ts +188 -53
  7. package/lib/components/card.ts +75 -61
  8. package/lib/components/chart.ts +17 -15
  9. package/lib/components/checkbox.ts +228 -0
  10. package/lib/components/code.ts +66 -152
  11. package/lib/components/container.ts +104 -208
  12. package/lib/components/data.ts +1 -3
  13. package/lib/components/datepicker.ts +226 -0
  14. package/lib/components/dialog.ts +258 -0
  15. package/lib/components/docs-data.json +1697 -388
  16. package/lib/components/dropdown.ts +244 -0
  17. package/lib/components/element.ts +271 -0
  18. package/lib/components/fileupload.ts +319 -0
  19. package/lib/components/footer.ts +37 -18
  20. package/lib/components/header.ts +53 -33
  21. package/lib/components/heading.ts +119 -0
  22. package/lib/components/helpers.ts +34 -0
  23. package/lib/components/hero.ts +57 -31
  24. package/lib/components/include.ts +292 -0
  25. package/lib/components/input.ts +166 -78
  26. package/lib/components/layout.ts +144 -18
  27. package/lib/components/list.ts +83 -74
  28. package/lib/components/loading.ts +263 -0
  29. package/lib/components/main.ts +43 -17
  30. package/lib/components/menu.ts +108 -24
  31. package/lib/components/modal.ts +50 -21
  32. package/lib/components/nav.ts +60 -18
  33. package/lib/components/paragraph.ts +111 -0
  34. package/lib/components/progress.ts +276 -0
  35. package/lib/components/radio.ts +236 -0
  36. package/lib/components/req.ts +300 -0
  37. package/lib/components/script.ts +33 -74
  38. package/lib/components/select.ts +247 -0
  39. package/lib/components/sidebar.ts +86 -36
  40. package/lib/components/style.ts +47 -70
  41. package/lib/components/switch.ts +261 -0
  42. package/lib/components/table.ts +47 -24
  43. package/lib/components/tabs.ts +105 -63
  44. package/lib/components/theme-toggle.ts +361 -0
  45. package/lib/components/token-calculator.ts +380 -0
  46. package/lib/components/tooltip.ts +244 -0
  47. package/lib/components/view.ts +36 -20
  48. package/lib/components/write.ts +284 -0
  49. package/lib/globals.d.ts +21 -0
  50. package/lib/jux.ts +172 -68
  51. package/lib/presets/notion.css +521 -0
  52. package/lib/presets/notion.jux +27 -0
  53. package/lib/reactivity/state.ts +364 -0
  54. package/machinery/compiler.js +126 -38
  55. package/machinery/generators/html.js +2 -3
  56. package/machinery/server.js +2 -2
  57. package/package.json +29 -3
  58. package/lib/components/import.ts +0 -430
  59. package/lib/components/node.ts +0 -200
  60. package/lib/components/reactivity.js +0 -104
  61. package/lib/components/theme.ts +0 -97
  62. package/lib/layouts/notion.css +0 -258
  63. package/lib/styles/base-theme.css +0 -186
  64. package/lib/styles/dark-theme.css +0 -144
  65. package/lib/styles/light-theme.css +0 -144
  66. package/lib/styles/tokens/dark.css +0 -86
  67. package/lib/styles/tokens/light.css +0 -86
  68. package/lib/templates/index.juxt +0 -33
  69. package/lib/themes/dark.css +0 -86
  70. package/lib/themes/light.css +0 -86
  71. /package/lib/{styles → presets}/global.css +0 -0
@@ -0,0 +1,361 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { ErrorHandler } from './error-handler.js';
3
+
4
+ /**
5
+ * Theme configuration
6
+ */
7
+ export interface Theme {
8
+ id: string;
9
+ label: string;
10
+ icon?: string;
11
+ }
12
+
13
+ /**
14
+ * ThemeToggle component options
15
+ */
16
+ export interface ThemeToggleOptions {
17
+ themes?: Theme[];
18
+ defaultTheme?: string;
19
+ storageKey?: string;
20
+ showLabel?: boolean;
21
+ variant?: 'button' | 'dropdown' | 'cycle';
22
+ style?: string;
23
+ class?: string;
24
+ }
25
+
26
+ /**
27
+ * ThemeToggle component state
28
+ */
29
+ type ThemeToggleState = {
30
+ themes: Theme[];
31
+ currentTheme: string;
32
+ storageKey: string;
33
+ showLabel: boolean;
34
+ variant: string;
35
+ style: string;
36
+ class: string;
37
+ };
38
+
39
+ /**
40
+ * ThemeToggle component - Manage and switch between themes
41
+ *
42
+ * Usage:
43
+ * // Simple light/dark toggle
44
+ * jux.themeToggle('myToggle').render('#appheader-actions');
45
+ *
46
+ * // Custom themes
47
+ * jux.themeToggle('myToggle', {
48
+ * themes: [
49
+ * { id: 'light', label: 'Light', icon: '☀️' },
50
+ * { id: 'dark', label: 'Dark', icon: '🌙' },
51
+ * { id: 'auto', label: 'Auto', icon: '🌓' }
52
+ * ],
53
+ * variant: 'dropdown'
54
+ * }).render('#appheader-actions');
55
+ */
56
+ export class ThemeToggle {
57
+ state: ThemeToggleState;
58
+ container: HTMLElement | null = null;
59
+ _id: string;
60
+ id: string;
61
+
62
+ constructor(id: string, options: ThemeToggleOptions = {}) {
63
+ this._id = id;
64
+ this.id = id;
65
+
66
+ const defaultThemes: Theme[] = [
67
+ { id: 'light', label: 'Light', icon: '☀️' },
68
+ { id: 'dark', label: 'Dark', icon: '🌙' }
69
+ ];
70
+
71
+ this.state = {
72
+ themes: options.themes ?? defaultThemes,
73
+ currentTheme: options.defaultTheme ?? this.detectTheme(options.storageKey),
74
+ storageKey: options.storageKey ?? 'jux-theme',
75
+ showLabel: options.showLabel ?? false,
76
+ variant: options.variant ?? 'button',
77
+ style: options.style ?? '',
78
+ class: options.class ?? ''
79
+ };
80
+
81
+ // Apply theme on initialization
82
+ this.applyTheme(this.state.currentTheme);
83
+ }
84
+
85
+ /* -------------------------
86
+ * Theme Detection & Management
87
+ * ------------------------- */
88
+
89
+ /**
90
+ * Detect theme from localStorage or system preference
91
+ */
92
+ private detectTheme(storageKey = 'jux-theme'): string {
93
+ if (typeof window === 'undefined') return 'light';
94
+
95
+ // Check localStorage first
96
+ const stored = localStorage.getItem(storageKey);
97
+ if (stored) return stored;
98
+
99
+ // Check current document attribute
100
+ const current = document.body.getAttribute('data-theme');
101
+ if (current) return current;
102
+
103
+ // Check system preference
104
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
105
+ return 'dark';
106
+ }
107
+
108
+ return 'light';
109
+ }
110
+
111
+ /**
112
+ * Apply theme to document
113
+ */
114
+ private applyTheme(themeId: string): void {
115
+ if (typeof document === 'undefined') return;
116
+
117
+ try {
118
+ // Update data-theme attribute on body
119
+ document.body.setAttribute('data-theme', themeId);
120
+
121
+ // Also update on html element for broader CSS support
122
+ document.documentElement.setAttribute('data-theme', themeId);
123
+
124
+ // Store in localStorage
125
+ localStorage.setItem(this.state.storageKey, themeId);
126
+
127
+ // Dispatch custom event for other components to listen
128
+ window.dispatchEvent(new CustomEvent('themechange', {
129
+ detail: { theme: themeId }
130
+ }));
131
+
132
+ console.log(`🎨 Theme applied: ${themeId}`);
133
+ } catch (error: any) {
134
+ ErrorHandler.captureError({
135
+ component: 'ThemeToggle',
136
+ method: 'applyTheme',
137
+ message: `Failed to apply theme: ${error.message}`,
138
+ stack: error.stack,
139
+ timestamp: new Date(),
140
+ context: { themeId }
141
+ });
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Cycle to next theme
147
+ */
148
+ private cycleTheme(): void {
149
+ const currentIndex = this.state.themes.findIndex(t => t.id === this.state.currentTheme);
150
+ const nextIndex = (currentIndex + 1) % this.state.themes.length;
151
+ const nextTheme = this.state.themes[nextIndex];
152
+
153
+ this.setTheme(nextTheme.id);
154
+ }
155
+
156
+ /* -------------------------
157
+ * Public API
158
+ * ------------------------- */
159
+
160
+ /**
161
+ * Set current theme
162
+ */
163
+ setTheme(themeId: string): this {
164
+ const theme = this.state.themes.find(t => t.id === themeId);
165
+
166
+ if (!theme) {
167
+ console.warn(`Theme "${themeId}" not found in available themes`);
168
+ return this;
169
+ }
170
+
171
+ this.state.currentTheme = themeId;
172
+ this.applyTheme(themeId);
173
+ this._updateDOM();
174
+
175
+ return this;
176
+ }
177
+
178
+ /**
179
+ * Get current theme
180
+ */
181
+ getTheme(): string {
182
+ return this.state.currentTheme;
183
+ }
184
+
185
+ /**
186
+ * Add a theme
187
+ */
188
+ addTheme(theme: Theme): this {
189
+ if (!this.state.themes.find(t => t.id === theme.id)) {
190
+ this.state.themes.push(theme);
191
+ }
192
+ return this;
193
+ }
194
+
195
+ /* -------------------------
196
+ * Fluent API
197
+ * ------------------------- */
198
+
199
+ themes(value: Theme[]): this {
200
+ this.state.themes = value;
201
+ return this;
202
+ }
203
+
204
+ variant(value: 'button' | 'dropdown' | 'cycle'): this {
205
+ this.state.variant = value;
206
+ return this;
207
+ }
208
+
209
+ showLabel(value: boolean): this {
210
+ this.state.showLabel = value;
211
+ return this;
212
+ }
213
+
214
+ style(value: string): this {
215
+ this.state.style = value;
216
+ return this;
217
+ }
218
+
219
+ class(value: string): this {
220
+ this.state.class = value;
221
+ return this;
222
+ }
223
+
224
+ /* -------------------------
225
+ * DOM Update
226
+ * ------------------------- */
227
+
228
+ private _updateDOM(): void {
229
+ if (!this.container) return;
230
+
231
+ const toggle = this.container.querySelector(`#${this._id}`);
232
+ if (!toggle) return;
233
+
234
+ const currentTheme = this.state.themes.find(t => t.id === this.state.currentTheme);
235
+ if (!currentTheme) return;
236
+
237
+ // Update button variant
238
+ if (this.state.variant === 'button' || this.state.variant === 'cycle') {
239
+ const button = toggle.querySelector('button');
240
+ if (button) {
241
+ button.innerHTML = this.state.showLabel
242
+ ? `${currentTheme.icon || ''} ${currentTheme.label}`.trim()
243
+ : currentTheme.icon || currentTheme.label;
244
+ }
245
+ }
246
+
247
+ // Update dropdown variant
248
+ if (this.state.variant === 'dropdown') {
249
+ const select = toggle.querySelector('select') as HTMLSelectElement;
250
+ if (select) {
251
+ select.value = this.state.currentTheme;
252
+ }
253
+ }
254
+ }
255
+
256
+ /* -------------------------
257
+ * Render
258
+ * ------------------------- */
259
+
260
+ render(targetId?: string): this {
261
+ let container: HTMLElement;
262
+
263
+ if (targetId) {
264
+ const target = document.querySelector(targetId);
265
+ if (!target || !(target instanceof HTMLElement)) {
266
+ throw new Error(`ThemeToggle: Target element "${targetId}" not found`);
267
+ }
268
+ container = target;
269
+ } else {
270
+ container = getOrCreateContainer(this._id);
271
+ }
272
+
273
+ this.container = container;
274
+ const { themes, currentTheme, showLabel, variant, style, class: className } = this.state;
275
+
276
+ const wrapper = document.createElement('div');
277
+ wrapper.className = `jux-theme-toggle jux-theme-toggle-${variant}`;
278
+ wrapper.id = this._id;
279
+
280
+ if (className) {
281
+ wrapper.className += ` ${className}`;
282
+ }
283
+
284
+ if (style) {
285
+ wrapper.setAttribute('style', style);
286
+ }
287
+
288
+ const theme = themes.find(t => t.id === currentTheme);
289
+
290
+ // Render based on variant
291
+ if (variant === 'button' || variant === 'cycle') {
292
+ // Single button that cycles through themes
293
+ const button = document.createElement('button');
294
+ button.className = 'jux-theme-toggle-button';
295
+ button.type = 'button';
296
+ button.setAttribute('aria-label', 'Toggle theme');
297
+
298
+ button.innerHTML = showLabel
299
+ ? `${theme?.icon || ''} ${theme?.label || currentTheme}`.trim()
300
+ : theme?.icon || theme?.label || currentTheme;
301
+
302
+ button.addEventListener('click', () => {
303
+ this.cycleTheme();
304
+ });
305
+
306
+ wrapper.appendChild(button);
307
+
308
+ } else if (variant === 'dropdown') {
309
+ // Dropdown select with all themes
310
+ const select = document.createElement('select');
311
+ select.className = 'jux-theme-toggle-select';
312
+ select.setAttribute('aria-label', 'Select theme');
313
+
314
+ themes.forEach(t => {
315
+ const option = document.createElement('option');
316
+ option.value = t.id;
317
+ option.textContent = showLabel
318
+ ? `${t.icon || ''} ${t.label}`.trim()
319
+ : t.icon || t.label;
320
+
321
+ if (t.id === currentTheme) {
322
+ option.selected = true;
323
+ }
324
+
325
+ select.appendChild(option);
326
+ });
327
+
328
+ select.addEventListener('change', (e) => {
329
+ const target = e.target as HTMLSelectElement;
330
+ this.setTheme(target.value);
331
+ });
332
+
333
+ wrapper.appendChild(select);
334
+ }
335
+
336
+ container.appendChild(wrapper);
337
+ return this;
338
+ }
339
+
340
+ /**
341
+ * Render to another Jux component's container
342
+ */
343
+ renderTo(juxComponent: any): this {
344
+ if (!juxComponent || typeof juxComponent !== 'object') {
345
+ throw new Error('ThemeToggle.renderTo: Invalid component - not an object');
346
+ }
347
+
348
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
349
+ throw new Error('ThemeToggle.renderTo: Invalid component - missing _id (not a Jux component)');
350
+ }
351
+
352
+ return this.render(`#${juxComponent._id}`);
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Factory helper
358
+ */
359
+ export function themeToggle(id: string, options: ThemeToggleOptions = {}): ThemeToggle {
360
+ return new ThemeToggle(id, options);
361
+ }