juxscript 1.0.18 → 1.0.20

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 (44) hide show
  1. package/lib/components/alert.ts +124 -128
  2. package/lib/components/areachart.ts +169 -287
  3. package/lib/components/areachartsmooth.ts +2 -2
  4. package/lib/components/badge.ts +63 -72
  5. package/lib/components/barchart.ts +120 -48
  6. package/lib/components/button.ts +99 -101
  7. package/lib/components/card.ts +97 -121
  8. package/lib/components/chart-types.ts +159 -0
  9. package/lib/components/chart-utils.ts +160 -0
  10. package/lib/components/chart.ts +628 -48
  11. package/lib/components/checkbox.ts +137 -51
  12. package/lib/components/code.ts +89 -75
  13. package/lib/components/container.ts +1 -1
  14. package/lib/components/datepicker.ts +93 -78
  15. package/lib/components/dialog.ts +163 -130
  16. package/lib/components/divider.ts +111 -193
  17. package/lib/components/docs-data.json +711 -264
  18. package/lib/components/doughnutchart.ts +125 -57
  19. package/lib/components/dropdown.ts +172 -85
  20. package/lib/components/element.ts +66 -61
  21. package/lib/components/fileupload.ts +142 -171
  22. package/lib/components/heading.ts +64 -21
  23. package/lib/components/hero.ts +109 -34
  24. package/lib/components/icon.ts +247 -0
  25. package/lib/components/icons.ts +174 -0
  26. package/lib/components/include.ts +77 -2
  27. package/lib/components/input.ts +174 -125
  28. package/lib/components/list.ts +120 -79
  29. package/lib/components/menu.ts +97 -2
  30. package/lib/components/modal.ts +144 -63
  31. package/lib/components/nav.ts +153 -52
  32. package/lib/components/paragraph.ts +78 -28
  33. package/lib/components/progress.ts +83 -107
  34. package/lib/components/radio.ts +151 -52
  35. package/lib/components/select.ts +110 -102
  36. package/lib/components/sidebar.ts +148 -105
  37. package/lib/components/switch.ts +124 -125
  38. package/lib/components/table.ts +214 -137
  39. package/lib/components/tabs.ts +194 -113
  40. package/lib/components/theme-toggle.ts +38 -7
  41. package/lib/components/tooltip.ts +207 -47
  42. package/lib/jux.ts +24 -5
  43. package/lib/reactivity/state.ts +13 -299
  44. package/package.json +1 -2
@@ -1,57 +1,41 @@
1
1
  import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
2
3
 
3
- /**
4
- * Sidebar component options
5
- */
6
4
  export interface SidebarOptions {
7
- title?: string;
8
- width?: string;
9
5
  position?: 'left' | 'right';
6
+ width?: string;
10
7
  collapsible?: boolean;
11
8
  collapsed?: boolean;
12
9
  style?: string;
13
10
  class?: string;
14
11
  }
15
12
 
16
- /**
17
- * Sidebar component state
18
- */
19
13
  type SidebarState = {
20
- title: string;
21
- width: string | null;
22
- position: string;
14
+ position: 'left' | 'right';
15
+ width: string;
23
16
  collapsible: boolean;
24
17
  collapsed: boolean;
25
18
  style: string;
26
19
  class: string;
27
20
  };
28
21
 
