juxscript 1.0.3 → 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.
- package/README.md +37 -92
- package/bin/cli.js +57 -56
- package/lib/components/alert.ts +240 -0
- package/lib/components/app.ts +216 -82
- package/lib/components/badge.ts +164 -0
- package/lib/components/button.ts +188 -53
- package/lib/components/card.ts +75 -61
- package/lib/components/chart.ts +17 -15
- package/lib/components/checkbox.ts +228 -0
- package/lib/components/code.ts +66 -152
- package/lib/components/container.ts +104 -208
- package/lib/components/data.ts +1 -3
- package/lib/components/datepicker.ts +226 -0
- package/lib/components/dialog.ts +258 -0
- package/lib/components/docs-data.json +1697 -388
- package/lib/components/dropdown.ts +244 -0
- package/lib/components/element.ts +271 -0
- package/lib/components/fileupload.ts +319 -0
- package/lib/components/footer.ts +37 -18
- package/lib/components/header.ts +53 -33
- package/lib/components/heading.ts +119 -0
- package/lib/components/helpers.ts +34 -0
- package/lib/components/hero.ts +57 -31
- package/lib/components/include.ts +292 -0
- package/lib/components/input.ts +166 -78
- package/lib/components/layout.ts +144 -18
- package/lib/components/list.ts +83 -74
- package/lib/components/loading.ts +263 -0
- package/lib/components/main.ts +43 -17
- package/lib/components/menu.ts +108 -24
- package/lib/components/modal.ts +50 -21
- package/lib/components/nav.ts +60 -18
- package/lib/components/paragraph.ts +111 -0
- package/lib/components/progress.ts +276 -0
- package/lib/components/radio.ts +236 -0
- package/lib/components/req.ts +300 -0
- package/lib/components/script.ts +33 -74
- package/lib/components/select.ts +247 -0
- package/lib/components/sidebar.ts +86 -36
- package/lib/components/style.ts +47 -70
- package/lib/components/switch.ts +261 -0
- package/lib/components/table.ts +47 -24
- package/lib/components/tabs.ts +105 -63
- package/lib/components/theme-toggle.ts +361 -0
- package/lib/components/token-calculator.ts +380 -0
- package/lib/components/tooltip.ts +244 -0
- package/lib/components/view.ts +36 -20
- package/lib/components/write.ts +284 -0
- package/lib/globals.d.ts +21 -0
- package/lib/jux.ts +172 -68
- package/lib/presets/notion.css +521 -0
- package/lib/presets/notion.jux +27 -0
- package/lib/reactivity/state.ts +364 -0
- package/machinery/compiler.js +126 -38
- package/machinery/generators/html.js +2 -3
- package/machinery/server.js +2 -2
- package/package.json +29 -3
- package/lib/components/import.ts +0 -430
- package/lib/components/node.ts +0 -200
- package/lib/components/reactivity.js +0 -104
- package/lib/components/theme.ts +0 -97
- package/lib/layouts/notion.css +0 -258
- package/lib/styles/base-theme.css +0 -186
- package/lib/styles/dark-theme.css +0 -144
- package/lib/styles/light-theme.css +0 -144
- package/lib/styles/tokens/dark.css +0 -86
- package/lib/styles/tokens/light.css +0 -86
- package/lib/templates/index.juxt +0 -33
- package/lib/themes/dark.css +0 -86
- package/lib/themes/light.css +0 -86
- /package/lib/{styles → presets}/global.css +0 -0
package/README.md
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# The web needs a higher level of abstraction: Meet **JUX**
|
|
2
|
+
|
|
3
|
+
- [X] Layouts (100% done.)
|
|
4
|
+
- [ ] Render Dependency Tree
|
|
5
|
+
> 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).
|
|
6
|
+
- [X] Reactivity (90% done.)
|
|
7
|
+
- [ ] Client Components (99% of what would be needed.)
|
|
8
|
+
- [ ] Poor Intellisense support? Could be this issue.
|
|
9
|
+
- [ ] Api Wrapper
|
|
10
|
+
- [X] Params/Active State for Menu/Nav matching - built in.
|
|
11
|
+
- [ ] CDN Bundle (import CDN/'state', 'jux' from cdn.)
|
|
12
|
+
- [ ] Icon
|
|
13
|
+
- [ ] Cross Page Store.
|
|
14
|
+
|
|
15
|
+
- [ ] Quickstart Boilerplates (20% done,notion.jux)
|
|
16
|
+
- [ ] Mobile Nav
|
|
17
|
+
- [ ] `npx jux present notion|default` etc..
|
|
18
|
+
- [ ] Server side components (api and database)
|
|
19
|
+
- [ ] Quick deploy option
|
|
20
|
+
- [ ] Distributable Bundle (Static Sites)
|
|
21
|
+
- [ ] Tree Shake/Efficiencies.
|
|
22
|
+
|
|
23
|
+
## *JUX* Authoring UI's in pure javascript.
|
|
2
24
|
|
|
3
25
|
> Build beautiful, reactive UI's using only javascript. **No markup required.**
|
|
4
26
|
> A clean authorship layer capable of being taught to non-developers, strong enough to be used by real developers.
|
|
@@ -59,6 +81,16 @@ npx jux serve
|
|
|
59
81
|
> building only (the `examples` project directory)
|
|
60
82
|
`npm run build:examples`
|
|
61
83
|
|
|
84
|
+
|
|
85
|
+
# Authoring in Jux
|
|
86
|
+
|
|
87
|
+
1. Build a Layout Page you can reuse.
|
|
88
|
+
Check out the presets, they speed this process up. Just modify them with your goals, such as logo, menu options, nav etc.
|
|
89
|
+
2. Build your Pages
|
|
90
|
+
Just get to work, using the **jux** component library to build most of what you need, filling in any details with *javascript* logically arranged the way you want. Think, authoring SFC but with only the composition layer.
|
|
91
|
+
3. Test it out!
|
|
92
|
+
Jux comes with a watcher system built in, so you can see your edits to *.jux* files live.
|
|
93
|
+
|
|
62
94
|
# GOAL: Eliminating the Markup System of building UI's.
|
|
63
95
|
> HTML markup is the equivalent of still writing in `C` ro `C++` despite the availability of more functional levels of abstraction like `python`.
|
|
64
96
|
> To remove the requirement for markup, **jux** must address the utility of markup.
|
|
@@ -206,99 +238,12 @@ export default {
|
|
|
206
238
|
|
|
207
239
|
|
|
208
240
|
### LAYOUT NOTES...
|
|
209
|
-
|
|
241
|
+
### Jux Standard Layout Regions
|
|
210
242
|
|
|
211
243
|
- [ ] CHORE : REVISIT! I think maybe we ship with our figma, notion, default etc. But then document how to build and reuse a .jux page as a layout using the `jux.layout` class.
|
|
212
244
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
**jux** layout pages provide a **strongly opinionated** layout structure that covers 99% of web applications. Every page includes these regions by default, even if not all are used. This eliminates layout decisions and provides consistent target containers across your entire application.
|
|
216
|
-
|
|
217
|
-
These are free to use, but you can roll your own just as easily.
|
|
218
|
-
You can find the documentation for each of these layouts here:
|
|
219
|
-
|
|
220
|
-
- Notion
|
|
221
|
-
- Jux
|
|
222
|
-
- Figma
|
|
223
|
-
- Other?
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
### Sample Documenation
|
|
227
|
-
```markdown
|
|
228
|
-
#app
|
|
229
|
-
├── #appheader // Top navigation bar - your app's primary navigation
|
|
230
|
-
│ ├── #appheader-logo // Brand identity - logo, app name
|
|
231
|
-
│ ├── #appheader-nav // Primary navigation menu
|
|
232
|
-
│ └── #appheader-actions // User actions - profile, notifications, settings
|
|
233
|
-
│
|
|
234
|
-
├── #appsubheader // Contextual navigation - changes based on current page
|
|
235
|
-
│ ├── #appsubheader-breadcrumbs // Where am I? - navigation path
|
|
236
|
-
│ ├── #appsubheader-tabs // Page-level tabs - views within the same context
|
|
237
|
-
│ └── #appsubheader-actions // Context-specific actions - filters, search, "New Item"
|
|
238
|
-
│
|
|
239
|
-
├── #appsidebar // Left sidebar - persistent navigation or filters
|
|
240
|
-
│ ├── #appsidebar-header // Sidebar title or search
|
|
241
|
-
│ ├── #appsidebar-content // Main sidebar content - nav tree, filters
|
|
242
|
-
│ └── #appsidebar-footer // Sidebar actions or status
|
|
243
|
-
│
|
|
244
|
-
├── #appmain // Your content lives here - the star of the show
|
|
245
|
-
│
|
|
246
|
-
├── #appaside // Right sidebar - contextual help, metadata
|
|
247
|
-
│ ├── #appaside-header // Aside title
|
|
248
|
-
│ ├── #appaside-content // Properties panel, help docs, related items
|
|
249
|
-
│ └── #appaside-footer // Aside actions
|
|
250
|
-
│
|
|
251
|
-
├── #appfooter // Bottom of the page
|
|
252
|
-
│ ├── #appfooter-content // Footer navigation, social links
|
|
253
|
-
│ └── #appfooter-legal // Copyright, terms, privacy
|
|
254
|
-
│
|
|
255
|
-
└── #appmodal // Overlays and dialogs
|
|
256
|
-
├── #appmodal-backdrop // Click-to-close background
|
|
257
|
-
└── #appmodal-container
|
|
258
|
-
├── #appmodal-header // Dialog title and close button
|
|
259
|
-
├── #appmodal-content // Dialog body
|
|
260
|
-
└── #appmodal-footer // Dialog actions - Cancel, Save, etc.
|
|
261
|
-
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
## Why consider a Jux layout?
|
|
265
|
-
|
|
266
|
-
**Predictability**: Every page has the same structure. Developers know exactly where to render components.
|
|
267
|
-
|
|
268
|
-
**Flexibility**: Don't need a sidebar? Leave it empty. CSS handles visibility automatically.
|
|
269
|
-
|
|
270
|
-
**Composability**: Components can render to any region with `.render('#target-id')`.
|
|
271
|
-
|
|
272
|
-
**Convention over Configuration**: No layout decisions needed. Just start building.
|
|
273
|
-
|
|
274
|
-
## Common Patterns
|
|
275
|
-
|
|
276
|
-
```javascript
|
|
277
|
-
// Dashboard with sidebar navigation
|
|
278
|
-
const sideNav = jux.nav('side-nav', { /* config */ });
|
|
279
|
-
await sideNav.render('#appsidebar-content');
|
|
280
|
-
|
|
281
|
-
const dashboard = jux.dashboard('main-dashboard', { /* config */ });
|
|
282
|
-
await dashboard.render('#appmain');
|
|
283
|
-
|
|
284
|
-
// Settings page with tabs
|
|
285
|
-
const tabs = jux.tabs('settings-tabs', { /* config */ });
|
|
286
|
-
await tabs.render('#appsubheader-tabs');
|
|
287
|
-
|
|
288
|
-
const content = jux.container('settings-content', { /* config */ });
|
|
289
|
-
await content.render('#appmain');
|
|
290
|
-
|
|
291
|
-
// Detail view with metadata sidebar
|
|
292
|
-
const detail = jux.detail('item-detail', { /* config */ });
|
|
293
|
-
await detail.render('#appmain');
|
|
294
|
-
|
|
295
|
-
const metadata = jux.metadata('item-meta', { /* config */ });
|
|
296
|
-
await metadata.render('#appaside-content');
|
|
245
|
+
### Start with a layout page
|
|
297
246
|
|
|
298
|
-
|
|
299
|
-
const dialog = jux.dialog('confirm-dialog', { /* config */ });
|
|
300
|
-
await dialog.render('#appmodal-content');
|
|
247
|
+
You can build a simple one, or copy and use one of the Jux presets.
|
|
301
248
|
|
|
302
|
-
|
|
303
|
-
const hero = jux.hero('page-hero', { /* config */ });
|
|
304
|
-
await hero.render(); // Automatically renders to #appmain
|
|
249
|
+
`npx jux preset notion|default|figma|slack|other`
|
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { compileJuxFile, copyLibToOutput, copyProjectAssets } from '../machinery/compiler.js';
|
|
3
|
+
import { compileJuxFile, copyLibToOutput, copyProjectAssets, transpileProjectTypeScript } from '../machinery/compiler.js';
|
|
4
4
|
import { generateDocs } from '../machinery/doc-generator.js';
|
|
5
5
|
import { start } from '../machinery/server.js';
|
|
6
6
|
import path from 'path';
|
|
@@ -14,20 +14,20 @@ const __dirname = path.dirname(__filename);
|
|
|
14
14
|
const PATHS = {
|
|
15
15
|
// Where jux package is installed (in node_modules/juxscript or local dev)
|
|
16
16
|
packageRoot: path.resolve(__dirname, '..'),
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
// Where the user's project root is (where they run `npx jux`)
|
|
19
19
|
projectRoot: process.cwd(),
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
// Where user's .jux source files live (CONVENTION: ./jux/)
|
|
22
22
|
get juxSource() {
|
|
23
23
|
return path.join(this.projectRoot, 'jux');
|
|
24
24
|
},
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
// Where jux lib files are (components, layouts, etc.)
|
|
27
27
|
get juxLib() {
|
|
28
28
|
return path.join(this.packageRoot, 'lib');
|
|
29
29
|
},
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// Where frontend build output goes (CONVENTION: ./jux-dist/)
|
|
32
32
|
get frontendDist() {
|
|
33
33
|
return path.join(this.projectRoot, 'jux-dist');
|
|
@@ -43,15 +43,22 @@ console.log(` Lib: ${PATHS.juxLib}\n`);
|
|
|
43
43
|
|
|
44
44
|
const command = process.argv[2];
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Recursively find .jux files in a directory
|
|
48
|
+
*
|
|
49
|
+
* @param {string} dir - Directory to search
|
|
50
|
+
* @param {string[]} fileList - Accumulator for found files
|
|
51
|
+
* @returns {string[]} Array of .jux file paths
|
|
52
|
+
*/
|
|
46
53
|
function findJuxFiles(dir, fileList = []) {
|
|
47
54
|
if (!fs.existsSync(dir)) return fileList;
|
|
48
|
-
|
|
55
|
+
|
|
49
56
|
const files = fs.readdirSync(dir);
|
|
50
|
-
|
|
57
|
+
|
|
51
58
|
files.forEach(file => {
|
|
52
59
|
const filePath = path.join(dir, file);
|
|
53
60
|
const stat = fs.statSync(filePath);
|
|
54
|
-
|
|
61
|
+
|
|
55
62
|
if (stat.isDirectory()) {
|
|
56
63
|
if (file !== 'node_modules' && file !== 'jux-dist' && file !== '.git' && file !== 'server') {
|
|
57
64
|
findJuxFiles(filePath, fileList);
|
|
@@ -60,29 +67,18 @@ function findJuxFiles(dir, fileList = []) {
|
|
|
60
67
|
fileList.push(filePath);
|
|
61
68
|
}
|
|
62
69
|
});
|
|
63
|
-
|
|
64
|
-
return fileList;
|
|
65
|
-
}
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
const configPath = path.join(PATHS.projectRoot, 'jux.config.js');
|
|
69
|
-
|
|
70
|
-
if (fs.existsSync(configPath)) {
|
|
71
|
-
try {
|
|
72
|
-
const configModule = await import(configPath);
|
|
73
|
-
return configModule.default || {};
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.warn('⚠️ Could not load jux.config.js:', err.message);
|
|
76
|
-
return {};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return {};
|
|
71
|
+
return fileList;
|
|
81
72
|
}
|
|
82
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Build the entire JUX project
|
|
76
|
+
*
|
|
77
|
+
* @param {boolean} isServe - Whether building for dev server with hot reload
|
|
78
|
+
*/
|
|
83
79
|
async function buildProject(isServe = false) {
|
|
84
80
|
console.log('🔨 Building JUX frontend...\n');
|
|
85
|
-
|
|
81
|
+
|
|
86
82
|
try {
|
|
87
83
|
// Verify jux source directory exists
|
|
88
84
|
if (!fs.existsSync(PATHS.juxSource)) {
|
|
@@ -108,13 +104,16 @@ async function buildProject(isServe = false) {
|
|
|
108
104
|
|
|
109
105
|
// Step 2: Copy jux lib to frontend dist
|
|
110
106
|
await copyLibToOutput(PATHS.juxLib, PATHS.frontendDist);
|
|
111
|
-
|
|
107
|
+
|
|
112
108
|
// Step 3: Copy project assets from jux/ (CSS, JS, images)
|
|
113
109
|
await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
|
|
114
110
|
|
|
115
|
-
// Step 4:
|
|
111
|
+
// Step 4: Transpile TypeScript files from jux/ to jux-dist/
|
|
112
|
+
await transpileProjectTypeScript(PATHS.juxSource, PATHS.frontendDist);
|
|
113
|
+
|
|
114
|
+
// Step 5: Compile .jux files from jux/ directory ONLY
|
|
116
115
|
const projectJuxFiles = findJuxFiles(PATHS.juxSource);
|
|
117
|
-
console.log(
|
|
116
|
+
console.log(`📝 Found ${projectJuxFiles.length} .jux file(s) in /jux\n`);
|
|
118
117
|
|
|
119
118
|
for (const file of projectJuxFiles) {
|
|
120
119
|
try {
|
|
@@ -128,24 +127,24 @@ async function buildProject(isServe = false) {
|
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
// Step
|
|
130
|
+
// Step 6: Compile vendor layouts
|
|
132
131
|
const layoutsDir = path.join(PATHS.juxLib, 'layouts');
|
|
133
|
-
|
|
132
|
+
|
|
134
133
|
if (fs.existsSync(layoutsDir)) {
|
|
135
134
|
console.log('\n📐 Compiling vendor layouts...');
|
|
136
135
|
const vendorJuxFiles = findJuxFiles(layoutsDir);
|
|
137
136
|
console.log(`Found ${vendorJuxFiles.length} vendor layout(s)\n`);
|
|
138
|
-
|
|
137
|
+
|
|
139
138
|
for (const file of vendorJuxFiles) {
|
|
140
139
|
try {
|
|
141
140
|
const relPath = path.relative(PATHS.juxLib, file);
|
|
142
|
-
|
|
143
|
-
await compileJuxFile(file, {
|
|
141
|
+
|
|
142
|
+
await compileJuxFile(file, {
|
|
144
143
|
distDir: path.join(PATHS.frontendDist, 'lib'),
|
|
145
144
|
projectRoot: PATHS.juxLib,
|
|
146
|
-
isServe
|
|
145
|
+
isServe
|
|
147
146
|
});
|
|
148
|
-
|
|
147
|
+
|
|
149
148
|
console.log(` ✓ Compiled: ${relPath}`);
|
|
150
149
|
} catch (err) {
|
|
151
150
|
console.error(`Error compiling vendor layout ${file}:`, err.message);
|
|
@@ -154,7 +153,7 @@ async function buildProject(isServe = false) {
|
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
console.log(`\n✅ Built ${projectJuxFiles.length} file(s) → ${PATHS.frontendDist}\n`);
|
|
157
|
-
|
|
156
|
+
|
|
158
157
|
// Show backend integration examples
|
|
159
158
|
console.log('📦 Serve from your backend:');
|
|
160
159
|
console.log(` Express: app.use(express.static('jux-dist'))`);
|
|
@@ -162,7 +161,7 @@ async function buildProject(isServe = false) {
|
|
|
162
161
|
console.log(` FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")`);
|
|
163
162
|
console.log(` Laravel: Route::view('/', 'jux-dist/index.html')`);
|
|
164
163
|
console.log('');
|
|
165
|
-
|
|
164
|
+
|
|
166
165
|
} catch (err) {
|
|
167
166
|
console.error('❌ Build error:', err.message);
|
|
168
167
|
console.error(err.stack);
|
|
@@ -173,21 +172,21 @@ async function buildProject(isServe = false) {
|
|
|
173
172
|
(async () => {
|
|
174
173
|
if (command === 'init') {
|
|
175
174
|
console.log('🎨 Initializing JUX project...\n');
|
|
176
|
-
|
|
175
|
+
|
|
177
176
|
const juxDir = PATHS.juxSource;
|
|
178
|
-
|
|
177
|
+
|
|
179
178
|
if (fs.existsSync(juxDir)) {
|
|
180
179
|
console.error('❌ jux/ directory already exists');
|
|
181
180
|
process.exit(1);
|
|
182
181
|
}
|
|
183
|
-
|
|
182
|
+
|
|
184
183
|
// Create structure
|
|
185
184
|
fs.mkdirSync(juxDir, { recursive: true });
|
|
186
|
-
|
|
187
|
-
// Copy template file from lib/
|
|
188
|
-
const templatePath = path.join(PATHS.packageRoot, 'lib', '
|
|
185
|
+
|
|
186
|
+
// Copy template file from lib/presets/index.juxt
|
|
187
|
+
const templatePath = path.join(PATHS.packageRoot, 'lib', 'presets', 'index.juxt');
|
|
189
188
|
const targetPath = path.join(juxDir, 'index.jux');
|
|
190
|
-
|
|
189
|
+
|
|
191
190
|
if (fs.existsSync(templatePath)) {
|
|
192
191
|
fs.copyFileSync(templatePath, targetPath);
|
|
193
192
|
console.log('✅ Created jux/index.jux from template');
|
|
@@ -212,38 +211,35 @@ jux.hero('hero1', {
|
|
|
212
211
|
fs.writeFileSync(targetPath, fallbackContent);
|
|
213
212
|
console.log('✅ Created jux/index.jux');
|
|
214
213
|
}
|
|
215
|
-
|
|
214
|
+
|
|
216
215
|
// Create .gitignore
|
|
217
216
|
const gitignorePath = path.join(PATHS.projectRoot, '.gitignore');
|
|
218
217
|
const gitignoreContent = `jux-dist/
|
|
219
218
|
node_modules/
|
|
220
219
|
.DS_Store
|
|
221
220
|
`;
|
|
222
|
-
|
|
221
|
+
|
|
223
222
|
if (!fs.existsSync(gitignorePath)) {
|
|
224
223
|
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
225
224
|
console.log('✅ Created .gitignore');
|
|
226
225
|
}
|
|
227
|
-
|
|
226
|
+
|
|
228
227
|
console.log('✅ Created jux/ directory\n');
|
|
229
228
|
console.log('Next steps:');
|
|
230
229
|
console.log(' 1. Edit jux/index.jux');
|
|
231
230
|
console.log(' 2. Run: npx jux build');
|
|
232
231
|
console.log(' 3. Serve jux-dist/ from your backend\n');
|
|
233
|
-
|
|
232
|
+
|
|
234
233
|
} else if (command === 'build') {
|
|
235
234
|
await buildProject(false);
|
|
236
235
|
console.log(`✅ Build complete: ${PATHS.frontendDist}`);
|
|
237
|
-
|
|
236
|
+
|
|
238
237
|
} else if (command === 'serve') {
|
|
239
238
|
await buildProject(true);
|
|
240
|
-
|
|
241
|
-
const config = await loadConfig();
|
|
242
|
-
config.distDir = PATHS.frontendDist; // Pass the correct dist directory
|
|
243
|
-
|
|
239
|
+
|
|
244
240
|
const port = parseInt(process.argv[3]) || 3000;
|
|
245
|
-
await start(port
|
|
246
|
-
|
|
241
|
+
await start(port);
|
|
242
|
+
|
|
247
243
|
} else {
|
|
248
244
|
console.log(`
|
|
249
245
|
JUX CLI - A JavaScript UX authorship platform
|
|
@@ -257,8 +253,13 @@ Project Structure (Convention):
|
|
|
257
253
|
my-project/
|
|
258
254
|
├── jux/ # Your .jux source files (REQUIRED)
|
|
259
255
|
│ ├── index.jux
|
|
256
|
+
│ ├── samples/
|
|
257
|
+
│ │ └── mypage.ts # TypeScript files transpiled to .js
|
|
260
258
|
│ └── pages/
|
|
261
259
|
├── jux-dist/ # Build output (generated, git-ignore this)
|
|
260
|
+
│ ├── samples/
|
|
261
|
+
│ │ └── mypage.js # Transpiled TypeScript
|
|
262
|
+
│ └── ...
|
|
262
263
|
├── server/ # Your backend (untouched by jux)
|
|
263
264
|
└── package.json
|
|
264
265
|
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Alert component options
|
|
5
|
+
*/
|
|
6
|
+
export interface AlertOptions {
|
|
7
|
+
message?: string;
|
|
8
|
+
variant?: 'info' | 'success' | 'warning' | 'error';
|
|
9
|
+
title?: string;
|
|
10
|
+
dismissible?: boolean;
|
|
11
|
+
onDismiss?: () => void;
|
|
12
|
+
icon?: string;
|
|
13
|
+
style?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Alert component state
|
|
19
|
+
*/
|
|
20
|
+
type AlertState = {
|
|
21
|
+
message: string;
|
|
22
|
+
variant: string;
|
|
23
|
+
title: string;
|
|
24
|
+
dismissible: boolean;
|
|
25
|
+
icon: string;
|
|
26
|
+
style: string;
|
|
27
|
+
class: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Alert component - Status messages and notifications
|
|
32
|
+
*
|
|
33
|
+
* Usage:
|
|
34
|
+
* jux.alert('my-alert', {
|
|
35
|
+
* variant: 'success',
|
|
36
|
+
* title: 'Success!',
|
|
37
|
+
* message: 'Your changes have been saved.',
|
|
38
|
+
* dismissible: true
|
|
39
|
+
* }).render('#app');
|
|
40
|
+
*
|
|
41
|
+
* Variants: info, success, warning, error
|
|
42
|
+
*/
|
|
43
|
+
export class Alert {
|
|
44
|
+
state: AlertState;
|
|
45
|
+
container: HTMLElement | null = null;
|
|
46
|
+
_id: string;
|
|
47
|
+
id: string;
|
|
48
|
+
private _onDismiss?: () => void;
|
|
49
|
+
|
|
50
|
+
constructor(id: string, options: AlertOptions = {}) {
|
|
51
|
+
this._id = id;
|
|
52
|
+
this.id = id;
|
|
53
|
+
this._onDismiss = options.onDismiss;
|
|
54
|
+
|
|
55
|
+
// Default icons per variant
|
|
56
|
+
const defaultIcons = {
|
|
57
|
+
info: 'ℹ️',
|
|
58
|
+
success: '✅',
|
|
59
|
+
warning: '⚠️',
|
|
60
|
+
error: '❌'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const variant = options.variant ?? 'info';
|
|
64
|
+
|
|
65
|
+
this.state = {
|
|
66
|
+
message: options.message ?? '',
|
|
67
|
+
variant,
|
|
68
|
+
title: options.title ?? '',
|
|
69
|
+
dismissible: options.dismissible ?? false,
|
|
70
|
+
icon: options.icon ?? defaultIcons[variant as keyof typeof defaultIcons],
|
|
71
|
+
style: options.style ?? '',
|
|
72
|
+
class: options.class ?? ''
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* -------------------------
|
|
77
|
+
* Fluent API
|
|
78
|
+
* ------------------------- */
|
|
79
|
+
|
|
80
|
+
message(value: string): this {
|
|
81
|
+
this.state.message = value;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
variant(value: 'info' | 'success' | 'warning' | 'error'): this {
|
|
86
|
+
this.state.variant = value;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
title(value: string): this {
|
|
91
|
+
this.state.title = value;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
dismissible(value: boolean): this {
|
|
96
|
+
this.state.dismissible = value;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
icon(value: string): this {
|
|
101
|
+
this.state.icon = value;
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
style(value: string): this {
|
|
106
|
+
this.state.style = value;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class(value: string): this {
|
|
111
|
+
this.state.class = value;
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* -------------------------
|
|
116
|
+
* Methods
|
|
117
|
+
* ------------------------- */
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Dismiss/remove the alert
|
|
121
|
+
*/
|
|
122
|
+
dismiss(): void {
|
|
123
|
+
const element = document.getElementById(this._id);
|
|
124
|
+
if (element) {
|
|
125
|
+
element.style.opacity = '0';
|
|
126
|
+
element.style.transform = 'translateY(-10px)';
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
element.remove();
|
|
129
|
+
if (this._onDismiss) {
|
|
130
|
+
this._onDismiss();
|
|
131
|
+
}
|
|
132
|
+
}, 200);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Auto-dismiss after delay
|
|
138
|
+
*/
|
|
139
|
+
autoDismiss(delay: number = 5000): this {
|
|
140
|
+
setTimeout(() => this.dismiss(), delay);
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* -------------------------
|
|
145
|
+
* Render
|
|
146
|
+
* ------------------------- */
|
|
147
|
+
|
|
148
|
+
render(targetId?: string): this {
|
|
149
|
+
let container: HTMLElement;
|
|
150
|
+
|
|
151
|
+
if (targetId) {
|
|
152
|
+
const target = document.querySelector(targetId);
|
|
153
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
154
|
+
throw new Error(`Alert: Target element "${targetId}" not found`);
|
|
155
|
+
}
|
|
156
|
+
container = target;
|
|
157
|
+
} else {
|
|
158
|
+
container = getOrCreateContainer(this._id);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.container = container;
|
|
162
|
+
const { message, variant, title, dismissible, icon, style, class: className } = this.state;
|
|
163
|
+
|
|
164
|
+
const alert = document.createElement('div');
|
|
165
|
+
alert.className = `jux-alert jux-alert-${variant}`;
|
|
166
|
+
alert.id = this._id;
|
|
167
|
+
alert.setAttribute('role', 'alert');
|
|
168
|
+
|
|
169
|
+
if (className) {
|
|
170
|
+
alert.className += ` ${className}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (style) {
|
|
174
|
+
alert.setAttribute('style', style);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Icon
|
|
178
|
+
if (icon) {
|
|
179
|
+
const iconEl = document.createElement('span');
|
|
180
|
+
iconEl.className = 'jux-alert-icon';
|
|
181
|
+
iconEl.textContent = icon;
|
|
182
|
+
alert.appendChild(iconEl);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Content
|
|
186
|
+
const content = document.createElement('div');
|
|
187
|
+
content.className = 'jux-alert-content';
|
|
188
|
+
|
|
189
|
+
if (title) {
|
|
190
|
+
const titleEl = document.createElement('div');
|
|
191
|
+
titleEl.className = 'jux-alert-title';
|
|
192
|
+
titleEl.textContent = title;
|
|
193
|
+
content.appendChild(titleEl);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (message) {
|
|
197
|
+
const messageEl = document.createElement('div');
|
|
198
|
+
messageEl.className = 'jux-alert-message';
|
|
199
|
+
messageEl.textContent = message;
|
|
200
|
+
content.appendChild(messageEl);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
alert.appendChild(content);
|
|
204
|
+
|
|
205
|
+
// Dismiss button
|
|
206
|
+
if (dismissible) {
|
|
207
|
+
const dismissBtn = document.createElement('button');
|
|
208
|
+
dismissBtn.className = 'jux-alert-dismiss';
|
|
209
|
+
dismissBtn.innerHTML = '×';
|
|
210
|
+
dismissBtn.setAttribute('aria-label', 'Dismiss alert');
|
|
211
|
+
dismissBtn.addEventListener('click', () => this.dismiss());
|
|
212
|
+
alert.appendChild(dismissBtn);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
container.appendChild(alert);
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Render to another Jux component's container
|
|
221
|
+
*/
|
|
222
|
+
renderTo(juxComponent: any): this {
|
|
223
|
+
if (!juxComponent || typeof juxComponent !== 'object') {
|
|
224
|
+
throw new Error('Alert.renderTo: Invalid component - not an object');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!juxComponent._id || typeof juxComponent._id !== 'string') {
|
|
228
|
+
throw new Error('Alert.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return this.render(`#${juxComponent._id}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Factory helper
|
|
237
|
+
*/
|
|
238
|
+
export function alert(id: string, options: AlertOptions = {}): Alert {
|
|
239
|
+
return new Alert(id, options);
|
|
240
|
+
}
|