@kodaris/krubble-components 1.0.4 → 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.
@@ -402,16 +402,19 @@
402
402
  * A customizable button component.
403
403
  *
404
404
  * @slot - The button content
405
- * @csspart button - The button element
406
405
  * @fires click - Fired when the button is clicked
407
406
  */
408
407
  exports.KRButton = class KRButton extends i$1 {
409
408
  constructor() {
410
409
  super(...arguments);
411
410
  /**
412
- * The button variant style
411
+ * The button variant (shape)
413
412
  */
414
- this.variant = 'primary';
413
+ this.variant = 'flat';
414
+ /**
415
+ * The button color
416
+ */
417
+ this.color = 'primary';
415
418
  /**
416
419
  * The button size
417
420
  */
@@ -420,104 +423,256 @@
420
423
  * Whether the button is disabled
421
424
  */
422
425
  this.disabled = false;
426
+ this._state = 'idle';
427
+ this._stateText = '';
428
+ this._handleKeydown = (e) => {
429
+ if (e.key === 'Enter' || e.key === ' ') {
430
+ e.preventDefault();
431
+ this.click();
432
+ }
433
+ };
434
+ }
435
+ connectedCallback() {
436
+ super.connectedCallback();
437
+ this.setAttribute('role', 'button');
438
+ this.setAttribute('tabindex', '0');
439
+ this.addEventListener('keydown', this._handleKeydown);
440
+ }
441
+ disconnectedCallback() {
442
+ super.disconnectedCallback();
443
+ this.removeEventListener('keydown', this._handleKeydown);
444
+ }
445
+ /**
446
+ * Shows a loading spinner and disables the button.
447
+ */
448
+ showLoading() {
449
+ this._clearStateTimeout();
450
+ this._state = 'loading';
451
+ this._stateText = '';
452
+ }
453
+ /**
454
+ * Shows a success state with optional custom text.
455
+ * @param text - Text to display (default: "Saved")
456
+ * @param duration - Duration in ms before auto-reset (default: 2000)
457
+ */
458
+ showSuccess(text = 'Success', duration = 2000) {
459
+ this._clearStateTimeout();
460
+ this._state = 'success';
461
+ this._stateText = text;
462
+ this._stateTimeout = window.setTimeout(() => this.reset(), duration);
463
+ }
464
+ /**
465
+ * Shows an error state with optional custom text.
466
+ * @param text - Text to display (default: "Error")
467
+ * @param duration - Duration in ms before auto-reset (default: 2000)
468
+ */
469
+ showError(text = 'Error', duration = 2000) {
470
+ this._clearStateTimeout();
471
+ this._state = 'error';
472
+ this._stateText = text;
473
+ this._stateTimeout = window.setTimeout(() => this.reset(), duration);
474
+ }
475
+ /**
476
+ * Resets the button to its idle state.
477
+ */
478
+ reset() {
479
+ this._clearStateTimeout();
480
+ this._state = 'idle';
481
+ this._stateText = '';
482
+ }
483
+ _clearStateTimeout() {
484
+ if (this._stateTimeout) {
485
+ clearTimeout(this._stateTimeout);
486
+ this._stateTimeout = undefined;
487
+ }
488
+ }
489
+ updated(changedProperties) {
490
+ // Reflect state classes to host
491
+ this.classList.toggle('kr-button--loading', this._state === 'loading');
492
+ this.classList.toggle('kr-button--success', this._state === 'success');
493
+ this.classList.toggle('kr-button--error', this._state === 'error');
494
+ this.classList.toggle(`kr-button--${this.variant}`, true);
495
+ this.classList.toggle(`kr-button--${this.color}`, true);
496
+ this.classList.toggle('kr-button--small', this.size === 'small');
497
+ this.classList.toggle('kr-button--large', this.size === 'large');
423
498
  }
424
499
  render() {
425
- const sizeClass = this.size !== 'medium' ? this.size : '';
426
500
  return b `
427
- <button
428
- part="button"
429
- class="${this.variant} ${sizeClass}"
430
- ?disabled=${this.disabled}
431
- >
432
- <slot></slot>
433
- </button>
501
+ <slot></slot>
502
+ ${this._state !== 'idle'
503
+ ? b `<span class="state-overlay">
504
+ ${this._state === 'loading'
505
+ ? b `<span class="spinner"></span>`
506
+ : this._stateText}
507
+ </span>`
508
+ : ''}
434
509
  `;
435
510
  }
436
511
  };