29
- /**
30
- * Sidebar component
31
- *
32
- * Usage:
33
- * const sidebar = jux.sidebar('mySidebar', {
34
- * title: 'Navigation',
35
- * width: '250px',
36
- * position: 'left'
37
- * });
38
- * sidebar.render('#appsidebar');
39
- */
40
22
  export class Sidebar {
41
23
  state: SidebarState;
42
24
  container: HTMLElement | null = null;
43
25
  _id: string;
44
26
  id: string;
45
27
 
28
+ private _bindings: Array<{ event: string, handler: Function }> = [];
29
+ private _syncBindings: Array<{ property: string, stateObj: State<any>, transform?: Function }> = [];
30
+
46
31
  constructor(id: string, options: SidebarOptions = {}) {
47
32
  this._id = id;
48
33
  this.id = id;
49
34
 
50
35
  this.state = {
51
- title: options.title ?? '',
52
- width: options.width ?? null, // No default width - let CSS handle it
53
36
  position: options.position ?? 'left',
54
- collapsible: options.collapsible ?? false,
37
+ width: options.width ?? null,
38
+ collapsible: options.collapsible ?? true,
55
39
  collapsed: options.collapsed ?? false,
56
40
  style: options.style ?? '',
57
41
  class: options.class ?? ''
@@ -62,10 +46,6 @@ export class Sidebar {
62
46
  * Fluent API
63
47
  * ------------------------- */
64
48
 
65
- title(value: string): this {
66
- this.state.title = value;
67
- return this;
68
- }
69
49
 
70
50
  width(value: string): this {
71
51
  this.state.width = value;
@@ -84,6 +64,7 @@ export class Sidebar {
84
64
 
85
65
  collapsed(value: boolean): this {
86
66
  this.state.collapsed = value;
67
+ this._updateCollapsedState();
87
68
  return this;
88
69
  }
89
70
 
@@ -97,49 +78,34 @@ export class Sidebar {
97
78
  return this;
98
79
  }
99
80
 
100
- toggle(): this {
101
- this.state.collapsed = !this.state.collapsed;
102
- this._updateDOM();
81
+ bind(event: string, handler: Function): this {
82
+ this._bindings.push({ event, handler });
103
83
  return this;
104
84
  }
105
85
 
106
- /* -------------------------
107
- * Helpers
108
- * ------------------------- */
109
-
110
- private _updateDOM(): void {
111
- if (!this.container) return;
112
-
113
- const sidebar = this.container.querySelector(`#${this._id}`);
114
- if (!sidebar || !(sidebar instanceof HTMLElement)) return;
115
-
116
- const { width, collapsed } = this.state;
117
-
118
- if (collapsed) {
119
- sidebar.classList.add('jux-sidebar-collapsed');
120
- if (width) {
121
- sidebar.style.width = '0';
122
- }
123
- } else {
124
- sidebar.classList.remove('jux-sidebar-collapsed');
125
- if (width) {
126
- sidebar.style.width = width;
127
- }
86
+ sync(property: string, stateObj: State<any>, transform?: Function): this {
87
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
88
+ throw new Error(`Sidebar.sync: Expected a State object for property "${property}"`);
128
89
  }
90
+ this._syncBindings.push({ property, stateObj, transform });
91
+ return this;
92
+ }
129
93
 
130
- const toggleBtn = sidebar.querySelector('.jux-sidebar-toggle');
131
- if (toggleBtn) {
132
- toggleBtn.textContent = collapsed ? '>' : '<';
133
- }
94
+ toggle(): void {
95
+ this.state.collapsed = !this.state.collapsed;
96
+ this._updateCollapsedState();
134
97
  }
135
98
 
136
- /* -------------------------
137
- * Render
138
- * ------------------------- */
99
+ private _updateCollapsedState(): void {
100
+ const sidebar = document.getElementById(this._id);
101
+ if (sidebar) {
102
+ sidebar.classList.toggle('jux-sidebar-collapsed', this.state.collapsed);
103
+ }
104
+ }
139
105
 
140
106
  render(targetId?: string): this {
107
+ // === 1. SETUP: Get container ===
141
108
  let container: HTMLElement;
142
-
143
109
  if (targetId) {
144
110
  const target = document.querySelector(targetId);
145
111
  if (!target || !(target instanceof HTMLElement)) {
@@ -149,78 +115,155 @@ export class Sidebar {
149
115
  } else {
150
116
  container = getOrCreateContainer(this._id);
151
117
  }
152
-
153
118
  this.container = container;
154
- const { title, width, position, collapsible, collapsed, style, class: className } = this.state;
155
119
 
120
+ // === 2. PREPARE: Destructure state ===
121
+ const { position, width, collapsible, collapsed, style, class: className } = this.state;
122
+
123
+ // === 3. BUILD: Create DOM elements ===
156
124
  const sidebar = document.createElement('aside');
157
125
  sidebar.className = `jux-sidebar jux-sidebar-${position}`;
158
126
  sidebar.id = this._id;
159
-
160
- // Only set width if explicitly provided
161
- if (width) {
162
- sidebar.style.width = collapsed ? '0' : width;
163
- }
164
-
165
- if (className) {
166
- sidebar.className += ` ${className}`;
167
- }
127
+ if (className) sidebar.className += ` ${className}`;
128
+ if (collapsed) sidebar.classList.add('jux-sidebar-collapsed');
168
129
 
169
- if (style) {
170
- sidebar.setAttribute('style', sidebar.getAttribute('style') + '; ' + style);
130
+ const sidebarStyle = `${width ? `width: ${collapsed ? '60px' : width};` : ''} ${style}`;
131
+ if (sidebarStyle.trim()) {
132
+ sidebar.setAttribute('style', sidebarStyle);
171
133
  }
172
134
 
173
- if (collapsed) {
174
- sidebar.classList.add('jux-sidebar-collapsed');
175
- }
176
-
177
- if (title) {
178
- const titleEl = document.createElement('div');
179
- titleEl.className = 'jux-sidebar-title';
180
- titleEl.textContent = title;
181
- sidebar.appendChild(titleEl);
182
- }
183
-
184
- const content = document.createElement('div');
185
- content.className = 'jux-sidebar-content';
186
- sidebar.appendChild(content);
187
-
188
- // Event binding - toggle button
189
135
  if (collapsible) {
190
136
  const toggleBtn = document.createElement('button');
191
137
  toggleBtn.className = 'jux-sidebar-toggle';
192
- toggleBtn.textContent = collapsed ? '>' : '<';
138
+ toggleBtn.innerHTML = collapsed
139
+ ? (position === 'left' ? '▶' : '◀')
140
+ : (position === 'left' ? '◀' : '▶');
141
+ toggleBtn.setAttribute('aria-label', 'Toggle sidebar');
142
+ toggleBtn.setAttribute('title', collapsed ? 'Expand sidebar' : 'Collapse sidebar');
193
143
 
194
144
  toggleBtn.addEventListener('click', () => {
195
145
  this.toggle();
146
+
147
+ // Update button icon and title
148
+ const isCollapsed = this.state.collapsed;
149
+ toggleBtn.innerHTML = isCollapsed
150
+ ? (position === 'left' ? '▶' : '◀')
151
+ : (position === 'left' ? '◀' : '▶');
152
+ toggleBtn.setAttribute('title', isCollapsed ? 'Expand sidebar' : 'Collapse sidebar');
153
+
154
+ // Animate width change
155
+ if (width) {
156
+ sidebar.style.width = isCollapsed ? '60px' : width;
157
+ }
196
158
  });
197
159
 
198
160
  sidebar.appendChild(toggleBtn);
199
161
  }
200
162
 
163
+ // === 4. WIRE: Add event listeners ===
164
+
165
+ this._bindings.forEach(({ event, handler }) => {
166
+ sidebar.addEventListener(event, handler as EventListener);
167
+ });
168
+
169
+ this._syncBindings.forEach(({ property, stateObj, transform }) => {
170
+ stateObj.subscribe((val: any) => {
171
+ const transformed = transform ? transform(val) : val;
172
+
173
+ if (property === 'collapsed') {
174
+ const isCollapsed = Boolean(transformed);
175
+ this.state.collapsed = isCollapsed;
176
+ this._updateCollapsedState();
177
+ }
178
+ });
179
+ });
180
+
181
+ // === 5. RENDER: Append to DOM ===
201
182
  container.appendChild(sidebar);
183
+ this._injectDefaultStyles();
184
+
202
185
  return this;
203
186
  }
204
187
 
205
- /**
206
- * Render to another Jux component's container
207
- */
208
- renderTo(juxComponent: any): this {
209
- if (!juxComponent || typeof juxComponent !== 'object') {
210
- throw new Error('Sidebar.renderTo: Invalid component - not an object');
211
- }
188
+ private _injectDefaultStyles(): void {
189
+ const styleId = 'jux-sidebar-styles';
190
+ if (document.getElementById(styleId)) return;
191
+
192
+ const style = document.createElement('style');
193
+ style.id = styleId;
194
+ style.textContent = `
195
+ .jux-sidebar {
196
+ position: relative;
197
+ height: 100vh;
198
+ background: #f9fafb;
199
+ border-right: 1px solid #e5e7eb;
200
+ transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
201
+ overflow: hidden;
202
+ }
203
+
204
+ .jux-sidebar-right {
205
+ border-right: none;
206
+ border-left: 1px solid #e5e7eb;
207
+ }
208
+
209
+ .jux-sidebar-toggle {
210
+ position: absolute;
211
+ bottom: 20px;
212
+ right: 20px;
213
+ width: 32px;
214
+ height: 32px;
215
+ border: none;
216
+ background: #3b82f6;
217
+ color: white;
218
+ border-radius: 6px;
219
+ cursor: pointer;
220
+ font-size: 14px;
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: center;
224
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
225
+ z-index: 100;
226
+ box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
227
+ }
228
+
229
+ .jux-sidebar-toggle:hover {
230
+ background: #2563eb;
231
+ transform: scale(1.05);
232
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
233
+ }
234
+
235
+ .jux-sidebar-toggle:active {
236
+ transform: scale(0.95);
237
+ }
238
+
239
+ .jux-sidebar-collapsed {
240
+ width: 60px !important;
241
+ }
242
+
243
+ /* Smooth fade for content */
244
+ .jux-sidebar-collapsed > *:not(.jux-sidebar-toggle) {
245
+ opacity: 0;
246
+ pointer-events: none;
247
+ transition: opacity 0.2s ease-out;
248
+ }
249
+
250
+ .jux-sidebar > *:not(.jux-sidebar-toggle) {
251
+ opacity: 1;
252
+ pointer-events: auto;
253
+ transition: opacity 0.3s ease-in 0.1s;
254
+ }
255
+ `;
256
+ document.head.appendChild(style);
257
+ }
212
258
 
213
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
214
- throw new Error('Sidebar.renderTo: Invalid component - missing _id (not a Jux component)');
259
+ renderTo(juxComponent: any): this {
260
+ if (!juxComponent?._id) {
261
+ throw new Error('Sidebar.renderTo: Invalid component');
215
262
  }
216
-
217
263
  return this.render(`#${juxComponent._id}`);
218
264
  }
219
265
  }
220
266
 
221
- /**
222
- * Factory helper
223
- */
224
267
  export function sidebar(id: string, options: SidebarOptions = {}): Sidebar {
225
268
  return new Sidebar(id, options);
226
269
  }