juxscript 1.0.7 → 1.0.8
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/lib/components/docs-data.json +13 -1
- package/lib/components/kpicard.ts +171 -67
- package/package.json +1 -1
|
@@ -2413,6 +2413,18 @@
|
|
|
2413
2413
|
"returns": "this",
|
|
2414
2414
|
"description": "Set animationDuration"
|
|
2415
2415
|
},
|
|
2416
|
+
{
|
|
2417
|
+
"name": "borderRadius",
|
|
2418
|
+
"params": "(value)",
|
|
2419
|
+
"returns": "this",
|
|
2420
|
+
"description": "Set borderRadius"
|
|
2421
|
+
},
|
|
2422
|
+
{
|
|
2423
|
+
"name": "showAccentBar",
|
|
2424
|
+
"params": "(value)",
|
|
2425
|
+
"returns": "this",
|
|
2426
|
+
"description": "Set showAccentBar"
|
|
2427
|
+
},
|
|
2416
2428
|
{
|
|
2417
2429
|
"name": "class",
|
|
2418
2430
|
"params": "(value)",
|
|
@@ -3431,5 +3443,5 @@
|
|
|
3431
3443
|
}
|
|
3432
3444
|
],
|
|
3433
3445
|
"version": "1.0.0",
|
|
3434
|
-
"lastUpdated": "2026-01-
|
|
3446
|
+
"lastUpdated": "2026-01-21T05:03:38.497Z"
|
|
3435
3447
|
}
|
|
@@ -21,9 +21,11 @@ export interface KPICardOptions {
|
|
|
21
21
|
width?: number;
|
|
22
22
|
height?: number;
|
|
23
23
|
theme?: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
|
|
24
|
-
styleMode?: 'default' | 'gradient' | 'glass' | 'outline';
|
|
24
|
+
styleMode?: 'default' | 'gradient' | 'glass' | 'outline' | 'glow';
|
|
25
25
|
animate?: boolean;
|
|
26
26
|
animationDuration?: number;
|
|
27
|
+
borderRadius?: number;
|
|
28
|
+
showAccentBar?: boolean;
|
|
27
29
|
class?: string;
|
|
28
30
|
style?: string;
|
|
29
31
|
}
|
|
@@ -40,9 +42,11 @@ type KPICardState = {
|
|
|
40
42
|
width: number;
|
|
41
43
|
height: number;
|
|
42
44
|
theme: 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
|
|
43
|
-
styleMode: 'default' | 'gradient' | 'glass' | 'outline';
|
|
45
|
+
styleMode: 'default' | 'gradient' | 'glass' | 'outline' | 'glow';
|
|
44
46
|
animate: boolean;
|
|
45
47
|
animationDuration: number;
|
|
48
|
+
borderRadius: number;
|
|
49
|
+
showAccentBar: boolean;
|
|
46
50
|
class: string;
|
|
47
51
|
style: string;
|
|
48
52
|
};
|
|
@@ -56,6 +60,7 @@ type KPICardState = {
|
|
|
56
60
|
* .value('78k')
|
|
57
61
|
* .delta(10)
|
|
58
62
|
* .theme('mint')
|
|
63
|
+
* .styleMode('gradient')
|
|
59
64
|
* .render('#app');
|
|
60
65
|
*/
|
|
61
66
|
export class KPICard {
|
|
@@ -75,6 +80,8 @@ export class KPICard {
|
|
|
75
80
|
styleMode: 'default',
|
|
76
81
|
animate: true,
|
|
77
82
|
animationDuration: 600,
|
|
83
|
+
borderRadius: 16,
|
|
84
|
+
showAccentBar: true,
|
|
78
85
|
class: '',
|
|
79
86
|
style: ''
|
|
80
87
|
};
|
|
@@ -100,6 +107,8 @@ export class KPICard {
|
|
|
100
107
|
if (options.styleMode !== undefined) this.state.styleMode = options.styleMode;
|
|
101
108
|
if (options.animate !== undefined) this.state.animate = options.animate;
|
|
102
109
|
if (options.animationDuration !== undefined) this.state.animationDuration = options.animationDuration;
|
|
110
|
+
if (options.borderRadius !== undefined) this.state.borderRadius = options.borderRadius;
|
|
111
|
+
if (options.showAccentBar !== undefined) this.state.showAccentBar = options.showAccentBar;
|
|
103
112
|
if (options.class !== undefined) this.state.class = options.class;
|
|
104
113
|
if (options.style !== undefined) this.state.style = options.style;
|
|
105
114
|
}
|
|
@@ -205,6 +214,16 @@ export class KPICard {
|
|
|
205
214
|
return this;
|
|
206
215
|
}
|
|
207
216
|
|
|
217
|
+
borderRadius(value: number): this {
|
|
218
|
+
this.state.borderRadius = value;
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
showAccentBar(value: boolean): this {
|
|
223
|
+
this.state.showAccentBar = value;
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
208
227
|
class(value: string): this {
|
|
209
228
|
this.state.class = value;
|
|
210
229
|
return this;
|
|
@@ -223,6 +242,7 @@ export class KPICard {
|
|
|
223
242
|
return this;
|
|
224
243
|
}
|
|
225
244
|
|
|
245
|
+
this._loadThemeFont();
|
|
226
246
|
this._buildCard(element as HTMLElement);
|
|
227
247
|
return this;
|
|
228
248
|
}
|
|
@@ -235,9 +255,21 @@ export class KPICard {
|
|
|
235
255
|
|
|
236
256
|
// Clear and rebuild
|
|
237
257
|
element.innerHTML = '';
|
|
258
|
+
this._loadThemeFont();
|
|
238
259
|
this._buildCard(element as HTMLElement);
|
|
239
260
|
}
|
|
240
261
|
|
|
262
|
+
private _loadThemeFont(): void {
|
|
263
|
+
const themeConfig = this._getThemeConfig();
|
|
264
|
+
|
|
265
|
+
if (themeConfig.font && !document.querySelector(`link[href="${themeConfig.font}"]`)) {
|
|
266
|
+
const link = document.createElement('link');
|
|
267
|
+
link.rel = 'stylesheet';
|
|
268
|
+
link.href = themeConfig.font;
|
|
269
|
+
document.head.appendChild(link);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
241
273
|
private _buildCard(container: HTMLElement): void {
|
|
242
274
|
const { width, height, class: className, style, animate, animationDuration } = this.state;
|
|
243
275
|
|
|
@@ -277,12 +309,18 @@ export class KPICard {
|
|
|
277
309
|
}
|
|
278
310
|
|
|
279
311
|
private _createContent(): HTMLElement {
|
|
280
|
-
const { title, value, delta, prefix, suffix, animate, animationDuration } = this.state;
|
|
312
|
+
const { title, value, delta, prefix, suffix, animate, animationDuration, styleMode, width, height } = this.state;
|
|
313
|
+
const themeConfig = this._getThemeConfig();
|
|
314
|
+
|
|
315
|
+
// Calculate scale factor based on default dimensions (280x200)
|
|
316
|
+
const baseWidth = 280;
|
|
317
|
+
const baseHeight = 200;
|
|
318
|
+
const scaleFactor = Math.min(width / baseWidth, height / baseHeight);
|
|
281
319
|
|
|
282
320
|
const content = document.createElement('div');
|
|
283
321
|
content.className = 'jux-kpicard-content';
|
|
284
322
|
content.style.cssText = `
|
|
285
|
-
padding:
|
|
323
|
+
padding: ${24 * scaleFactor}px;
|
|
286
324
|
display: flex;
|
|
287
325
|
flex-direction: column;
|
|
288
326
|
height: 100%;
|
|
@@ -294,11 +332,11 @@ export class KPICard {
|
|
|
294
332
|
titleEl.className = 'jux-kpicard-title';
|
|
295
333
|
titleEl.textContent = title;
|
|
296
334
|
titleEl.style.cssText = `
|
|
297
|
-
font-size:
|
|
335
|
+
font-size: ${16 * scaleFactor}px;
|
|
298
336
|
font-weight: 500;
|
|
299
|
-
color: #6b7280;
|
|
300
|
-
margin-bottom:
|
|
301
|
-
font-family:
|
|
337
|
+
color: ${styleMode === 'gradient' ? 'rgba(255, 255, 255, 0.95)' : '#6b7280'};
|
|
338
|
+
margin-bottom: ${16 * scaleFactor}px;
|
|
339
|
+
font-family: ${themeConfig.variables['--chart-font-family']};
|
|
302
340
|
`;
|
|
303
341
|
content.appendChild(titleEl);
|
|
304
342
|
|
|
@@ -307,7 +345,7 @@ export class KPICard {
|
|
|
307
345
|
valueContainer.style.cssText = `
|
|
308
346
|
display: flex;
|
|
309
347
|
align-items: baseline;
|
|
310
|
-
margin-bottom:
|
|
348
|
+
margin-bottom: ${12 * scaleFactor}px;
|
|
311
349
|
`;
|
|
312
350
|
|
|
313
351
|
// Value
|
|
@@ -315,11 +353,12 @@ export class KPICard {
|
|
|
315
353
|
valueEl.className = 'jux-kpicard-value';
|
|
316
354
|
valueEl.textContent = `${prefix}${value}${suffix}`;
|
|
317
355
|
valueEl.style.cssText = `
|
|
318
|
-
font-size:
|
|
356
|
+
font-size: ${56 * scaleFactor}px;
|
|
319
357
|
font-weight: 800;
|
|
320
|
-
color: #1f2937;
|
|
358
|
+
color: ${styleMode === 'gradient' ? '#ffffff' : '#1f2937'};
|
|
321
359
|
line-height: 1;
|
|
322
|
-
font-family:
|
|
360
|
+
font-family: ${themeConfig.variables['--chart-font-family']};
|
|
361
|
+
${styleMode === 'glow' ? `text-shadow: 0 0 ${20 * scaleFactor}px ${themeConfig.colors[0]}40;` : ''}
|
|
323
362
|
`;
|
|
324
363
|
|
|
325
364
|
if (animate) {
|
|
@@ -345,21 +384,25 @@ export class KPICard {
|
|
|
345
384
|
deltaContainer.style.cssText = `
|
|
346
385
|
display: flex;
|
|
347
386
|
align-items: center;
|
|
348
|
-
gap:
|
|
387
|
+
gap: ${8 * scaleFactor}px;
|
|
349
388
|
`;
|
|
350
389
|
|
|
351
390
|
// Arrow SVG
|
|
352
|
-
const arrow = this._createArrowSVG(delta > 0);
|
|
391
|
+
const arrow = this._createArrowSVG(delta > 0, styleMode === 'gradient', scaleFactor);
|
|
353
392
|
deltaContainer.appendChild(arrow);
|
|
354
393
|
|
|
355
394
|
// Delta text
|
|
356
395
|
const deltaText = document.createElement('span');
|
|
357
396
|
deltaText.textContent = `${delta > 0 ? '+' : ''}${delta}%`;
|
|
397
|
+
const deltaColor = styleMode === 'gradient'
|
|
398
|
+
? (delta > 0 ? 'rgba(255, 255, 255, 0.95)' : 'rgba(255, 200, 200, 0.95)')
|
|
399
|
+
: (delta > 0 ? '#10b981' : '#ef4444');
|
|
400
|
+
|
|
358
401
|
deltaText.style.cssText = `
|
|
359
|
-
font-size:
|
|
402
|
+
font-size: ${18 * scaleFactor}px;
|
|
360
403
|
font-weight: 700;
|
|
361
|
-
color: ${
|
|
362
|
-
font-family:
|
|
404
|
+
color: ${deltaColor};
|
|
405
|
+
font-family: ${themeConfig.variables['--chart-font-family']};
|
|
363
406
|
`;
|
|
364
407
|
|
|
365
408
|
if (animate) {
|
|
@@ -382,37 +425,87 @@ export class KPICard {
|
|
|
382
425
|
return content;
|
|
383
426
|
}
|
|
384
427
|
|
|
385
|
-
private _createArrowSVG(isUp: boolean): SVGSVGElement {
|
|
428
|
+
private _createArrowSVG(isUp: boolean, isGradientMode: boolean, scaleFactor: number = 1): SVGSVGElement {
|
|
429
|
+
const baseWidth = 120;
|
|
430
|
+
const baseHeight = 32;
|
|
431
|
+
const width = baseWidth * scaleFactor;
|
|
432
|
+
const height = baseHeight * scaleFactor;
|
|
433
|
+
|
|
386
434
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
387
|
-
svg.setAttribute('width',
|
|
388
|
-
svg.setAttribute('height',
|
|
389
|
-
svg.setAttribute('viewBox',
|
|
435
|
+
svg.setAttribute('width', width.toString());
|
|
436
|
+
svg.setAttribute('height', height.toString());
|
|
437
|
+
svg.setAttribute('viewBox', `0 0 ${baseWidth} ${baseHeight}`);
|
|
390
438
|
svg.setAttribute('fill', 'none');
|
|
391
439
|
svg.style.flexShrink = '0';
|
|
392
440
|
|
|
393
|
-
const color =
|
|
441
|
+
const color = isGradientMode
|
|
442
|
+
? (isUp ? 'rgba(255, 255, 255, 0.9)' : 'rgba(255, 200, 200, 0.9)')
|
|
443
|
+
: (isUp ? '#10b981' : '#ef4444');
|
|
394
444
|
|
|
395
|
-
// Create squiggly arrow path
|
|
396
|
-
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
397
|
-
|
|
398
445
|
if (isUp) {
|
|
399
|
-
//
|
|
400
|
-
path.
|
|
446
|
+
// Upward trending arrow - wider
|
|
447
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
448
|
+
path.setAttribute('d', 'M4 26 L22 18 L40 22 L58 14 L76 18 L94 10');
|
|
449
|
+
path.setAttribute('stroke', color);
|
|
450
|
+
path.setAttribute('stroke-width', '3');
|
|
451
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
452
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
453
|
+
path.setAttribute('fill', 'none');
|
|
454
|
+
svg.appendChild(path);
|
|
455
|
+
|
|
456
|
+
// Points at each vertex - bigger circles
|
|
457
|
+
const points = [
|
|
458
|
+
{ x: 4, y: 26 },
|
|
459
|
+
{ x: 22, y: 18 },
|
|
460
|
+
{ x: 40, y: 22 },
|
|
461
|
+
{ x: 58, y: 14 },
|
|
462
|
+
{ x: 76, y: 18 },
|
|
463
|
+
{ x: 94, y: 10 }
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
points.forEach(point => {
|
|
467
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
468
|
+
circle.setAttribute('cx', point.x.toString());
|
|
469
|
+
circle.setAttribute('cy', point.y.toString());
|
|
470
|
+
circle.setAttribute('r', '4.5');
|
|
471
|
+
circle.setAttribute('fill', color);
|
|
472
|
+
svg.appendChild(circle);
|
|
473
|
+
});
|
|
401
474
|
} else {
|
|
402
|
-
//
|
|
403
|
-
path.
|
|
475
|
+
// Downward trending arrow - wider
|
|
476
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
477
|
+
path.setAttribute('d', 'M4 6 L22 14 L40 10 L58 18 L76 14 L94 22');
|
|
478
|
+
path.setAttribute('stroke', color);
|
|
479
|
+
path.setAttribute('stroke-width', '3');
|
|
480
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
481
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
482
|
+
path.setAttribute('fill', 'none');
|
|
483
|
+
svg.appendChild(path);
|
|
484
|
+
|
|
485
|
+
// Points at each vertex - bigger circles
|
|
486
|
+
const points = [
|
|
487
|
+
{ x: 4, y: 6 },
|
|
488
|
+
{ x: 22, y: 14 },
|
|
489
|
+
{ x: 40, y: 10 },
|
|
490
|
+
{ x: 58, y: 18 },
|
|
491
|
+
{ x: 76, y: 14 },
|
|
492
|
+
{ x: 94, y: 22 }
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
points.forEach(point => {
|
|
496
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
497
|
+
circle.setAttribute('cx', point.x.toString());
|
|
498
|
+
circle.setAttribute('cy', point.y.toString());
|
|
499
|
+
circle.setAttribute('r', '4.5');
|
|
500
|
+
circle.setAttribute('fill', color);
|
|
501
|
+
svg.appendChild(circle);
|
|
502
|
+
});
|
|
404
503
|
}
|
|
405
|
-
|
|
406
|
-
path.setAttribute('stroke', color);
|
|
407
|
-
path.setAttribute('stroke-width', '2');
|
|
408
|
-
path.setAttribute('stroke-linecap', 'round');
|
|
409
|
-
path.setAttribute('stroke-linejoin', 'round');
|
|
410
504
|
|
|
411
|
-
svg.appendChild(path);
|
|
412
505
|
return svg;
|
|
413
506
|
}
|
|
414
507
|
|
|
415
|
-
private
|
|
508
|
+
private _getThemeConfig() {
|
|
416
509
|
const { theme } = this.state;
|
|
417
510
|
|
|
418
511
|
switch (theme) {
|
|
@@ -436,63 +529,74 @@ export class KPICard {
|
|
|
436
529
|
}
|
|
437
530
|
|
|
438
531
|
private _applyThemeAndStyle(wrapper: HTMLElement): void {
|
|
439
|
-
const { styleMode } = this.state;
|
|
440
|
-
const
|
|
532
|
+
const { styleMode, borderRadius, showAccentBar, width, height } = this.state;
|
|
533
|
+
const themeConfig = this._getThemeConfig();
|
|
534
|
+
const colors = themeConfig.colors;
|
|
535
|
+
|
|
536
|
+
// Calculate scale factor for accent bar
|
|
537
|
+
const baseWidth = 280;
|
|
538
|
+
const scaleFactor = width / baseWidth;
|
|
539
|
+
const accentBarHeight = 4 * scaleFactor;
|
|
441
540
|
|
|
442
541
|
const baseStyles = `
|
|
443
|
-
border-radius:
|
|
542
|
+
border-radius: ${borderRadius}px;
|
|
444
543
|
overflow: hidden;
|
|
445
544
|
position: relative;
|
|
446
|
-
font-family: -
|
|
545
|
+
font-family: ${themeConfig.variables['--chart-font-family']};
|
|
447
546
|
`;
|
|
448
547
|
|
|
449
548
|
if (styleMode === 'gradient') {
|
|
450
549
|
wrapper.style.cssText += `
|
|
451
550
|
${baseStyles}
|
|
452
|
-
background: linear-gradient(135deg, ${colors[0]} 0%, ${colors[1]} 100%);
|
|
453
|
-
box-shadow: 0
|
|
551
|
+
background: linear-gradient(135deg, ${colors[0]} 0%, ${colors[1]} 50%, ${colors[2]} 100%);
|
|
552
|
+
box-shadow: 0 ${8 * scaleFactor}px ${32 * scaleFactor}px ${colors[0]}40;
|
|
454
553
|
`;
|
|
455
|
-
|
|
456
|
-
// Adjust text colors for gradient
|
|
457
|
-
const titleElements = wrapper.querySelectorAll('.jux-kpicard-title');
|
|
458
|
-
const valueElements = wrapper.querySelectorAll('.jux-kpicard-value');
|
|
459
|
-
titleElements.forEach(el => (el as HTMLElement).style.color = 'rgba(255, 255, 255, 0.9)');
|
|
460
|
-
valueElements.forEach(el => (el as HTMLElement).style.color = '#ffffff');
|
|
461
554
|
} else if (styleMode === 'glass') {
|
|
462
555
|
wrapper.style.cssText += `
|
|
463
556
|
${baseStyles}
|
|
464
|
-
background:
|
|
465
|
-
backdrop-filter: blur(
|
|
466
|
-
border:
|
|
467
|
-
box-shadow: 0
|
|
557
|
+
background: linear-gradient(135deg, ${colors[0]}20 0%, ${colors[1]}20 100%);
|
|
558
|
+
backdrop-filter: blur(${12 * scaleFactor}px) saturate(180%);
|
|
559
|
+
border: ${1 * scaleFactor}px solid ${colors[0]}30;
|
|
560
|
+
box-shadow: 0 ${8 * scaleFactor}px ${32 * scaleFactor}px rgba(0, 0, 0, 0.1);
|
|
468
561
|
`;
|
|
469
562
|
} else if (styleMode === 'outline') {
|
|
470
563
|
wrapper.style.cssText += `
|
|
471
564
|
${baseStyles}
|
|
472
|
-
background:
|
|
473
|
-
border:
|
|
565
|
+
background: #ffffff;
|
|
566
|
+
border: ${3 * scaleFactor}px solid ${colors[0]};
|
|
567
|
+
box-shadow: 0 ${4 * scaleFactor}px ${12 * scaleFactor}px ${colors[0]}20;
|
|
568
|
+
`;
|
|
569
|
+
} else if (styleMode === 'glow') {
|
|
570
|
+
wrapper.style.cssText += `
|
|
571
|
+
${baseStyles}
|
|
572
|
+
background: #ffffff;
|
|
573
|
+
border: ${2 * scaleFactor}px solid ${colors[0]}40;
|
|
574
|
+
box-shadow: 0 0 ${30 * scaleFactor}px ${colors[0]}30, 0 ${4 * scaleFactor}px ${12 * scaleFactor}px rgba(0, 0, 0, 0.1);
|
|
474
575
|
`;
|
|
475
576
|
} else {
|
|
476
577
|
// default
|
|
477
578
|
wrapper.style.cssText += `
|
|
478
579
|
${baseStyles}
|
|
479
580
|
background: #ffffff;
|
|
480
|
-
border:
|
|
481
|
-
box-shadow: 0
|
|
581
|
+
border: ${1 * scaleFactor}px solid #e5e7eb;
|
|
582
|
+
box-shadow: 0 ${1 * scaleFactor}px ${3 * scaleFactor}px rgba(0, 0, 0, 0.1);
|
|
482
583
|
`;
|
|
483
584
|
}
|
|
484
585
|
|
|
485
|
-
// Apply theme color accent
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
586
|
+
// Apply theme color accent bar (optional)
|
|
587
|
+
if (showAccentBar) {
|
|
588
|
+
const accentBar = document.createElement('div');
|
|
589
|
+
accentBar.style.cssText = `
|
|
590
|
+
position: absolute;
|
|
591
|
+
top: 0;
|
|
592
|
+
left: 0;
|
|
593
|
+
right: 0;
|
|
594
|
+
height: ${accentBarHeight}px;
|
|
595
|
+
background: linear-gradient(90deg, ${colors[0]}, ${colors[1]}, ${colors[2]});
|
|
596
|
+
border-radius: ${borderRadius}px ${borderRadius}px 0 0;
|
|
597
|
+
`;
|
|
598
|
+
wrapper.appendChild(accentBar);
|
|
599
|
+
}
|
|
496
600
|
}
|
|
497
601
|
}
|
|
498
602
|
|