437
512
  exports.KRButton.styles = i$4 `
438
513
  :host {
439
- display: inline-block;
440
- }
441
-
442
- button {
514
+ display: inline-flex;
515
+ position: relative;
516
+ align-items: center;
517
+ justify-content: center;
443
518
  font-family: inherit;
444
519
  font-size: 14px;
445
520
  font-weight: 400;
446
521
  letter-spacing: 0.01em;
447
522
  padding: 0 22px;
448
523
  height: 36px;
449
- line-height: 36px;
450
524
  border: none;
451
525
  border-radius: 20px;
452
526
  cursor: pointer;
453
527
  transition: background 0.15s ease;
528
+ user-select: none;
529
+ box-sizing: border-box;
454
530
  }
455
531
 
456
- button:active:not(:disabled) {
457
- transform: scale(0.98);
532
+ :host([disabled]),
533
+ :host(.kr-button--loading),
534
+ :host(.kr-button--success),
535
+ :host(.kr-button--error) {
536
+ cursor: not-allowed;
537
+ pointer-events: none;
458
538
  }
459
539
 
460
- button:disabled {
540
+ :host([disabled]:not(.kr-button--loading):not(.kr-button--success):not(.kr-button--error)) {
461
541
  opacity: 0.5;
462
- cursor: not-allowed;
463
542
  }
464
543
 
465
- /* Variants */
466
- button.primary {
544
+ /* Flat + Primary (default) */
545
+ :host,
546
+ :host(.kr-button--flat.kr-button--primary) {
467
547
  background: #163052;
468
548
  color: white;
469
549
  }
470
550
 
471
- button.primary:hover:not(:disabled) {
551
+ :host(:hover:not([disabled])),
552
+ :host(.kr-button--flat.kr-button--primary:hover:not([disabled])) {
472
553
  background: #0e1f35;
473
554
  }
474
555
 
475
- button.secondary {
556
+ /* Flat + Secondary */
557
+ :host(.kr-button--flat.kr-button--secondary) {
476
558
  background: #f3f4f6;
477
559
  color: #374151;
478
560
  }
479
561
 
480
- button.secondary:hover:not(:disabled) {
562
+ :host(.kr-button--flat.kr-button--secondary:hover:not([disabled])) {
481
563
  background: #e5e7eb;
482
564
  }
483
565
 
484
- button.outline {
566
+ /* Outline + Primary */
567
+ :host(.kr-button--outline.kr-button--primary) {
568
+ background: transparent;
569
+ border: 1px solid #163052;
570
+ color: #163052;
571
+ }
572
+
573
+ :host(.kr-button--outline.kr-button--primary:hover:not([disabled])) {
574
+ background: rgba(22, 48, 82, 0.05);
575
+ }
576
+
577
+ /* Outline + Secondary */
578
+ :host(.kr-button--outline.kr-button--secondary) {
485
579
  background: transparent;
486
580
  border: 1px solid #d1d5db;
487
581
  color: #374151;
488
- box-shadow: none;
489
582
  }
490
583
 
491
- button.outline:hover:not(:disabled) {
584
+ :host(.kr-button--outline.kr-button--secondary:hover:not([disabled])) {
492
585
  background: #f9fafb;
493
586
  border-color: #9ca3af;
494
- box-shadow: none;
495
587
  }
496
588
 
497
589
  /* Sizes */
498
- button.small {
590
+ :host(.kr-button--small) {
499
591
  font-size: 13px;
500
592
  height: 28px;
501
- line-height: 28px;
502
593
  padding: 0 16px;
503
594
  }
504
595
 
505
- button.large {
506
- font-size: 1rem;
596
+ :host(.kr-button--large) {
597
+ font-size: 16px;
507
598
  height: 44px;
508
- line-height: 44px;
509
599
  padding: 0 24px;
600
+ border-radius: 30px;
601
+ }
602
+
603
+ /* State colors */
604
+ :host(.kr-button--success) {
605
+ background: #198754 !important;
606
+ color: white !important;
607
+ }
608
+
609
+ :host(.kr-button--error) {
610
+ background: rgb(220, 53, 69) !important;
611
+ color: white !important;
612
+ }
613
+
614
+ /* Content */
615
+ :host(.kr-button--loading) slot,
616
+ :host(.kr-button--success) slot,
617
+ :host(.kr-button--error) slot {
618
+ visibility: hidden;
619
+ }
620
+
621
+ /* State overlay */
622
+ .state-overlay {
623
+ position: absolute;
624
+ inset: 0;
625
+ display: flex;
626
+ align-items: center;
627
+ justify-content: center;
628
+ }
629
+
630
+ /* Spinner */
631
+ @keyframes kr-spin {
632
+ to {
633
+ transform: rotate(360deg);
634
+ }
635
+ }
636
+
637
+ .spinner {
638
+ width: 12px;
639
+ height: 12px;
640
+ border: 2.5px solid currentColor;
641
+ border-top-color: transparent;
642
+ border-radius: 50%;
643
+ animation: kr-spin 0.6s linear infinite;
644
+ }
645
+
646
+ :host(.kr-button--small) .spinner {
647
+ width: 10px;
648
+ height: 10px;
649
+ border-width: 2px;
650
+ }
651
+
652
+ :host(.kr-button--large) .spinner {
653
+ width: 16px;
654
+ height: 16px;
655
+ border-width: 3px;
510
656
  }
511
657
  `;
