@kodaris/krubble-components 1.0.3 → 1.0.5

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.
@@ -396,16 +396,19 @@ var __decorate$5 = (undefined && undefined.__decorate) || function (decorators,
396
396
  * A customizable button component.
397
397
  *
398
398
  * @slot - The button content
399
- * @csspart button - The button element
400
399
  * @fires click - Fired when the button is clicked
401
400
  */
402
401
  let KRButton = class KRButton extends i$1 {
403
402
  constructor() {
404
403
  super(...arguments);
405
404
  /**
406
- * The button variant style
405
+ * The button variant (shape)
407
406
  */
408
- this.variant = 'primary';
407
+ this.variant = 'flat';
408
+ /**
409
+ * The button color
410
+ */
411
+ this.color = 'primary';
409
412
  /**
410
413
  * The button size
411
414
  */
@@ -414,104 +417,256 @@ let KRButton = class KRButton extends i$1 {
414
417
  * Whether the button is disabled
415
418
  */
416
419
  this.disabled = false;
420
+ this._state = 'idle';
421
+ this._stateText = '';
422
+ this._handleKeydown = (e) => {
423
+ if (e.key === 'Enter' || e.key === ' ') {
424
+ e.preventDefault();
425
+ this.click();
426
+ }
427
+ };
428
+ }
429
+ connectedCallback() {
430
+ super.connectedCallback();
431
+ this.setAttribute('role', 'button');
432
+ this.setAttribute('tabindex', '0');
433
+ this.addEventListener('keydown', this._handleKeydown);
434
+ }
435
+ disconnectedCallback() {
436
+ super.disconnectedCallback();
437
+ this.removeEventListener('keydown', this._handleKeydown);
438
+ }
439
+ /**
440
+ * Shows a loading spinner and disables the button.
441
+ */
442
+ showLoading() {
443
+ this._clearStateTimeout();
444
+ this._state = 'loading';
445
+ this._stateText = '';
446
+ }
447
+ /**
448
+ * Shows a success state with optional custom text.
449
+ * @param text - Text to display (default: "Saved")
450
+ * @param duration - Duration in ms before auto-reset (default: 2000)
451
+ */
452
+ showSuccess(text = 'Success', duration = 2000) {
453
+ this._clearStateTimeout();
454
+ this._state = 'success';
455
+ this._stateText = text;
456
+ this._stateTimeout = window.setTimeout(() => this.reset(), duration);
457
+ }
458
+ /**
459
+ * Shows an error state with optional custom text.
460
+ * @param text - Text to display (default: "Error")
461
+ * @param duration - Duration in ms before auto-reset (default: 2000)
462
+ */
463
+ showError(text = 'Error', duration = 2000) {
464
+ this._clearStateTimeout();
465
+ this._state = 'error';
466
+ this._stateText = text;
467
+ this._stateTimeout = window.setTimeout(() => this.reset(), duration);
468
+ }
469
+ /**
470
+ * Resets the button to its idle state.
471
+ */
472
+ reset() {
473
+ this._clearStateTimeout();
474
+ this._state = 'idle';
475
+ this._stateText = '';
476
+ }
477
+ _clearStateTimeout() {
478
+ if (this._stateTimeout) {
479
+ clearTimeout(this._stateTimeout);
480
+ this._stateTimeout = undefined;
481
+ }
482
+ }
483
+ updated(changedProperties) {
484
+ // Reflect state classes to host
485
+ this.classList.toggle('kr-button--loading', this._state === 'loading');
486
+ this.classList.toggle('kr-button--success', this._state === 'success');
487
+ this.classList.toggle('kr-button--error', this._state === 'error');
488
+ this.classList.toggle(`kr-button--${this.variant}`, true);
489
+ this.classList.toggle(`kr-button--${this.color}`, true);
490
+ this.classList.toggle('kr-button--small', this.size === 'small');
491
+ this.classList.toggle('kr-button--large', this.size === 'large');
417
492
  }
418
493
  render() {
419
- const sizeClass = this.size !== 'medium' ? this.size : '';
420
494
  return b `
421
- <button
422
- part="button"
423
- class="${this.variant} ${sizeClass}"
424
- ?disabled=${this.disabled}
425
- >
426
- <slot></slot>
427
- </button>
495
+ <slot></slot>
496
+ ${this._state !== 'idle'
497
+ ? b `<span class="state-overlay">
498
+ ${this._state === 'loading'
499
+ ? b `<span class="spinner"></span>`
500
+ : this._stateText}
501
+ </span>`
502
+ : ''}
428
503
  `;
429
504
  }
430
505
  };
431
506
  KRButton.styles = i$4 `
432
507
  :host {
433
- display: inline-block;
434
- }
435
-
436
- button {
508
+ display: inline-flex;
509
+ position: relative;
510
+ align-items: center;
511
+ justify-content: center;
437
512
  font-family: inherit;
438
513
  font-size: 14px;
439
514
  font-weight: 400;
440
515
  letter-spacing: 0.01em;
441
516
  padding: 0 22px;
442
517
  height: 36px;
443
- line-height: 36px;
444
518
  border: none;
445
519
  border-radius: 20px;
446
520
  cursor: pointer;
447
521
  transition: background 0.15s ease;
522
+ user-select: none;
523
+ box-sizing: border-box;
448
524
  }
449
525
 
450
- button:active:not(:disabled) {
451
- transform: scale(0.98);
526
+ :host([disabled]),
527
+ :host(.kr-button--loading),
528
+ :host(.kr-button--success),
529
+ :host(.kr-button--error) {
530
+ cursor: not-allowed;
531
+ pointer-events: none;
452
532
  }
453
533
 
454
- button:disabled {
534
+ :host([disabled]:not(.kr-button--loading):not(.kr-button--success):not(.kr-button--error)) {
455
535
  opacity: 0.5;
456
- cursor: not-allowed;
457
536
  }
458
537
 
459
- /* Variants */
460
- button.primary {
538
+ /* Flat + Primary (default) */
539
+ :host,
540
+ :host(.kr-button--flat.kr-button--primary) {
461
541
  background: #163052;
462
542
  color: white;
463
543
  }
464
544
 
465
- button.primary:hover:not(:disabled) {
545
+ :host(:hover:not([disabled])),
546
+ :host(.kr-button--flat.kr-button--primary:hover:not([disabled])) {
466
547
  background: #0e1f35;
467
548
  }
468
549
 
469
- button.secondary {
550
+ /* Flat + Secondary */
551
+ :host(.kr-button--flat.kr-button--secondary) {
470
552
  background: #f3f4f6;
471
553
  color: #374151;
472
554
  }
473
555
 
474
- button.secondary:hover:not(:disabled) {
556
+ :host(.kr-button--flat.kr-button--secondary:hover:not([disabled])) {
475
557
  background: #e5e7eb;
476
558
  }
477
559
 
478
- button.outline {
560
+ /* Outline + Primary */
561
+ :host(.kr-button--outline.kr-button--primary) {
562
+ background: transparent;
563
+ border: 1px solid #163052;
564
+ color: #163052;
565
+ }
566
+
567
+ :host(.kr-button--outline.kr-button--primary:hover:not([disabled])) {
568
+ background: rgba(22, 48, 82, 0.05);
569
+ }
570
+
571
+ /* Outline + Secondary */
572
+ :host(.kr-button--outline.kr-button--secondary) {
479
573
  background: transparent;
480
574
  border: 1px solid #d1d5db;
481
575
  color: #374151;
482
- box-shadow: none;
483
576
  }
484
577
 
485
- button.outline:hover:not(:disabled) {
578
+ :host(.kr-button--outline.kr-button--secondary:hover:not([disabled])) {
486
579
  background: #f9fafb;
487
580
  border-color: #9ca3af;
488
- box-shadow: none;
489
581
  }
490
582
 
491
583
  /* Sizes */
492
- button.small {
584
+ :host(.kr-button--small) {
493
585
  font-size: 13px;
494
586
  height: 28px;
495
- line-height: 28px;
496
587
  padding: 0 16px;
497
588
  }
498
589
 
499
- button.large {
500
- font-size: 1rem;
590
+ :host(.kr-button--large) {
591
+ font-size: 16px;
501
592
  height: 44px;
502
- line-height: 44px;
503
593
  padding: 0 24px;
594
+ border-radius: 30px;
595
+ }
596
+
597
+ /* State colors */
598
+ :host(.kr-button--success) {
599
+ background: #198754 !important;
600
+ color: white !important;
601
+ }
602
+
603
+ :host(.kr-button--error) {
604
+ background: rgb(220, 53, 69) !important;
605
+ color: white !important;
606
+ }
607
+
608
+ /* Content */
609
+ :host(.kr-button--loading) slot,
610
+ :host(.kr-button--success) slot,
611
+ :host(.kr-button--error) slot {
612
+ visibility: hidden;
613
+ }
614
+
615
+ /* State overlay */
616
+ .state-overlay {
617
+ position: absolute;
618
+ inset: 0;
619
+ display: flex;
620
+ align-items: center;
621
+ justify-content: center;
622
+ }
623
+
624
+ /* Spinner */
625
+ @keyframes kr-spin {
626
+ to {
627
+ transform: rotate(360deg);
628
+ }
629
+ }
630
+
631
+ .spinner {
632
+ width: 12px;
633
+ height: 12px;
634
+ border: 2.5px solid currentColor;
635
+ border-top-color: transparent;
636
+ border-radius: 50%;
637
+ animation: kr-spin 0.6s linear infinite;
638
+ }
639
+
640
+ :host(.kr-button--small) .spinner {
641
+ width: 10px;
642
+ height: 10px;
643
+ border-width: 2px;
644
+ }
645
+
646
+ :host(.kr-button--large) .spinner {
647
+ width: 16px;
648
+ height: 16px;
649
+ border-width: 3px;
504
650
  }
505
651
  `;
506
652
  __decorate$5([
507
- n({ type: String })
653
+ n({ type: String, reflect: true })
508
654
  ], KRButton.prototype, "variant", void 0);
509
655
  __decorate$5([
510
- n({ type: String })
656
+ n({ type: String, reflect: true })
657
+ ], KRButton.prototype, "color", void 0);
658
+ __decorate$5([
659
+ n({ type: String, reflect: true })
511
660
  ], KRButton.prototype, "size", void 0);
512
661
  __decorate$5([
513
- n({ type: Boolean })
662
+ n({ type: Boolean, reflect: true })
514
663
  ], KRButton.prototype, "disabled", void 0);
664
+ __decorate$5([
665
+ r$1()
666
+ ], KRButton.prototype, "_state", void 0);
667
+ __decorate$5([
668
+ r$1()
669
+ ], KRButton.prototype, "_stateText", void 0);
515
670
  KRButton = __decorate$5([
516
671
  t$1('kr-button')
517
672
  ], KRButton);
@@ -1127,10 +1282,13 @@ var KRSnackbar_1;
1127
1282
  * @example
1128
1283
  * ```ts
1129
1284
  * // Show a success message
1130
- * KRSnackbar.show('Item saved successfully', { type: 'success' });
1285
+ * KRSnackbar.show({ message: 'Item saved successfully', type: 'success' });
1286
+ *
1287
+ * // Show a message with title
1288
+ * KRSnackbar.show({ title: 'Upload Complete', message: 'Your file has been uploaded.', type: 'success' });
1131
1289
  *
1132
1290
  * // Show an error that stays until manually dismissed
1133
- * KRSnackbar.show('Failed to save', { type: 'error', duration: 0 });
1291
+ * KRSnackbar.show({ message: 'Failed to save', type: 'error', duration: 0 });
1134
1292
  * ```
1135
1293
  */
1136
1294
  let KRSnackbar = KRSnackbar_1 = class KRSnackbar extends i$1 {
@@ -1141,6 +1299,10 @@ let KRSnackbar = KRSnackbar_1 = class KRSnackbar extends i$1 {
1141
1299
  * The snackbar type/severity. Controls the color scheme and icon.
1142
1300
  */
1143
1301
  this.type = 'info';
1302
+ /**
1303
+ * Optional title to display above the message.
1304
+ */
1305
+ this.title = '';
1144
1306
  /**
1145
1307
  * The message to display in the snackbar.
1146
1308
  */
@@ -1152,16 +1314,24 @@ let KRSnackbar = KRSnackbar_1 = class KRSnackbar extends i$1 {
1152
1314
  this.duration = 5000;
1153
1315
  }
1154
1316
  /**
1155
- * Show a snackbar with the given message and options.
1156
- * @param message - The message to display
1157
- * @param options - Optional configuration for type and duration
1317
+ * Show a snackbar with the given options.
1318
+ * @param options - Configuration including message, optional title, type, and duration
1158
1319
  * @returns The created snackbar element
1159
1320
  */
1160
- static show(message, options) {
1321
+ static show(options) {
1161
1322
  const snackbar = document.createElement('kr-snackbar');
1162
- snackbar.message = message;
1163
- snackbar.type = options?.type ?? 'info';
1164
- snackbar.duration = options?.duration ?? 5000;
1323
+ snackbar.message = options.message;
1324
+ snackbar.title = options.title ?? '';
1325
+ snackbar.type = options.type ?? 'info';
1326
+ if (options.duration !== undefined) {
1327
+ snackbar.duration = options.duration;
1328
+ }
1329
+ else if (snackbar.type === 'error') {
1330
+ snackbar.duration = 0;
1331
+ }
1332
+ else {
1333
+ snackbar.duration = 5000;
1334
+ }
1165
1335
  document.body.appendChild(snackbar);
1166
1336
  return snackbar;
1167
1337
  }
@@ -1170,6 +1340,9 @@ let KRSnackbar = KRSnackbar_1 = class KRSnackbar extends i$1 {
1170
1340
  // Set role and type class on host
1171
1341
  this.setAttribute('role', 'alert');
1172
1342
  this.classList.add(`kr-snackbar--${this.type}`);
1343
+ if (this.title) {
1344
+ this.classList.add('kr-snackbar--has-title');
1345
+ }
1173
1346
  // Add to active snackbars and update positions
1174
1347
  KRSnackbar_1.activeSnackbars.push(this);
1175
1348
  this.updatePositions();
@@ -1195,7 +1368,7 @@ let KRSnackbar = KRSnackbar_1 = class KRSnackbar extends i$1 {
1195
1368
  }
1196
1369
  }
1197
1370
  updatePositions() {
1198
- let bottomOffset = 16;
1371
+ let bottomOffset = 24;
1199
1372
  for (const snackbar of KRSnackbar_1.activeSnackbars) {
1200
1373
  snackbar.style.bottom = `${bottomOffset}px`;
1201
1374
  bottomOffset += snackbar.offsetHeight + 8;
@@ -1221,7 +1394,10 @@ let KRSnackbar = KRSnackbar_1 = class KRSnackbar extends i$1 {
1221
1394
  };
1222
1395
  return b `
1223
1396
  ${icons[this.type]}
1224
- <div class="message">${this.message}</div>
1397
+ <div class="content">
1398
+ ${this.title ? b `<div class="title">${this.title}</div>` : ''}
1399
+ <div class="message">${this.message}</div>
1400
+ </div>
1225
1401
  <button
1226
1402
  class="dismiss"
1227
1403
  type="button"
@@ -1261,6 +1437,10 @@ KRSnackbar.styles = i$4 `
1261
1437
  animation: slideIn 0.2s ease-out;
1262
1438
  }
1263
1439
 
1440
+ :host(.kr-snackbar--has-title) {
1441
+ align-items: flex-start;
1442
+ }
1443
+
1264
1444
  @keyframes slideIn {
1265
1445
  from {
1266
1446
  opacity: 0;
@@ -1332,10 +1512,22 @@ KRSnackbar.styles = i$4 `
1332
1512
  flex-shrink: 0;
1333
1513
  width: 20px;
1334
1514
  height: 20px;
1515
+ margin-top: 1px;
1335
1516
  }
1336
1517
 
1337
- .message {
1518
+ .content {
1338
1519
  flex: 1;
1520
+ min-width: 0;
1521
+ }
1522
+
1523
+ .title {
1524
+ font-size: 14px;
1525
+ font-weight: 600;
1526
+ color: #000000;
1527
+ margin: 0 0 2px 0;
1528
+ }
1529
+
1530
+ .message {
1339
1531
  font-size: 14px;
1340
1532
  color: #000000;
1341
1533
  margin: 0;
@@ -1372,6 +1564,9 @@ KRSnackbar.activeSnackbars = [];
1372
1564
  __decorate$1([
1373
1565
  n({ type: String })
1374
1566
  ], KRSnackbar.prototype, "type", void 0);
1567
+ __decorate$1([
1568
+ n({ type: String })
1569
+ ], KRSnackbar.prototype, "title", void 0);
1375
1570
  __decorate$1([
1376
1571
  n({ type: String })
1377
1572
  ], KRSnackbar.prototype, "message", void 0);