@tinybigui/react 0.4.1 → 0.4.2
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/dist/index.cjs +272 -253
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +187 -99
- package/dist/index.d.ts +187 -99
- package/dist/index.js +273 -256
- package/dist/index.js.map +1 -1
- package/dist/styles.css +2 -1
- package/dist/styles.css.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -426,218 +426,206 @@ var ButtonHeadless = React.forwardRef(
|
|
|
426
426
|
ButtonHeadless.displayName = "ButtonHeadless";
|
|
427
427
|
var buttonVariants = classVarianceAuthority.cva(
|
|
428
428
|
[
|
|
429
|
-
//
|
|
430
|
-
"relative inline-flex items-center justify-center
|
|
431
|
-
"
|
|
432
|
-
// Split MD3 transition: spatial (border-radius)
|
|
433
|
-
// effects (color/bg/shadow)
|
|
429
|
+
// Layout + shape — NO overflow-hidden here (see note above)
|
|
430
|
+
"relative inline-flex items-center justify-center",
|
|
431
|
+
"rounded-full cursor-pointer select-none",
|
|
432
|
+
// Split MD3 transition: spatial (border-radius) → expressive spring;
|
|
433
|
+
// effects (color/bg/shadow) → standard effects spring.
|
|
434
434
|
"btn-transition",
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
"focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
435
|
+
// Disabled — self-targeting data-[x]: selectors
|
|
436
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none"
|
|
438
437
|
],
|
|
439
438
|
{
|
|
440
439
|
variants: {
|
|
441
440
|
/**
|
|
442
|
-
* Button variant (MD3 specification)
|
|
441
|
+
* Button variant (MD3 specification — strict, no color override)
|
|
442
|
+
*
|
|
443
|
+
* Elevation per state follows _md-comp-*-button.scss tokens:
|
|
444
|
+
* Filled/Tonal hover→level-1, focus/pressed→level-0
|
|
445
|
+
* Elevated base→level-1, hover→level-2, focus/pressed→level-1
|
|
446
|
+
* Outlined/Text no elevation
|
|
443
447
|
*/
|
|
444
448
|
variant: {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Filled — highest emphasis.
|
|
451
|
+
* MD3: container=primary, label=on-primary, state-layer=on-primary
|
|
452
|
+
* Elevation: 0 base → 1 hover → 0 focus → 0 pressed
|
|
453
|
+
*/
|
|
454
|
+
filled: [
|
|
455
|
+
"bg-primary text-on-primary shadow-none",
|
|
456
|
+
// Hover: gains level-1 elevation
|
|
457
|
+
"group-data-[hovered]/button:shadow-elevation-1",
|
|
458
|
+
// Focus/pressed: shadow must explicitly return to level-0
|
|
459
|
+
// (doubled attribute selector → higher specificity than hover)
|
|
460
|
+
"group-data-[focus-visible]/button:shadow-none",
|
|
461
|
+
"group-data-[pressed]/button:group-data-[pressed]/button:shadow-none",
|
|
462
|
+
// Disabled overrides
|
|
463
|
+
"group-data-[disabled]/button:bg-on-surface/12",
|
|
464
|
+
"group-data-[disabled]/button:text-on-surface/38",
|
|
465
|
+
"group-data-[disabled]/button:shadow-none"
|
|
466
|
+
],
|
|
467
|
+
/**
|
|
468
|
+
* Outlined — medium emphasis. Transparent with border.
|
|
469
|
+
* MD3: container=transparent, outline=outline, label=primary, state-layer=primary
|
|
470
|
+
* Elevation: always 0
|
|
471
|
+
*/
|
|
472
|
+
outlined: [
|
|
473
|
+
"bg-transparent border border-outline text-primary",
|
|
474
|
+
// Disabled overrides
|
|
475
|
+
"group-data-[disabled]/button:border-on-surface/12",
|
|
476
|
+
"group-data-[disabled]/button:text-on-surface/38"
|
|
477
|
+
],
|
|
478
|
+
/**
|
|
479
|
+
* Tonal — secondary emphasis.
|
|
480
|
+
* MD3 name: "Filled tonal". container=secondary-container, label=on-secondary-container
|
|
481
|
+
* Elevation: 0 base → 1 hover → 0 focus → 0 pressed
|
|
482
|
+
*/
|
|
483
|
+
tonal: [
|
|
484
|
+
"bg-secondary-container text-on-secondary-container shadow-none",
|
|
485
|
+
// Hover: gains level-1 elevation (same as filled)
|
|
486
|
+
"group-data-[hovered]/button:shadow-elevation-1",
|
|
487
|
+
// Focus/pressed: return to level-0
|
|
488
|
+
"group-data-[focus-visible]/button:shadow-none",
|
|
489
|
+
"group-data-[pressed]/button:group-data-[pressed]/button:shadow-none",
|
|
490
|
+
// Disabled overrides
|
|
491
|
+
"group-data-[disabled]/button:bg-on-surface/12",
|
|
492
|
+
"group-data-[disabled]/button:text-on-surface/38",
|
|
493
|
+
"group-data-[disabled]/button:shadow-none"
|
|
494
|
+
],
|
|
495
|
+
/**
|
|
496
|
+
* Elevated — separation via shadow.
|
|
497
|
+
* MD3: container=surface-container-low, label=primary
|
|
498
|
+
* Elevation: 1 base → 2 hover → 1 focus → 1 pressed
|
|
499
|
+
*/
|
|
500
|
+
elevated: [
|
|
501
|
+
"bg-surface-container-low text-primary shadow-elevation-1",
|
|
502
|
+
// Hover: gains extra elevation
|
|
503
|
+
"group-data-[hovered]/button:shadow-elevation-2",
|
|
504
|
+
// Focus/pressed: return to base level-1
|
|
505
|
+
// (doubled selector wins over single hover selector at same cascade position)
|
|
506
|
+
"group-data-[focus-visible]/button:shadow-elevation-1",
|
|
507
|
+
"group-data-[pressed]/button:group-data-[pressed]/button:shadow-elevation-1",
|
|
508
|
+
// Disabled overrides
|
|
509
|
+
"group-data-[disabled]/button:bg-on-surface/12",
|
|
510
|
+
"group-data-[disabled]/button:text-on-surface/38",
|
|
511
|
+
"group-data-[disabled]/button:shadow-none"
|
|
512
|
+
],
|
|
513
|
+
/**
|
|
514
|
+
* Text — lowest emphasis.
|
|
515
|
+
* MD3: container=transparent, label=primary, state-layer=primary
|
|
516
|
+
* Elevation: always 0
|
|
517
|
+
*/
|
|
518
|
+
text: [
|
|
519
|
+
"bg-transparent text-primary",
|
|
520
|
+
// Disabled overrides
|
|
521
|
+
"group-data-[disabled]/button:text-on-surface/38"
|
|
522
|
+
]
|
|
461
523
|
},
|
|
462
524
|
/**
|
|
463
525
|
* Button size
|
|
526
|
+
* MD3 spec: small=32dp, medium=40dp, large=56dp
|
|
527
|
+
* Padding: small=16dp, medium=24dp, large=32dp
|
|
528
|
+
* Text variant uses reduced padding: small=12dp, medium=12dp
|
|
464
529
|
*/
|
|
465
530
|
size: {
|
|
466
|
-
small: "h-8 px-4 text-
|
|
467
|
-
medium: "h-10 px-6 text-
|
|
468
|
-
large: "h-
|
|
531
|
+
small: "h-8 px-4 gap-1 text-label-medium tracking-[0.1px]",
|
|
532
|
+
medium: "h-10 px-6 gap-2 text-label-large tracking-[0.1px]",
|
|
533
|
+
large: "h-14 px-8 gap-2 text-title-medium"
|
|
469
534
|
},
|
|
470
535
|
/**
|
|
471
|
-
* Full width
|
|
536
|
+
* Full width button (spans container)
|
|
472
537
|
*/
|
|
473
538
|
fullWidth: {
|
|
474
539
|
true: "w-full",
|
|
475
540
|
false: ""
|
|
476
|
-
},
|
|
477
|
-
/**
|
|
478
|
-
* Disabled state (MD3 spec: container 12% opacity, content 38% opacity)
|
|
479
|
-
*/
|
|
480
|
-
disabled: {
|
|
481
|
-
true: [
|
|
482
|
-
"pointer-events-none cursor-not-allowed",
|
|
483
|
-
"bg-on-surface/12",
|
|
484
|
-
// MD3: disabled container uses on-surface at 12%
|
|
485
|
-
"text-on-surface/38",
|
|
486
|
-
// MD3: disabled text/icons use on-surface at 38%
|
|
487
|
-
"border-on-surface/12",
|
|
488
|
-
// For outlined variant
|
|
489
|
-
"shadow-none"
|
|
490
|
-
// Remove elevation when disabled
|
|
491
|
-
],
|
|
492
|
-
false: ""
|
|
493
|
-
},
|
|
494
|
-
/**
|
|
495
|
-
* Loading state
|
|
496
|
-
*/
|
|
497
|
-
loading: {
|
|
498
|
-
true: "cursor-wait",
|
|
499
|
-
false: ""
|
|
500
541
|
}
|
|
501
542
|
},
|
|
502
543
|
/**
|
|
503
|
-
* Compound variants
|
|
544
|
+
* Compound variants for text variant reduced padding per size
|
|
545
|
+
* MD3: text buttons use 12dp padding (px-3) instead of standard padding
|
|
504
546
|
*/
|
|
505
547
|
compoundVariants: [
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
{
|
|
510
|
-
variant: "filled",
|
|
511
|
-
color: "primary",
|
|
512
|
-
className: "bg-primary text-on-primary"
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
variant: "filled",
|
|
516
|
-
color: "secondary",
|
|
517
|
-
className: "bg-secondary text-on-secondary"
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
variant: "filled",
|
|
521
|
-
color: "tertiary",
|
|
522
|
-
className: "bg-tertiary text-on-tertiary"
|
|
523
|
-
},
|
|
524
|
-
{
|
|
525
|
-
variant: "filled",
|
|
526
|
-
color: "error",
|
|
527
|
-
className: "bg-error text-on-error"
|
|
528
|
-
},
|
|
529
|
-
// ====================
|
|
530
|
-
// OUTLINED VARIANTS
|
|
531
|
-
// ====================
|
|
532
|
-
{
|
|
533
|
-
variant: "outlined",
|
|
534
|
-
color: "primary",
|
|
535
|
-
className: "text-primary"
|
|
536
|
-
},
|
|
537
|
-
{
|
|
538
|
-
variant: "outlined",
|
|
539
|
-
color: "secondary",
|
|
540
|
-
className: "text-secondary"
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
variant: "outlined",
|
|
544
|
-
color: "tertiary",
|
|
545
|
-
className: "text-tertiary"
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
variant: "outlined",
|
|
549
|
-
color: "error",
|
|
550
|
-
className: "text-error"
|
|
551
|
-
},
|
|
552
|
-
// ====================
|
|
553
|
-
// TONAL VARIANTS
|
|
554
|
-
// ====================
|
|
555
|
-
{
|
|
556
|
-
variant: "tonal",
|
|
557
|
-
color: "primary",
|
|
558
|
-
className: "bg-primary-container text-on-primary-container"
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
variant: "tonal",
|
|
562
|
-
color: "secondary",
|
|
563
|
-
className: "bg-secondary-container text-on-secondary-container"
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
variant: "tonal",
|
|
567
|
-
color: "tertiary",
|
|
568
|
-
className: "bg-tertiary-container text-on-tertiary-container"
|
|
569
|
-
},
|
|
570
|
-
{
|
|
571
|
-
variant: "tonal",
|
|
572
|
-
color: "error",
|
|
573
|
-
className: "bg-error-container text-on-error-container"
|
|
574
|
-
},
|
|
575
|
-
// ====================
|
|
576
|
-
// ELEVATED VARIANTS
|
|
577
|
-
// ====================
|
|
578
|
-
{
|
|
579
|
-
variant: "elevated",
|
|
580
|
-
color: "primary",
|
|
581
|
-
className: "bg-surface-container-low text-primary"
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
variant: "elevated",
|
|
585
|
-
color: "secondary",
|
|
586
|
-
className: "bg-surface-container-low text-secondary"
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
variant: "elevated",
|
|
590
|
-
color: "tertiary",
|
|
591
|
-
className: "bg-surface-container-low text-tertiary"
|
|
592
|
-
},
|
|
593
|
-
{
|
|
594
|
-
variant: "elevated",
|
|
595
|
-
color: "error",
|
|
596
|
-
className: "bg-surface-container-low text-error"
|
|
597
|
-
},
|
|
598
|
-
// ====================
|
|
599
|
-
// TEXT VARIANTS
|
|
600
|
-
// ====================
|
|
601
|
-
{
|
|
602
|
-
variant: "text",
|
|
603
|
-
color: "primary",
|
|
604
|
-
className: "text-primary hover:bg-primary/[0.08]"
|
|
605
|
-
// MD3: text buttons gain primary color at 8% opacity on hover
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
variant: "text",
|
|
609
|
-
color: "secondary",
|
|
610
|
-
className: "text-secondary hover:bg-secondary/[0.08]"
|
|
611
|
-
// MD3: text buttons gain secondary color at 8% opacity on hover
|
|
612
|
-
},
|
|
613
|
-
{
|
|
614
|
-
variant: "text",
|
|
615
|
-
color: "tertiary",
|
|
616
|
-
className: "text-tertiary hover:bg-tertiary/[0.08]"
|
|
617
|
-
// MD3: text buttons gain tertiary color at 8% opacity on hover
|
|
618
|
-
},
|
|
619
|
-
{
|
|
620
|
-
variant: "text",
|
|
621
|
-
color: "error",
|
|
622
|
-
className: "text-error hover:bg-error/[0.08]"
|
|
623
|
-
// MD3: text buttons gain error color at 8% opacity on hover
|
|
624
|
-
}
|
|
548
|
+
{ variant: "text", size: "small", className: "px-3" },
|
|
549
|
+
{ variant: "text", size: "medium", className: "px-3" },
|
|
550
|
+
{ variant: "text", size: "large", className: "px-4" }
|
|
625
551
|
],
|
|
626
|
-
/**
|
|
627
|
-
* Default variants
|
|
628
|
-
*/
|
|
629
552
|
defaultVariants: {
|
|
630
553
|
variant: "filled",
|
|
631
|
-
color: "primary",
|
|
632
554
|
size: "medium",
|
|
633
|
-
fullWidth: false
|
|
634
|
-
disabled: false,
|
|
635
|
-
loading: false
|
|
555
|
+
fullWidth: false
|
|
636
556
|
}
|
|
637
557
|
}
|
|
638
558
|
);
|
|
559
|
+
var buttonStateLayerVariants = classVarianceAuthority.cva(
|
|
560
|
+
[
|
|
561
|
+
"absolute inset-0 rounded-[inherit] overflow-hidden pointer-events-none opacity-0",
|
|
562
|
+
// Effects transition for opacity — standard spring, no overshoot
|
|
563
|
+
"transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
|
|
564
|
+
// Hover: 8%
|
|
565
|
+
"group-data-[hovered]/button:opacity-8",
|
|
566
|
+
// Focus: 10%
|
|
567
|
+
"group-data-[focus-visible]/button:opacity-10",
|
|
568
|
+
// Pressed: 10%, doubled selector wins over hover
|
|
569
|
+
"group-data-[pressed]/button:group-data-[pressed]/button:opacity-10",
|
|
570
|
+
// No state layer when disabled
|
|
571
|
+
"group-data-[disabled]/button:hidden"
|
|
572
|
+
],
|
|
573
|
+
{
|
|
574
|
+
variants: {
|
|
575
|
+
variant: {
|
|
576
|
+
filled: "bg-on-primary",
|
|
577
|
+
outlined: "bg-primary",
|
|
578
|
+
tonal: "bg-on-secondary-container",
|
|
579
|
+
elevated: "bg-primary",
|
|
580
|
+
text: "bg-primary"
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
defaultVariants: { variant: "filled" }
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
var buttonFocusRingVariants = classVarianceAuthority.cva([
|
|
587
|
+
"pointer-events-none absolute inset-[-3px] rounded-full",
|
|
588
|
+
"outline outline-2 outline-offset-0 outline-secondary",
|
|
589
|
+
// Effects transition — opacity change must not overshoot
|
|
590
|
+
"transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
|
|
591
|
+
"opacity-0",
|
|
592
|
+
"group-data-[focus-visible]/button:opacity-100"
|
|
593
|
+
]);
|
|
594
|
+
var buttonIconVariants = classVarianceAuthority.cva(
|
|
595
|
+
[
|
|
596
|
+
"relative z-10 inline-flex shrink-0 items-center justify-center",
|
|
597
|
+
"size-[18px]",
|
|
598
|
+
// Color transition uses effects token (no spatial overshoot on color)
|
|
599
|
+
"transition-colors duration-spring-standard-fast-effects ease-spring-standard-fast-effects"
|
|
600
|
+
],
|
|
601
|
+
{
|
|
602
|
+
variants: {
|
|
603
|
+
hidden: {
|
|
604
|
+
true: "invisible",
|
|
605
|
+
false: ""
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
defaultVariants: { hidden: false }
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
var buttonLabelVariants = classVarianceAuthority.cva(["relative z-10 inline-flex items-center"]);
|
|
612
|
+
var QUERY = "(prefers-reduced-motion: reduce)";
|
|
613
|
+
function useReducedMotion() {
|
|
614
|
+
const [reduced, setReduced] = React.useState(() => {
|
|
615
|
+
if (typeof window === "undefined") return false;
|
|
616
|
+
return window.matchMedia(QUERY).matches;
|
|
617
|
+
});
|
|
618
|
+
React.useEffect(() => {
|
|
619
|
+
const mql = window.matchMedia(QUERY);
|
|
620
|
+
const handler = (e) => setReduced(e.matches);
|
|
621
|
+
mql.addEventListener("change", handler);
|
|
622
|
+
return () => mql.removeEventListener("change", handler);
|
|
623
|
+
}, []);
|
|
624
|
+
return reduced;
|
|
625
|
+
}
|
|
639
626
|
function useRipple(options = {}) {
|
|
640
627
|
const { disabled = false, color = "currentColor", duration = 450 } = options;
|
|
628
|
+
const prefersReducedMotion = useReducedMotion();
|
|
641
629
|
const [ripples, setRipples] = React.useState([]);
|
|
642
630
|
const rippleKeyCounter = React.useRef(0);
|
|
643
631
|
const timersRef = React.useRef([]);
|
|
@@ -648,7 +636,7 @@ function useRipple(options = {}) {
|
|
|
648
636
|
}, []);
|
|
649
637
|
const onMouseDown = React.useCallback(
|
|
650
638
|
(event) => {
|
|
651
|
-
if (disabled) return;
|
|
639
|
+
if (disabled || prefersReducedMotion) return;
|
|
652
640
|
const element = event.currentTarget;
|
|
653
641
|
const rect = element.getBoundingClientRect();
|
|
654
642
|
const x = event.clientX - rect.left;
|
|
@@ -664,9 +652,9 @@ function useRipple(options = {}) {
|
|
|
664
652
|
}, duration);
|
|
665
653
|
timersRef.current.push(timer);
|
|
666
654
|
},
|
|
667
|
-
[disabled, duration]
|
|
655
|
+
[disabled, duration, prefersReducedMotion]
|
|
668
656
|
);
|
|
669
|
-
const rippleElements = disabled ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
657
|
+
const rippleElements = disabled || prefersReducedMotion ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
670
658
|
"span",
|
|
671
659
|
{
|
|
672
660
|
"data-ripple-container": true,
|
|
@@ -674,7 +662,7 @@ function useRipple(options = {}) {
|
|
|
674
662
|
children: ripples.map((ripple) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
675
663
|
"span",
|
|
676
664
|
{
|
|
677
|
-
className: "animate-ripple absolute rounded-full opacity-12",
|
|
665
|
+
className: "animate-md-ripple absolute rounded-full opacity-12",
|
|
678
666
|
style: {
|
|
679
667
|
left: ripple.x,
|
|
680
668
|
top: ripple.y,
|
|
@@ -766,7 +754,7 @@ var Spinner = () => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
|
766
754
|
{
|
|
767
755
|
role: "progressbar",
|
|
768
756
|
"aria-label": "Loading",
|
|
769
|
-
className: "h-
|
|
757
|
+
className: "relative z-10 h-[18px] w-[18px] animate-spin",
|
|
770
758
|
xmlns: "http://www.w3.org/2000/svg",
|
|
771
759
|
fill: "none",
|
|
772
760
|
viewBox: "0 0 24 24",
|
|
@@ -787,7 +775,6 @@ var Button = React.forwardRef(
|
|
|
787
775
|
({
|
|
788
776
|
// Variant props (CVA)
|
|
789
777
|
variant = "filled",
|
|
790
|
-
color = "primary",
|
|
791
778
|
size = "medium",
|
|
792
779
|
fullWidth = false,
|
|
793
780
|
// Content props
|
|
@@ -800,64 +787,84 @@ var Button = React.forwardRef(
|
|
|
800
787
|
isDisabled = false,
|
|
801
788
|
// Styling
|
|
802
789
|
className,
|
|
803
|
-
// Other props
|
|
790
|
+
// Other button props
|
|
804
791
|
tabIndex = 0,
|
|
805
792
|
type = "button",
|
|
806
|
-
|
|
793
|
+
// Passed through to ButtonHeadless → useButton
|
|
807
794
|
...props
|
|
808
795
|
}, ref) => {
|
|
796
|
+
const buttonRef = React.useRef(null);
|
|
797
|
+
const resolvedRef = ref ?? buttonRef;
|
|
809
798
|
const groupCtx = useOptionalButtonGroup();
|
|
810
799
|
const isConnected = groupCtx?.variant === "connected";
|
|
811
|
-
if (process.env.NODE_ENV === "development") {
|
|
812
|
-
if (!children) {
|
|
813
|
-
console.warn(
|
|
814
|
-
"[Button] Button should have text content. Use IconButton for icon-only buttons."
|
|
815
|
-
);
|
|
816
|
-
}
|
|
817
|
-
if (icon && trailingIcon) {
|
|
818
|
-
console.warn("[Button] Button should have either icon or trailingIcon, not both.");
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
800
|
const isButtonDisabled = isDisabled || loading;
|
|
801
|
+
const [isPressed, setIsPressed] = React.useState(false);
|
|
802
|
+
const handlePressStart = React.useCallback(() => setIsPressed(true), []);
|
|
803
|
+
const handlePressEnd = React.useCallback(() => setIsPressed(false), []);
|
|
804
|
+
const { isHovered, hoverProps } = reactAria.useHover({ isDisabled: isButtonDisabled });
|
|
805
|
+
const { isFocusVisible, focusProps } = reactAria.useFocusRing();
|
|
822
806
|
const { onMouseDown: handleRipple, ripples } = useRipple({
|
|
823
807
|
disabled: isButtonDisabled || disableRipple
|
|
824
808
|
});
|
|
809
|
+
const buttonValue = props.value;
|
|
810
|
+
const isGroupSelected = isConnected && groupCtx && buttonValue ? groupCtx.selectedValues.has(buttonValue) : false;
|
|
825
811
|
const connectedClasses = isConnected && groupCtx ? [
|
|
826
|
-
...getConnectedRadiusClasses(groupCtx,
|
|
812
|
+
...getConnectedRadiusClasses(groupCtx, buttonValue),
|
|
827
813
|
groupCtx.enforceMinWidth ? "min-w-12" : ""
|
|
828
814
|
] : [];
|
|
815
|
+
const hasIcon = !!icon || !!trailingIcon;
|
|
816
|
+
if (process.env.NODE_ENV === "development") {
|
|
817
|
+
if (!children) {
|
|
818
|
+
console.warn(
|
|
819
|
+
"[Button] Button should have text content. Use IconButton for icon-only buttons."
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
829
823
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
830
824
|
ButtonHeadless,
|
|
831
825
|
{
|
|
832
|
-
...
|
|
833
|
-
|
|
826
|
+
...reactAria.mergeProps(
|
|
827
|
+
hoverProps,
|
|
828
|
+
focusProps,
|
|
829
|
+
// Track pressed state via useButton's press lifecycle callbacks,
|
|
830
|
+
// rather than a separate usePress hook, to avoid event handler conflicts.
|
|
831
|
+
{ onPressStart: handlePressStart, onPressEnd: handlePressEnd },
|
|
832
|
+
props
|
|
833
|
+
),
|
|
834
|
+
ref: resolvedRef,
|
|
834
835
|
type,
|
|
835
836
|
isDisabled: isButtonDisabled,
|
|
836
|
-
...onPress && { onPress },
|
|
837
837
|
tabIndex,
|
|
838
838
|
onMouseDown: handleRipple,
|
|
839
|
+
...getInteractionDataAttributes({
|
|
840
|
+
isHovered,
|
|
841
|
+
isFocusVisible,
|
|
842
|
+
isPressed,
|
|
843
|
+
isDisabled: isButtonDisabled
|
|
844
|
+
}),
|
|
839
845
|
"data-variant": variant,
|
|
840
|
-
"data-
|
|
846
|
+
"data-with-icon": hasIcon ? "" : void 0,
|
|
847
|
+
"data-loading": loading ? "" : void 0,
|
|
848
|
+
"data-group-selected": isGroupSelected ? "" : void 0,
|
|
841
849
|
className: cn(
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
disabled: isButtonDisabled,
|
|
849
|
-
loading
|
|
850
|
-
}),
|
|
850
|
+
buttonVariants({ variant, size, fullWidth }),
|
|
851
|
+
// group/button: enables group-data-[x]/button child selectors in all slots
|
|
852
|
+
// (added here, not in CVA, following the Switch pattern)
|
|
853
|
+
"group/button",
|
|
854
|
+
// Asymmetric border-radius easing: expressive when selected, decelerate when not
|
|
855
|
+
isGroupSelected ? "btn-transition-selected" : "",
|
|
851
856
|
...connectedClasses,
|
|
852
857
|
// User custom classes
|
|
853
858
|
className
|
|
854
859
|
),
|
|
855
860
|
children: [
|
|
856
861
|
ripples,
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className:
|
|
860
|
-
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(buttonStateLayerVariants({ variant })), "aria-hidden": "true" }),
|
|
863
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(buttonFocusRingVariants()), "aria-hidden": "true" }),
|
|
864
|
+
icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(buttonIconVariants({ hidden: loading })), children: icon }),
|
|
865
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
|
|
866
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(buttonLabelVariants()), children }),
|
|
867
|
+
trailingIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(buttonIconVariants({ hidden: loading })), children: trailingIcon })
|
|
861
868
|
]
|
|
862
869
|
}
|
|
863
870
|
);
|
|
@@ -871,6 +878,7 @@ var ButtonGroupHeadless = React.forwardRef(
|
|
|
871
878
|
size = "medium",
|
|
872
879
|
shape = "round",
|
|
873
880
|
selectionMode,
|
|
881
|
+
isDisabled = false,
|
|
874
882
|
// Selection — controlled
|
|
875
883
|
selectedValues: controlledValues,
|
|
876
884
|
onSelectionChange: onControlledChange,
|
|
@@ -892,7 +900,7 @@ var ButtonGroupHeadless = React.forwardRef(
|
|
|
892
900
|
const isControlled = controlledValues !== void 0;
|
|
893
901
|
const selectedValues = isControlled ? controlledValues : uncontrolledValues;
|
|
894
902
|
const handleSelectionChange = (value) => {
|
|
895
|
-
if (!selectionMode) return;
|
|
903
|
+
if (!selectionMode || isDisabled) return;
|
|
896
904
|
let nextValues;
|
|
897
905
|
if (selectionMode === "multi") {
|
|
898
906
|
nextValues = new Set(selectedValues);
|
|
@@ -933,6 +941,7 @@ var ButtonGroupHeadless = React.forwardRef(
|
|
|
933
941
|
selectionMode,
|
|
934
942
|
selectedValues,
|
|
935
943
|
onSelectionChange: handleSelectionChange,
|
|
944
|
+
isDisabled,
|
|
936
945
|
connectedInnerRadius: getInnerRadius(size),
|
|
937
946
|
connectedOuterRadius: getOuterRadius(shape, size),
|
|
938
947
|
enforceMinWidth: variant === "connected" && (size === "extra-small" || size === "small")
|
|
@@ -945,9 +954,15 @@ var ButtonGroupHeadless = React.forwardRef(
|
|
|
945
954
|
}
|
|
946
955
|
);
|
|
947
956
|
ButtonGroupHeadless.displayName = "ButtonGroupHeadless";
|
|
948
|
-
var
|
|
949
|
-
|
|
950
|
-
|
|
957
|
+
var buttonGroupRootVariants = classVarianceAuthority.cva(
|
|
958
|
+
[
|
|
959
|
+
// Layout
|
|
960
|
+
"items-center",
|
|
961
|
+
// Spatial motion for gap changes — standard spring (calm, utility UI)
|
|
962
|
+
"transition-[gap] duration-spring-standard-fast-spatial ease-spring-standard-fast-spatial",
|
|
963
|
+
// Disabled state — self-targeting data-[x]: selector on root
|
|
964
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-38"
|
|
965
|
+
],
|
|
951
966
|
{
|
|
952
967
|
variants: {
|
|
953
968
|
/**
|
|
@@ -995,6 +1010,14 @@ var buttonGroupVariants = classVarianceAuthority.cva(
|
|
|
995
1010
|
}
|
|
996
1011
|
}
|
|
997
1012
|
);
|
|
1013
|
+
var buttonGroupFocusRingVariants = classVarianceAuthority.cva([
|
|
1014
|
+
"pointer-events-none absolute inset-[-3px] rounded-[inherit]",
|
|
1015
|
+
"outline outline-2 outline-offset-0 outline-secondary",
|
|
1016
|
+
"transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
|
|
1017
|
+
"opacity-0",
|
|
1018
|
+
"group-data-[focus-visible]/button-group:opacity-100"
|
|
1019
|
+
]);
|
|
1020
|
+
var buttonGroupVariants = buttonGroupRootVariants;
|
|
998
1021
|
var ButtonGroup = React.forwardRef(
|
|
999
1022
|
({
|
|
1000
1023
|
variant = "standard",
|
|
@@ -1004,6 +1027,7 @@ var ButtonGroup = React.forwardRef(
|
|
|
1004
1027
|
selectedValues,
|
|
1005
1028
|
onSelectionChange,
|
|
1006
1029
|
defaultValue,
|
|
1030
|
+
isDisabled = false,
|
|
1007
1031
|
children,
|
|
1008
1032
|
className,
|
|
1009
1033
|
...htmlProps
|
|
@@ -1020,6 +1044,13 @@ var ButtonGroup = React.forwardRef(
|
|
|
1020
1044
|
},
|
|
1021
1045
|
[ref]
|
|
1022
1046
|
);
|
|
1047
|
+
const hasSelection = React.useMemo(() => {
|
|
1048
|
+
if (selectedValues) return selectedValues.size > 0;
|
|
1049
|
+
if (defaultValue) {
|
|
1050
|
+
return Array.isArray(defaultValue) ? defaultValue.length > 0 : true;
|
|
1051
|
+
}
|
|
1052
|
+
return false;
|
|
1053
|
+
}, [selectedValues, defaultValue]);
|
|
1023
1054
|
if (process.env.NODE_ENV === "development") {
|
|
1024
1055
|
const childArray = Array.isArray(children) ? children : [children];
|
|
1025
1056
|
for (const child of childArray) {
|
|
@@ -1068,7 +1099,12 @@ var ButtonGroup = React.forwardRef(
|
|
|
1068
1099
|
selectedValues,
|
|
1069
1100
|
onSelectionChange,
|
|
1070
1101
|
defaultValue,
|
|
1071
|
-
|
|
1102
|
+
isDisabled,
|
|
1103
|
+
className: cn(buttonGroupRootVariants({ variant, size }), "group/button-group", className),
|
|
1104
|
+
...getInteractionDataAttributes({ isDisabled }),
|
|
1105
|
+
"data-connected": variant === "connected" ? "" : void 0,
|
|
1106
|
+
"data-has-selection": hasSelection ? "" : void 0,
|
|
1107
|
+
"data-selection-mode": selectionMode ?? void 0,
|
|
1072
1108
|
children
|
|
1073
1109
|
}
|
|
1074
1110
|
);
|
|
@@ -1128,10 +1164,12 @@ var iconButtonVariants = classVarianceAuthority.cva(
|
|
|
1128
1164
|
"relative inline-flex items-center justify-center cursor-pointer",
|
|
1129
1165
|
"overflow-hidden rounded-full",
|
|
1130
1166
|
// Circular shape
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1167
|
+
// Split MD3 transition: btn-transition handles spatial (border-radius) with asymmetric
|
|
1168
|
+
// easing (decelerate by default, switched to expressive via btn-transition-selected when
|
|
1169
|
+
// the button is group-selected) and effects (color/bg/shadow) with standard spring.
|
|
1170
|
+
"btn-transition",
|
|
1133
1171
|
"focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
1134
|
-
// State layers — effects token: opacity only, no overshoot
|
|
1172
|
+
// State layers — effects token: opacity only, no overshoot (separate ::before pseudo-element)
|
|
1135
1173
|
"before:absolute before:inset-0 before:rounded-[inherit]",
|
|
1136
1174
|
"before:transition-opacity before:duration-spring-standard-fast-effects before:ease-spring-standard-fast-effects",
|
|
1137
1175
|
"before:bg-current before:opacity-0",
|
|
@@ -1368,6 +1406,7 @@ var IconButton = React.forwardRef(
|
|
|
1368
1406
|
onMouseDown: mergedOnMouseDown,
|
|
1369
1407
|
isDisabled
|
|
1370
1408
|
});
|
|
1409
|
+
const isGroupSelected = isConnected && groupCtx && value ? groupCtx.selectedValues.has(value) : false;
|
|
1371
1410
|
const connectedClasses = isConnected && groupCtx ? [
|
|
1372
1411
|
...getConnectedRadiusClasses(groupCtx, value),
|
|
1373
1412
|
groupCtx.enforceMinWidth ? "min-w-12" : ""
|
|
@@ -1377,22 +1416,13 @@ var IconButton = React.forwardRef(
|
|
|
1377
1416
|
{
|
|
1378
1417
|
ref,
|
|
1379
1418
|
className: cn(
|
|
1380
|
-
//
|
|
1381
|
-
"relative inline-flex items-center justify-center",
|
|
1382
|
-
"overflow-hidden rounded-full",
|
|
1383
|
-
// Circular shape (overridden by connected group classes)
|
|
1384
|
-
// Spatial (border-radius, transform): expressive fast spring — 350ms, visible overshoot
|
|
1385
|
-
"duration-expressive-fast-spatial ease-expressive-fast-spatial transition-all",
|
|
1386
|
-
"focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
1387
|
-
// State layers (hover, focus, active) — effects token: opacity, no overshoot
|
|
1388
|
-
"before:absolute before:inset-0 before:rounded-[inherit]",
|
|
1389
|
-
"before:duration-spring-standard-fast-effects before:ease-spring-standard-fast-effects before:transition-opacity",
|
|
1390
|
-
"before:bg-current before:opacity-0",
|
|
1391
|
-
"hover:before:opacity-8",
|
|
1392
|
-
"focus-visible:before:opacity-12",
|
|
1393
|
-
"active:before:opacity-12",
|
|
1394
|
-
// CVA variants
|
|
1419
|
+
// CVA variants — includes btn-transition for asymmetric border-radius easing
|
|
1395
1420
|
iconButtonVariants({ variant, color, size, selected: selected ?? false, isDisabled }),
|
|
1421
|
+
// Asymmetric border-radius easing: expressive when selected, decelerate when not.
|
|
1422
|
+
// btn-transition-selected overrides --_btn-radius-easing to the bouncy spring while
|
|
1423
|
+
// the button is gaining the pill shape; removal restores decelerate for the return
|
|
1424
|
+
// path, preventing the overshoot-to-0px sharp-corner flash.
|
|
1425
|
+
isGroupSelected ? "btn-transition-selected" : "",
|
|
1396
1426
|
...connectedClasses,
|
|
1397
1427
|
// User custom classes
|
|
1398
1428
|
className
|
|
@@ -1400,6 +1430,7 @@ var IconButton = React.forwardRef(
|
|
|
1400
1430
|
"aria-label": ariaLabel,
|
|
1401
1431
|
"data-variant": variant,
|
|
1402
1432
|
"data-color": color,
|
|
1433
|
+
"data-group-selected": isGroupSelected ? "" : void 0,
|
|
1403
1434
|
...selected !== void 0 && { selected },
|
|
1404
1435
|
...title && { title },
|
|
1405
1436
|
...mergedPropsValue,
|
|
@@ -4791,20 +4822,6 @@ var BadgeContent = React.forwardRef(
|
|
|
4791
4822
|
}
|
|
4792
4823
|
);
|
|
4793
4824
|
BadgeContent.displayName = "BadgeContent";
|
|
4794
|
-
var QUERY = "(prefers-reduced-motion: reduce)";
|
|
4795
|
-
function useReducedMotion() {
|
|
4796
|
-
const [reduced, setReduced] = React.useState(() => {
|
|
4797
|
-
if (typeof window === "undefined") return false;
|
|
4798
|
-
return window.matchMedia(QUERY).matches;
|
|
4799
|
-
});
|
|
4800
|
-
React.useEffect(() => {
|
|
4801
|
-
const mql = window.matchMedia(QUERY);
|
|
4802
|
-
const handler = (e) => setReduced(e.matches);
|
|
4803
|
-
mql.addEventListener("change", handler);
|
|
4804
|
-
return () => mql.removeEventListener("change", handler);
|
|
4805
|
-
}, []);
|
|
4806
|
-
return reduced;
|
|
4807
|
-
}
|
|
4808
4825
|
var Badge = React.forwardRef(
|
|
4809
4826
|
({
|
|
4810
4827
|
count,
|
|
@@ -14924,6 +14941,8 @@ exports.bottomSheetHandlePillVariants = bottomSheetHandlePillVariants;
|
|
|
14924
14941
|
exports.bottomSheetHandleWrapperVariants = bottomSheetHandleWrapperVariants;
|
|
14925
14942
|
exports.bottomSheetScrimVariants = bottomSheetScrimVariants;
|
|
14926
14943
|
exports.bottomSheetVariants = bottomSheetVariants;
|
|
14944
|
+
exports.buttonGroupFocusRingVariants = buttonGroupFocusRingVariants;
|
|
14945
|
+
exports.buttonGroupRootVariants = buttonGroupRootVariants;
|
|
14927
14946
|
exports.buttonGroupVariants = buttonGroupVariants;
|
|
14928
14947
|
exports.calendarCellVariants = calendarCellVariants;
|
|
14929
14948
|
exports.cardVariants = cardVariants;
|