512
658
  __decorate$5([
513
- n({ type: String })
659
+ n({ type: String, reflect: true })
514
660
  ], exports.KRButton.prototype, "variant", void 0);
515
661
  __decorate$5([
516
- n({ type: String })
662
+ n({ type: String, reflect: true })
663
+ ], exports.KRButton.prototype, "color", void 0);
664
+ __decorate$5([
665
+ n({ type: String, reflect: true })
517
666
  ], exports.KRButton.prototype, "size", void 0);
518
667
  __decorate$5([
519
- n({ type: Boolean })
668
+ n({ type: Boolean, reflect: true })
520
669
  ], exports.KRButton.prototype, "disabled", void 0);
670
+ __decorate$5([
671
+ r$1()
672
+ ], exports.KRButton.prototype, "_state", void 0);
673
+ __decorate$5([
674
+ r$1()
675
+ ], exports.KRButton.prototype, "_stateText", void 0);
521
676
  exports.KRButton = __decorate$5([
522
677
  t$1('kr-button')
523
678
  ], exports.KRButton);
@@ -1191,6 +1346,9 @@
1191
1346
  // Set role and type class on host
1192
1347
  this.setAttribute('role', 'alert');
1193
1348
  this.classList.add(`kr-snackbar--${this.type}`);
1349
+ if (this.title) {
1350
+ this.classList.add('kr-snackbar--has-title');
1351
+ }
1194
1352
  // Add to active snackbars and update positions
1195
1353
  KRSnackbar_1.activeSnackbars.push(this);
1196
1354
  this.updatePositions();
@@ -1272,7 +1430,7 @@
1272
1430
  left: 16px;
1273
1431
  z-index: 10000;
1274
1432
  display: flex;
1275
- align-items: flex-start;
1433
+ align-items: center;
1276
1434
  gap: 12px;
1277
1435
  padding: 14px 12px 14px 16px;
1278
1436
  border-radius: 8px;
@@ -1285,6 +1443,10 @@
1285
1443
  animation: slideIn 0.2s ease-out;
1286
1444
  }
1287
1445
 
1446
+ :host(.kr-snackbar--has-title) {
1447
+ align-items: flex-start;
1448
+ }
1449
+
1288
1450
  @keyframes slideIn {
1289
1451
  from {
1290
1452
  opacity: 0;