juxscript 1.0.8 → 1.0.10
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 +12 -4
- package/bin/cli.js +66 -38
- package/lib/components/divider.ts +233 -0
- package/lib/components/docs-data.json +70 -1
- package/lib/components/hero1/hero1.ts +196 -0
- package/lib/components/hero1/index.js +4 -0
- package/lib/components/kpicard.ts +43 -8
- package/lib/components/sidebar.ts +13 -5
- package/lib/jux.ts +8 -4
- package/machinery/compiler.js +21 -4
- package/machinery/server.js +19 -16
- package/package.json +33 -51
- package/lib/presets/global.css +0 -1131
- package/lib/presets/notion.css +0 -521
- package/lib/presets/notion.jux +0 -27
- package/machinery/generators/css.js +0 -128
- package/machinery/generators/html.js +0 -107
package/README.md
CHANGED
|
@@ -7,25 +7,33 @@
|
|
|
7
7
|
+ Stay tuned! For now, you will see our roadmap here!
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
+
- [X] Router
|
|
11
|
+
- [ ] Cross Page Store.
|
|
12
|
+
- [ ] Distributable Bundle (Static Sites)
|
|
13
|
+
- [ ] Tree Shake/Efficiencies.
|
|
14
|
+
|
|
10
15
|
- [X] Layouts (100% done.)
|
|
16
|
+
- [ ] *Authoring Layout Pages* - `docs`
|
|
17
|
+
- [ ] *Authoring Application Pages* - `docs`
|
|
18
|
+
- [ ] *Authoring Custom Components* - `docs`
|
|
19
|
+
|
|
11
20
|
- [ ] Render Dependency Tree
|
|
12
21
|
> Idea here is, one element may be a predicate for another. Will need promises. **predicting problems with slow-loading components that depend on containers from other components. May to to separate concerns with container "building" vs. content addition OR use async processes (promises).
|
|
13
22
|
- [X] Reactivity (90% done.)
|
|
14
23
|
- [ ] Client Components (99% of what would be needed.)
|
|
15
|
-
- [
|
|
24
|
+
- [X] Charts
|
|
16
25
|
- [ ] Poor Intellisense support? Could be this issue.
|
|
17
26
|
- [ ] Api Wrapper
|
|
18
27
|
- [X] Params/Active State for Menu/Nav matching - built in.
|
|
19
28
|
- [ ] CDN Bundle (import CDN/'state', 'jux' from cdn.)
|
|
20
29
|
- [ ] Icon
|
|
21
|
-
|
|
30
|
+
|
|
22
31
|
- [ ] Quickstart Boilerplates (20% done,notion.jux)
|
|
23
32
|
- [ ] Mobile Nav
|
|
24
33
|
- [ ] `npx jux present notion|default` etc..
|
|
25
34
|
- [ ] Server side components (api and database)
|
|
26
35
|
- [ ] Quick deploy option
|
|
27
|
-
|
|
28
|
-
- [ ] Tree Shake/Efficiencies.
|
|
36
|
+
|
|
29
37
|
|
|
30
38
|
## *JUX* Authoring UI's in pure javascript.
|
|
31
39
|
|
package/bin/cli.js
CHANGED
|
@@ -183,33 +183,59 @@ async function buildProject(isServe = false) {
|
|
|
183
183
|
// Create structure
|
|
184
184
|
fs.mkdirSync(juxDir, { recursive: true });
|
|
185
185
|
|
|
186
|
-
//
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (fs.existsSync(templatePath)) {
|
|
191
|
-
fs.copyFileSync(templatePath, targetPath);
|
|
192
|
-
console.log('✅ Created jux/index.jux from template');
|
|
193
|
-
} else {
|
|
194
|
-
// Fallback if template doesn't exist
|
|
195
|
-
console.warn('⚠️ Template not found, creating basic index.jux');
|
|
196
|
-
const fallbackContent = `// Welcome to JUX!
|
|
197
|
-
import { jux } from '/lib/jux.js';
|
|
186
|
+
// Create index.jux with proper imports
|
|
187
|
+
const indexContent = `// Welcome to JUX!
|
|
188
|
+
import { jux } from 'juxscript';
|
|
198
189
|
|
|
199
|
-
|
|
200
|
-
|
|
190
|
+
// Apply a layout preset (optional)
|
|
191
|
+
// import 'juxscript/presets/notion.js';
|
|
201
192
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
193
|
+
// Create your app structure
|
|
194
|
+
jux.container('app-container')
|
|
195
|
+
.direction('column')
|
|
196
|
+
.gap(20)
|
|
197
|
+
.style('padding: 40px;')
|
|
198
|
+
.render('body');
|
|
205
199
|
|
|
206
|
-
jux.hero('
|
|
200
|
+
jux.hero('welcome-hero', {
|
|
207
201
|
title: 'Welcome to JUX',
|
|
208
202
|
subtitle: 'A JavaScript UX authorship platform'
|
|
209
|
-
}).render('#
|
|
203
|
+
}).render('#app-container');
|
|
204
|
+
|
|
205
|
+
jux.divider({}).render('#app-container');
|
|
206
|
+
|
|
207
|
+
jux.write(\`
|
|
208
|
+
<h2>Getting Started</h2>
|
|
209
|
+
<p>Edit <code>jux/index.jux</code> to build your app.</p>
|
|
210
|
+
<ul>
|
|
211
|
+
<li>Run <code>npx jux build</code> to compile</li>
|
|
212
|
+
<li>Run <code>npx jux serve</code> for dev mode</li>
|
|
213
|
+
<li>Serve <code>jux-dist/</code> from your backend</li>
|
|
214
|
+
</ul>
|
|
215
|
+
\`).render('#app-container');
|
|
210
216
|
`;
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
|
|
218
|
+
const targetPath = path.join(juxDir, 'index.jux');
|
|
219
|
+
fs.writeFileSync(targetPath, indexContent);
|
|
220
|
+
console.log('✅ Created jux/index.jux');
|
|
221
|
+
|
|
222
|
+
// Create package.json if it doesn't exist
|
|
223
|
+
const pkgPath = path.join(PATHS.projectRoot, 'package.json');
|
|
224
|
+
if (!fs.existsSync(pkgPath)) {
|
|
225
|
+
const pkgContent = {
|
|
226
|
+
"name": "my-jux-project",
|
|
227
|
+
"version": "1.0.0",
|
|
228
|
+
"type": "module",
|
|
229
|
+
"scripts": {
|
|
230
|
+
"build": "jux build",
|
|
231
|
+
"serve": "jux serve"
|
|
232
|
+
},
|
|
233
|
+
"dependencies": {
|
|
234
|
+
"juxscript": "^1.0.8"
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkgContent, null, 2));
|
|
238
|
+
console.log('✅ Created package.json');
|
|
213
239
|
}
|
|
214
240
|
|
|
215
241
|
// Create .gitignore
|
|
@@ -226,9 +252,10 @@ node_modules/
|
|
|
226
252
|
|
|
227
253
|
console.log('✅ Created jux/ directory\n');
|
|
228
254
|
console.log('Next steps:');
|
|
229
|
-
console.log(' 1.
|
|
230
|
-
console.log(' 2.
|
|
231
|
-
console.log(' 3.
|
|
255
|
+
console.log(' 1. npm install # Install juxscript');
|
|
256
|
+
console.log(' 2. Edit jux/index.jux # Build your app');
|
|
257
|
+
console.log(' 3. npx jux build # Compile to jux-dist/');
|
|
258
|
+
console.log(' 4. Serve jux-dist/ from your backend\n');
|
|
232
259
|
|
|
233
260
|
} else if (command === 'build') {
|
|
234
261
|
await buildProject(false);
|
|
@@ -249,24 +276,25 @@ Usage:
|
|
|
249
276
|
npx jux build Compile .jux files from ./jux/ to ./jux-dist/
|
|
250
277
|
npx jux serve [port] Start dev server with hot reload (default: 3000)
|
|
251
278
|
|
|
252
|
-
Project Structure
|
|
279
|
+
Project Structure:
|
|
253
280
|
my-project/
|
|
254
|
-
├── jux/ # Your .jux source files
|
|
255
|
-
│ ├── index.jux
|
|
256
|
-
│
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
├── jux-dist/ # Build output (generated, git-ignore this)
|
|
260
|
-
│ ├── samples/
|
|
261
|
-
│ │ └── mypage.js # Transpiled TypeScript
|
|
262
|
-
│ └── ...
|
|
263
|
-
├── server/ # Your backend (untouched by jux)
|
|
281
|
+
├── jux/ # Your .jux source files
|
|
282
|
+
│ ├── index.jux # Entry point (uses 'juxscript' imports)
|
|
283
|
+
│ └── pages/ # Additional pages
|
|
284
|
+
├── jux-dist/ # Build output (git-ignore this)
|
|
285
|
+
├── server/ # Your backend
|
|
264
286
|
└── package.json
|
|
265
287
|
|
|
288
|
+
Import Style:
|
|
289
|
+
// In your project's .jux files
|
|
290
|
+
import { jux, state } from 'juxscript';
|
|
291
|
+
import 'juxscript/presets/notion.js';
|
|
292
|
+
|
|
266
293
|
Getting Started:
|
|
267
|
-
1. npx jux init # Create
|
|
268
|
-
2.
|
|
269
|
-
3.
|
|
294
|
+
1. npx jux init # Create project structure
|
|
295
|
+
2. npm install # Install dependencies
|
|
296
|
+
3. npx jux build # Build to jux-dist/
|
|
297
|
+
4. Serve jux-dist/ from your backend
|
|
270
298
|
|
|
271
299
|
Examples:
|
|
272
300
|
npx jux build Build for production
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Divider - Simple horizontal or vertical divider line
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface DividerOptions {
|
|
6
|
+
orientation?: 'horizontal' | 'vertical';
|
|
7
|
+
thickness?: number;
|
|
8
|
+
color?: string;
|
|
9
|
+
margin?: string;
|
|
10
|
+
style?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Divider component for visual separation
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* // Simple horizontal divider
|
|
19
|
+
* jux.divider().render('#container');
|
|
20
|
+
*
|
|
21
|
+
* // Vertical divider
|
|
22
|
+
* jux.divider({ orientation: 'vertical' }).render('#container');
|
|
23
|
+
*
|
|
24
|
+
* // Custom styling
|
|
25
|
+
* jux.divider({
|
|
26
|
+
* thickness: 2,
|
|
27
|
+
* color: '#3b82f6',
|
|
28
|
+
* margin: '24px 0'
|
|
29
|
+
* }).render('#container');
|
|
30
|
+
*
|
|
31
|
+
* // With custom class
|
|
32
|
+
* jux.divider({ className: 'my-divider' }).render('#container');
|
|
33
|
+
*/
|
|
34
|
+
export class Divider {
|
|
35
|
+
private options: Required<DividerOptions>;
|
|
36
|
+
|
|
37
|
+
constructor(options: DividerOptions = {}) {
|
|
38
|
+
this.options = {
|
|
39
|
+
orientation: 'horizontal',
|
|
40
|
+
thickness: 1,
|
|
41
|
+
color: '#e5e7eb',
|
|
42
|
+
margin: '16px 0',
|
|
43
|
+
style: '',
|
|
44
|
+
className: '',
|
|
45
|
+
...options
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Set orientation
|
|
51
|
+
*/
|
|
52
|
+
orientation(value: 'horizontal' | 'vertical'): this {
|
|
53
|
+
this.options.orientation = value;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set thickness in pixels
|
|
59
|
+
*/
|
|
60
|
+
thickness(value: number): this {
|
|
61
|
+
this.options.thickness = value;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Set color
|
|
67
|
+
*/
|
|
68
|
+
color(value: string): this {
|
|
69
|
+
this.options.color = value;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set margin
|
|
75
|
+
*/
|
|
76
|
+
margin(value: string): this {
|
|
77
|
+
this.options.margin = value;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Set custom styles
|
|
83
|
+
*/
|
|
84
|
+
style(value: string): this {
|
|
85
|
+
this.options.style = value;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set class name
|
|
91
|
+
*/
|
|
92
|
+
className(value: string): this {
|
|
93
|
+
this.options.className = value;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Render divider to target element
|
|
99
|
+
*/
|
|
100
|
+
render(targetSelector: string): this {
|
|
101
|
+
const target = document.querySelector(targetSelector);
|
|
102
|
+
|
|
103
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
104
|
+
console.warn(`Divider: Target element "${targetSelector}" not found`);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const divider = document.createElement('hr');
|
|
109
|
+
divider.className = `jux-divider ${this.options.className}`.trim();
|
|
110
|
+
|
|
111
|
+
const isVertical = this.options.orientation === 'vertical';
|
|
112
|
+
|
|
113
|
+
const baseStyles = `
|
|
114
|
+
border: none;
|
|
115
|
+
background-color: ${this.options.color};
|
|
116
|
+
margin: ${this.options.margin};
|
|
117
|
+
${isVertical ? `
|
|
118
|
+
width: ${this.options.thickness}px;
|
|
119
|
+
height: 100%;
|
|
120
|
+
display: inline-block;
|
|
121
|
+
vertical-align: middle;
|
|
122
|
+
` : `
|
|
123
|
+
width: 100%;
|
|
124
|
+
height: ${this.options.thickness}px;
|
|
125
|
+
`}
|
|
126
|
+
${this.options.style}
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
divider.setAttribute('style', baseStyles);
|
|
130
|
+
|
|
131
|
+
target.appendChild(divider);
|
|
132
|
+
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Replace target content with divider
|
|
138
|
+
*/
|
|
139
|
+
replace(targetSelector: string): this {
|
|
140
|
+
const target = document.querySelector(targetSelector);
|
|
141
|
+
|
|
142
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
143
|
+
console.warn(`Divider: Target element "${targetSelector}" not found`);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
target.innerHTML = '';
|
|
148
|
+
return this.render(targetSelector);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Render before target element
|
|
153
|
+
*/
|
|
154
|
+
before(targetSelector: string): this {
|
|
155
|
+
const target = document.querySelector(targetSelector);
|
|
156
|
+
|
|
157
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
158
|
+
console.warn(`Divider: Target element "${targetSelector}" not found`);
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const divider = document.createElement('hr');
|
|
163
|
+
divider.className = `jux-divider ${this.options.className}`.trim();
|
|
164
|
+
|
|
165
|
+
const isVertical = this.options.orientation === 'vertical';
|
|
166
|
+
|
|
167
|
+
const baseStyles = `
|
|
168
|
+
border: none;
|
|
169
|
+
background-color: ${this.options.color};
|
|
170
|
+
margin: ${this.options.margin};
|
|
171
|
+
${isVertical ? `
|
|
172
|
+
width: ${this.options.thickness}px;
|
|
173
|
+
height: 100%;
|
|
174
|
+
display: inline-block;
|
|
175
|
+
vertical-align: middle;
|
|
176
|
+
` : `
|
|
177
|
+
width: 100%;
|
|
178
|
+
height: ${this.options.thickness}px;
|
|
179
|
+
`}
|
|
180
|
+
${this.options.style}
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
divider.setAttribute('style', baseStyles);
|
|
184
|
+
target.parentNode?.insertBefore(divider, target);
|
|
185
|
+
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Render after target element
|
|
191
|
+
*/
|
|
192
|
+
after(targetSelector: string): this {
|
|
193
|
+
const target = document.querySelector(targetSelector);
|
|
194
|
+
|
|
195
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
196
|
+
console.warn(`Divider: Target element "${targetSelector}" not found`);
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const divider = document.createElement('hr');
|
|
201
|
+
divider.className = `jux-divider ${this.options.className}`.trim();
|
|
202
|
+
|
|
203
|
+
const isVertical = this.options.orientation === 'vertical';
|
|
204
|
+
|
|
205
|
+
const baseStyles = `
|
|
206
|
+
border: none;
|
|
207
|
+
background-color: ${this.options.color};
|
|
208
|
+
margin: ${this.options.margin};
|
|
209
|
+
${isVertical ? `
|
|
210
|
+
width: ${this.options.thickness}px;
|
|
211
|
+
height: 100%;
|
|
212
|
+
display: inline-block;
|
|
213
|
+
vertical-align: middle;
|
|
214
|
+
` : `
|
|
215
|
+
width: 100%;
|
|
216
|
+
height: ${this.options.thickness}px;
|
|
217
|
+
`}
|
|
218
|
+
${this.options.style}
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
divider.setAttribute('style', baseStyles);
|
|
222
|
+
target.parentNode?.insertBefore(divider, target.nextSibling);
|
|
223
|
+
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Factory function for quick divider creation
|
|
230
|
+
*/
|
|
231
|
+
export function divider(options: DividerOptions = {}): Divider {
|
|
232
|
+
return new Divider(options);
|
|
233
|
+
}
|
|
@@ -1553,6 +1553,75 @@
|
|
|
1553
1553
|
],
|
|
1554
1554
|
"example": "jux.dialog('confirm-delete', {"
|
|
1555
1555
|
},
|
|
1556
|
+
{
|
|
1557
|
+
"name": "Divider",
|
|
1558
|
+
"category": "UI Components",
|
|
1559
|
+
"description": "Divider - Simple horizontal or vertical divider line",
|
|
1560
|
+
"constructor": "jux.divider(options: DividerOptions = {})",
|
|
1561
|
+
"fluentMethods": [
|
|
1562
|
+
{
|
|
1563
|
+
"name": "orientation",
|
|
1564
|
+
"params": "(value)",
|
|
1565
|
+
"returns": "this",
|
|
1566
|
+
"description": "Set orientation"
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
"name": "thickness",
|
|
1570
|
+
"params": "(value)",
|
|
1571
|
+
"returns": "this",
|
|
1572
|
+
"description": "Set thickness"
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
"name": "color",
|
|
1576
|
+
"params": "(value)",
|
|
1577
|
+
"returns": "this",
|
|
1578
|
+
"description": "Set color"
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
"name": "margin",
|
|
1582
|
+
"params": "(value)",
|
|
1583
|
+
"returns": "this",
|
|
1584
|
+
"description": "Set margin"
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
"name": "style",
|
|
1588
|
+
"params": "(value)",
|
|
1589
|
+
"returns": "this",
|
|
1590
|
+
"description": "Set style"
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
"name": "className",
|
|
1594
|
+
"params": "(value)",
|
|
1595
|
+
"returns": "this",
|
|
1596
|
+
"description": "Set className"
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
"name": "render",
|
|
1600
|
+
"params": "(targetSelector)",
|
|
1601
|
+
"returns": "this",
|
|
1602
|
+
"description": "Set render"
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
"name": "replace",
|
|
1606
|
+
"params": "(targetSelector)",
|
|
1607
|
+
"returns": "this",
|
|
1608
|
+
"description": "Set replace"
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
"name": "before",
|
|
1612
|
+
"params": "(targetSelector)",
|
|
1613
|
+
"returns": "this",
|
|
1614
|
+
"description": "Set before"
|
|
1615
|
+
},
|
|
1616
|
+
{
|
|
1617
|
+
"name": "after",
|
|
1618
|
+
"params": "(targetSelector)",
|
|
1619
|
+
"returns": "this",
|
|
1620
|
+
"description": "Set after"
|
|
1621
|
+
}
|
|
1622
|
+
],
|
|
1623
|
+
"example": "// Simple horizontal divider"
|
|
1624
|
+
},
|
|
1556
1625
|
{
|
|
1557
1626
|
"name": "Doughnutchart",
|
|
1558
1627
|
"category": "UI Components",
|
|
@@ -3443,5 +3512,5 @@
|
|
|
3443
3512
|
}
|
|
3444
3513
|
],
|
|
3445
3514
|
"version": "1.0.0",
|
|
3446
|
-
"lastUpdated": "2026-01-
|
|
3515
|
+
"lastUpdated": "2026-01-21T20:25:28.145Z"
|
|
3447
3516
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { getOrCreateContainer } from '../helpers.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hero component options
|
|
5
|
+
*/
|
|
6
|
+
export interface HeroOptions {
|
|
7
|
+
title?: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
cta?: string;
|
|
10
|
+
ctaLink?: string;
|
|
11
|
+
backgroundImage?: string;
|
|
12
|
+
variant?: 'default' | 'centered' | 'split';
|
|
13
|
+
style?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hero component state
|
|
19
|
+
*/
|
|
20
|
+
type HeroState = {
|
|
21
|
+
title: string;
|
|
22
|
+
subtitle: string;
|
|
23
|
+
cta: string;
|
|
24
|
+
ctaLink: string;
|
|
25
|
+
backgroundImage: string;
|
|
26
|
+
variant: string;
|
|
27
|
+
style: string;
|
|
28
|
+
class: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hero component
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* const hero = jux.hero('myHero', {
|
|
36
|
+
* title: 'Welcome',
|
|
37
|
+
* subtitle: 'Get started today',
|
|
38
|
+
* cta: 'Learn More'
|
|
39
|
+
* });
|
|
40
|
+
* hero.render();
|
|
41
|
+
*/
|
|
42
|
+
export class Hero1 {
|
|
43
|
+
state: HeroState;
|
|
44
|
+
container: HTMLElement | null = null;
|
|
45
|
+
_id: string;
|
|
46
|
+
id: string;
|
|
47
|
+
|
|
48
|
+
constructor(id: string, options: HeroOptions = {}) {
|
|
49
|
+
this._id = id;
|
|
50
|
+
this.id = id;
|
|
51
|
+
|
|
52
|
+
this.state = {
|
|
53
|
+
title: options.title ?? '',
|
|
54
|
+
subtitle: options.subtitle ?? '',
|
|
55
|
+
cta: options.cta ?? '',
|
|
56
|
+
ctaLink: options.ctaLink ?? '#',
|
|
57
|
+
backgroundImage: options.backgroundImage ?? '',
|
|
58
|
+
variant: options.variant ?? 'default',
|
|
59
|
+
style: options.style ?? '',
|
|
60
|
+
class: options.class ?? ''
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* -------------------------
|
|
65
|
+
* Fluent API
|
|
66
|
+
* ------------------------- */
|
|
67
|
+
|
|
68
|
+
title(value: string): this {
|
|
69
|
+
this.state.title = value;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
subtitle(value: string): this {
|
|
74
|
+
this.state.subtitle = value;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
cta(value: string): this {
|
|
79
|
+
this.state.cta = value;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
ctaLink(value: string): this {
|
|
84
|
+
this.state.ctaLink = value;
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
backgroundImage(value: string): this {
|
|
89
|
+
this.state.backgroundImage = value;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
variant(value: string): this {
|
|
94
|
+
this.state.variant = value;
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
style(value: string): this {
|
|
99
|
+
this.state.style = value;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class(value: string): this {
|
|
104
|
+
this.state.class = value;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* -------------------------
|
|
109
|
+
* Render
|
|
110
|
+
* ------------------------- */
|
|
111
|
+
|
|
112
|
+
render(targetId?: string): this {
|
|
113
|
+
let container: HTMLElement;
|
|
114
|
+
|
|
115
|
+
if (targetId) {
|
|
116
|
+
const target = document.querySelector(targetId);
|
|
117
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
118
|
+
throw new Error(`Hero: Target element "${targetId}" not found`);
|
|
119
|
+
}
|
|
120
|
+
container = target;
|
|
121
|
+
} else {
|
|
122
|
+
container = getOrCreateContainer(this._id);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.container = container;
|
|
126
|
+
const { title, subtitle, cta, ctaLink, backgroundImage, variant, style, class: className } = this.state;
|
|
127
|
+
|
|
128
|
+
const hero = document.createElement('div');
|
|
129
|
+
hero.className = `jux-hero jux-hero-${variant}`;
|
|
130
|
+
hero.id = this._id;
|
|
131
|
+
|
|
132
|
+
if (className) {
|
|
133
|
+
hero.className += ` ${className}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (style) {
|
|
137
|
+
hero.setAttribute('style', style);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (backgroundImage) {
|
|
141
|
+
hero.style.backgroundImage = `url(${backgroundImage})`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const content = document.createElement('div');
|
|
145
|
+
content.className = 'jux-hero-content';
|
|
146
|
+
|
|
147
|
+
if (title) {
|
|
148
|
+
const titleEl = document.createElement('h1');
|
|
149
|
+
titleEl.className = 'jux-hero-title';
|
|
150
|
+
titleEl.textContent = title;
|
|
151
|
+
content.appendChild(titleEl);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (subtitle) {
|
|
155
|
+
const subtitleEl = document.createElement('p');
|
|
156
|
+
subtitleEl.className = 'jux-hero-subtitle';
|
|
157
|
+
subtitleEl.textContent = subtitle;
|
|
158
|
+
content.appendChild(subtitleEl);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (cta) {
|
|
162
|
+
const ctaEl = document.createElement('a');
|
|
163
|
+
ctaEl.className = 'jux-hero-cta jux-button jux-button-primary';
|
|
164
|
+
ctaEl.href = ctaLink;
|
|
165
|
+
ctaEl.textContent = cta;
|
|
166
|
+
content.appendChild(ctaEl);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
hero.appendChild(content);
|
|
170
|
+
container.appendChild(hero);
|
|
171
|
+
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Render to another Jux component's container
|
|
177
|
+
*/
|
|
178
|
+
renderTo(juxComponent: any): this {
|
|
179
|
+
if (!juxComponent || typeof juxComponent !== 'object') {
|
|
180
|
+
throw new Error('Hero.renderTo: Invalid component - not an object');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!juxComponent._id || typeof juxComponent._id !== 'string') {
|
|
184
|
+
throw new Error('Hero.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return this.render(`#${juxComponent._id}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Factory helper
|
|
193
|
+
*/
|
|
194
|
+
export function hero1(id: string, options: HeroOptions = {}): Hero1 {
|
|
195
|
+
return new Hero1(id, options);
|
|
196
|
+
}
|