@tinybigui/react 0.11.0 → 0.11.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 CHANGED
@@ -3672,10 +3672,9 @@ var Tabs = React.forwardRef(
3672
3672
  Tabs.displayName = "Tabs";
3673
3673
  var tabListVariants = classVarianceAuthority.cva(
3674
3674
  [
3675
- // Base classes
3676
3675
  "relative flex",
3677
3676
  "bg-surface",
3678
- // Bottom divider line (MD3 spec)
3677
+ // MD3: 1dp bottom divider in outline-variant color
3679
3678
  "border-b border-outline-variant"
3680
3679
  ],
3681
3680
  {
@@ -3692,108 +3691,110 @@ var tabListVariants = classVarianceAuthority.cva(
3692
3691
  );
3693
3692
  var tabVariants = classVarianceAuthority.cva(
3694
3693
  [
3695
- // Base layout
3694
+ // Layout
3696
3695
  "relative flex flex-col items-center justify-center",
3697
3696
  "min-h-12 px-4",
3698
3697
  "cursor-pointer select-none",
3698
+ // Clip state layer to tab boundary
3699
3699
  "overflow-hidden",
3700
- // Typography: MD3 Title Small
3701
- "text-sm font-medium tracking-[0.1px]",
3702
- // Transition
3703
- "transition-colors duration-200",
3704
- // Focus visible
3700
+ // MD3 Title Small typography
3701
+ "text-title-small font-medium tracking-[0.1px]",
3702
+ // Effects transition for color changes (not spatial)
3703
+ "transition-colors duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
3704
+ // Remove default button focus outline — handled via group-data-[focus-visible]/tab
3705
3705
  "focus-visible:outline-none",
3706
- // State layer via before pseudo-element
3707
- "before:absolute before:inset-0 before:transition-opacity before:duration-200",
3708
- "before:bg-current before:opacity-0",
3709
- "hover:before:opacity-8",
3710
- "active:before:opacity-12",
3711
- "focus-visible:before:opacity-12"
3706
+ // Disabled self-targeting data-[x]: selectors (not group)
3707
+ "data-[disabled]:opacity-38 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none",
3708
+ // Icon+label stacked: 64dp height
3709
+ "data-[with-icon]:data-[with-label]:min-h-16"
3712
3710
  ],
3713
3711
  {
3714
3712
  variants: {
3715
3713
  /**
3716
- * Tab variant (Primary or Secondary)
3714
+ * Design-time variant: affects active label/icon color and state-layer color.
3715
+ * primary: active → text-primary
3716
+ * secondary: active → text-on-surface
3717
3717
  */
3718
3718
  variant: {
3719
- primary: "",
3720
- secondary: ""
3721
- },
3722
- /**
3723
- * Selected state
3724
- */
3725
- selected: {
3726
- true: "",
3727
- false: ""
3728
- },
3729
- /**
3730
- * Disabled state
3731
- */
3732
- disabled: {
3733
- true: "opacity-38 cursor-not-allowed pointer-events-none",
3734
- false: ""
3719
+ primary: [
3720
+ // Inactive: on-surface-variant (base)
3721
+ "text-on-surface-variant",
3722
+ // Active: text-primary
3723
+ "group-data-[selected]/tab:text-primary"
3724
+ // Disabled active: on-surface/38 inherits from opacity-38 on root
3725
+ ],
3726
+ secondary: [
3727
+ // Inactive: on-surface-variant (base)
3728
+ "text-on-surface-variant",
3729
+ // Active: on-surface
3730
+ "group-data-[selected]/tab:text-on-surface"
3731
+ ]
3735
3732
  },
3736
3733
  /**
3737
- * Layout determines min-width behavior
3734
+ * Design-time variant: affects flex sizing in the tab row.
3738
3735
  */
3739
3736
  layout: {
3740
3737
  fixed: "flex-1",
3741
3738
  scrollable: "min-w-[90px] shrink-0"
3742
3739
  }
3743
3740
  },
3744
- compoundVariants: [
3745
- // Primary + selected
3746
- {
3747
- variant: "primary",
3748
- selected: true,
3749
- disabled: false,
3750
- className: "text-primary"
3751
- },
3752
- // Primary + unselected
3753
- {
3754
- variant: "primary",
3755
- selected: false,
3756
- disabled: false,
3757
- className: "text-on-surface-variant"
3758
- },
3759
- // Secondary + selected
3760
- {
3761
- variant: "secondary",
3762
- selected: true,
3763
- disabled: false,
3764
- className: "text-on-surface"
3765
- },
3766
- // Secondary + unselected
3767
- {
3768
- variant: "secondary",
3769
- selected: false,
3770
- disabled: false,
3771
- className: "text-on-surface-variant"
3772
- }
3773
- ],
3741
+ // compoundVariants intentionally empty — state combinations are handled
3742
+ // via group-data-[selected]/tab:group-data-[...]/tab: in state slot
3743
+ compoundVariants: [],
3774
3744
  defaultVariants: {
3775
3745
  variant: "primary",
3776
- selected: false,
3777
- disabled: false,
3778
3746
  layout: "fixed"
3779
3747
  }
3780
3748
  }
3781
3749
  );
3750
+ var tabStateLayerVariants = classVarianceAuthority.cva(
3751
+ [
3752
+ "absolute inset-0 pointer-events-none opacity-0",
3753
+ // Effects transition for opacity — no spatial overshoot
3754
+ "transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
3755
+ // Hover: 8%
3756
+ "group-data-[hovered]/tab:opacity-8",
3757
+ // Focus: 10%
3758
+ "group-data-[focus-visible]/tab:opacity-10",
3759
+ // Pressed: 10%, doubled selector beats hover at equal cascade position
3760
+ "group-data-[pressed]/tab:group-data-[pressed]/tab:opacity-10",
3761
+ // No state layer when disabled
3762
+ "group-data-[disabled]/tab:hidden"
3763
+ ],
3764
+ {
3765
+ variants: {
3766
+ variant: {
3767
+ primary: [
3768
+ // Inactive state-layer color
3769
+ "bg-on-surface",
3770
+ // Active state-layer color (higher cascade position wins over base bg-on-surface)
3771
+ "group-data-[selected]/tab:bg-primary"
3772
+ ],
3773
+ secondary: [
3774
+ // Secondary: always on-surface
3775
+ "bg-on-surface"
3776
+ ]
3777
+ }
3778
+ },
3779
+ defaultVariants: {
3780
+ variant: "primary"
3781
+ }
3782
+ }
3783
+ );
3782
3784
  var tabIndicatorVariants = classVarianceAuthority.cva(
3783
3785
  [
3784
- // Base: absolutely positioned at bottom
3785
3786
  "absolute bottom-0 left-0",
3786
3787
  "pointer-events-none",
3787
- // Transition using MD3 motion tokens (medium2 duration, emphasized easing)
3788
+ // Spatial spring indicator position/width are spatial (not effects)
3788
3789
  "transition-[left,width]",
3789
- "duration-medium2",
3790
- "ease-emphasized"
3790
+ "duration-spring-standard-default-spatial",
3791
+ "ease-spring-standard-default-spatial"
3791
3792
  ],
3792
3793
  {
3793
3794
  variants: {
3794
3795
  variant: {
3795
- primary: ["h-[3px]", "bg-primary", "rounded-t-sm"],
3796
- secondary: ["h-[2px]", "bg-on-surface-variant"]
3796
+ primary: ["h-[3px]", "bg-primary", "rounded-tl-sm rounded-tr-sm"],
3797
+ secondary: ["h-[2px]", "bg-primary"]
3797
3798
  }
3798
3799
  },
3799
3800
  defaultVariants: {
@@ -3801,20 +3802,28 @@ var tabIndicatorVariants = classVarianceAuthority.cva(
3801
3802
  }
3802
3803
  }
3803
3804
  );
3804
- var tabPanelVariants = classVarianceAuthority.cva(
3805
+ var tabIconVariants = classVarianceAuthority.cva(
3805
3806
  [
3806
- // Base panel styles
3807
- "outline-none",
3808
- "focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
3807
+ "relative inline-flex items-center justify-center",
3808
+ // MD3 tab icon size: 24dp
3809
+ "size-6",
3810
+ // Effects transition for color (inherited from parent, guard here too)
3811
+ "transition-colors duration-spring-standard-fast-effects ease-spring-standard-fast-effects"
3809
3812
  ],
3810
3813
  {
3811
- variants: {},
3812
- defaultVariants: {}
3814
+ variants: {
3815
+ hasLabel: {
3816
+ true: "mb-1",
3817
+ false: ""
3818
+ }
3819
+ },
3820
+ defaultVariants: {
3821
+ hasLabel: false
3822
+ }
3813
3823
  }
3814
3824
  );
3815
3825
  var tabBadgeVariants = classVarianceAuthority.cva(
3816
3826
  [
3817
- // Base badge
3818
3827
  "absolute",
3819
3828
  "inline-flex items-center justify-center",
3820
3829
  "bg-error text-on-error",
@@ -3824,7 +3833,7 @@ var tabBadgeVariants = classVarianceAuthority.cva(
3824
3833
  {
3825
3834
  variants: {
3826
3835
  type: {
3827
- dot: ["top-1 right-1", "w-1.5 h-1.5", "rounded-full"],
3836
+ dot: ["top-0 right-0", "w-1.5 h-1.5", "rounded-full"],
3828
3837
  count: ["-top-1 -right-1", "min-w-[16px] h-4", "px-1", "rounded-full", "text-[11px]"]
3829
3838
  }
3830
3839
  },
@@ -3833,20 +3842,10 @@ var tabBadgeVariants = classVarianceAuthority.cva(
3833
3842
  }
3834
3843
  }
3835
3844
  );
3836
- var tabIconVariants = classVarianceAuthority.cva(
3837
- ["relative", "inline-flex items-center justify-center", "w-6 h-6"],
3838
- {
3839
- variants: {
3840
- hasLabel: {
3841
- true: "mb-1",
3842
- false: ""
3843
- }
3844
- },
3845
- defaultVariants: {
3846
- hasLabel: false
3847
- }
3848
- }
3849
- );
3845
+ var tabPanelVariants = classVarianceAuthority.cva([
3846
+ "outline-none",
3847
+ "focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
3848
+ ]);
3850
3849
  var TabList = React.forwardRef(
3851
3850
  ({ children, className }, forwardedRef) => {
3852
3851
  const { state } = useHeadlessTabsContext("TabList");
@@ -3925,18 +3924,41 @@ var TabList = React.forwardRef(
3925
3924
  const selectedTab = container.querySelector('[aria-selected="true"]');
3926
3925
  if (!selectedTab) return;
3927
3926
  const containerRect = container.getBoundingClientRect();
3928
- const tabRect = selectedTab.getBoundingClientRect();
3929
- const newLeft = tabRect.left - containerRect.left + container.scrollLeft;
3930
- const newWidth = tabRect.width;
3931
- setIndicatorStyle((prev) => {
3932
- if (prev.left === newLeft && prev.width === newWidth) return prev;
3933
- return { left: newLeft, width: newWidth };
3934
- });
3927
+ if (variant === "primary") {
3928
+ const contentEl = selectedTab.querySelector("[data-tab-content]");
3929
+ if (!contentEl) return;
3930
+ const contentRect = contentEl.getBoundingClientRect();
3931
+ const newLeft = contentRect.left - containerRect.left + container.scrollLeft;
3932
+ const newWidth = contentRect.width;
3933
+ setIndicatorStyle((prev) => {
3934
+ if (prev.left === newLeft && prev.width === newWidth) return prev;
3935
+ return { left: newLeft, width: newWidth };
3936
+ });
3937
+ } else {
3938
+ const tabRect = selectedTab.getBoundingClientRect();
3939
+ const newLeft = tabRect.left - containerRect.left + container.scrollLeft;
3940
+ const newWidth = tabRect.width;
3941
+ setIndicatorStyle((prev) => {
3942
+ if (prev.left === newLeft && prev.width === newWidth) return prev;
3943
+ return { left: newLeft, width: newWidth };
3944
+ });
3945
+ }
3935
3946
  setIndicatorReady(true);
3936
- }, [ref]);
3947
+ }, [ref, variant]);
3937
3948
  React.useLayoutEffect(() => {
3938
3949
  updateIndicator();
3939
3950
  }, [state.selectedKey, updateIndicator]);
3951
+ React.useEffect(() => {
3952
+ const container = ref.current;
3953
+ if (!container || typeof ResizeObserver === "undefined") return;
3954
+ const observer = new ResizeObserver(() => {
3955
+ updateIndicator();
3956
+ });
3957
+ observer.observe(container);
3958
+ return () => {
3959
+ observer.disconnect();
3960
+ };
3961
+ }, [ref, updateIndicator]);
3940
3962
  const mergedTabListProps = { ...tabListProps, onKeyDown: handleKeyDown };
3941
3963
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...mergedTabListProps, ref, className: cn(tabListVariants({ layout }), className), children: [
3942
3964
  children,
@@ -3951,7 +3973,6 @@ var TabList = React.forwardRef(
3951
3973
  !indicatorReady && "opacity-0"
3952
3974
  ),
3953
3975
  style: {
3954
- // Dynamic left/width values from DOM measurements
3955
3976
  left: `${indicatorStyle.left}px`,
3956
3977
  width: `${indicatorStyle.width}px`
3957
3978
  }
@@ -3979,10 +4000,12 @@ var Tab = React.forwardRef(
3979
4000
  const {
3980
4001
  tabProps,
3981
4002
  isSelected,
3982
- isDisabled: ariaIsDisabled
4003
+ isDisabled: ariaIsDisabled,
4004
+ isPressed
3983
4005
  } = reactAria.useTab({ key: id, isDisabled }, state, ref);
3984
4006
  const { isFocusVisible, focusProps } = reactAria.useFocusRing();
3985
4007
  const finalIsDisabled = isDisabled || ariaIsDisabled;
4008
+ const { isHovered, hoverProps } = reactAria.useHover({ isDisabled: finalIsDisabled });
3986
4009
  const { onMouseDown: handleRipple, ripples } = useRipple({
3987
4010
  disabled: finalIsDisabled || disableRipple
3988
4011
  });
@@ -3996,7 +4019,7 @@ var Tab = React.forwardRef(
3996
4019
  state.selectionManager.setFocusedKey(id);
3997
4020
  }
3998
4021
  }, [state.selectionManager, id, finalIsDisabled]);
3999
- const mergedProps = utils.mergeProps(tabProps, focusProps, {
4022
+ const mergedProps = utils.mergeProps(tabProps, focusProps, hoverProps, {
4000
4023
  onMouseDown: disableRipple ? void 0 : handleRipple,
4001
4024
  onClick: handleClick,
4002
4025
  onFocus: handleFocus
@@ -4013,47 +4036,56 @@ var Tab = React.forwardRef(
4013
4036
  type: "button",
4014
4037
  "data-key": String(id),
4015
4038
  tabIndex: finalIsDisabled ? -1 : isSelected ? 0 : -1,
4016
- className: cn(
4017
- tabVariants({
4018
- variant,
4019
- selected: isSelected,
4020
- disabled: finalIsDisabled,
4021
- layout
4022
- }),
4023
- isFocusVisible && "outline-primary outline-2 outline-offset-2",
4024
- hasLabel && hasIcon && "min-h-16",
4025
- className
4026
- ),
4039
+ className: cn(tabVariants({ variant, layout }), className),
4040
+ ...getInteractionDataAttributes({
4041
+ isHovered,
4042
+ isFocusVisible,
4043
+ isPressed,
4044
+ isSelected,
4045
+ isDisabled: finalIsDisabled
4046
+ }),
4047
+ "data-with-icon": hasIcon ? "" : void 0,
4048
+ "data-with-label": hasLabel ? "" : void 0,
4027
4049
  ...htmlProps,
4028
4050
  children: [
4029
4051
  !disableRipple && ripples,
4030
- hasIcon && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn(tabIconVariants({ hasLabel })), children: [
4031
- icon,
4032
- badgeDisplay && /* @__PURE__ */ jsxRuntime.jsx(
4033
- "span",
4034
- {
4035
- "data-badge-type": isDotBadge ? "dot" : "count",
4036
- "aria-hidden": "true",
4037
- className: cn(tabBadgeVariants({ type: isDotBadge ? "dot" : "count" })),
4038
- children: !isDotBadge && badgeDisplay
4039
- }
4040
- )
4041
- ] }),
4042
- hasLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 truncate", children: [
4043
- label,
4044
- !hasIcon && badgeDisplay && /* @__PURE__ */ jsxRuntime.jsx(
4045
- "span",
4046
- {
4047
- "data-badge-type": isDotBadge ? "dot" : "count",
4048
- "aria-hidden": "true",
4049
- className: cn(
4050
- "relative ml-1",
4051
- tabBadgeVariants({ type: isDotBadge ? "dot" : "count" })
4052
- ),
4053
- children: !isDotBadge && badgeDisplay
4054
- }
4055
- )
4056
- ] })
4052
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(tabStateLayerVariants({ variant })), "aria-hidden": "true" }),
4053
+ /* @__PURE__ */ jsxRuntime.jsxs(
4054
+ "span",
4055
+ {
4056
+ "data-tab-content": true,
4057
+ className: "relative z-10 inline-flex flex-col items-center justify-center",
4058
+ children: [
4059
+ hasIcon && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn(tabIconVariants({ hasLabel })), children: [
4060
+ icon,
4061
+ badgeDisplay && /* @__PURE__ */ jsxRuntime.jsx(
4062
+ "span",
4063
+ {
4064
+ "data-badge-type": isDotBadge ? "dot" : "count",
4065
+ "aria-hidden": "true",
4066
+ className: cn(tabBadgeVariants({ type: isDotBadge ? "dot" : "count" })),
4067
+ children: !isDotBadge && badgeDisplay
4068
+ }
4069
+ )
4070
+ ] }),
4071
+ hasLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "truncate", children: [
4072
+ label,
4073
+ !hasIcon && badgeDisplay && /* @__PURE__ */ jsxRuntime.jsx(
4074
+ "span",
4075
+ {
4076
+ "data-badge-type": isDotBadge ? "dot" : "count",
4077
+ "aria-hidden": "true",
4078
+ className: cn(
4079
+ "relative ml-1",
4080
+ tabBadgeVariants({ type: isDotBadge ? "dot" : "count" })
4081
+ ),
4082
+ children: !isDotBadge && badgeDisplay
4083
+ }
4084
+ )
4085
+ ] })
4086
+ ]
4087
+ }
4088
+ )
4057
4089
  ]
4058
4090
  }
4059
4091
  );
@@ -5403,28 +5435,42 @@ var ProgressHeadless = React.forwardRef(
5403
5435
  }
5404
5436
  );
5405
5437
  ProgressHeadless.displayName = "ProgressHeadless";
5406
- var CardHeadless = React.forwardRef(function CardHeadless2({ className, children, ...ariaButtonProps }, forwardedRef) {
5438
+ var CardHeadless = React.forwardRef(function CardHeadless2({ className, children, onMouseDown, onMouseUp, onMouseLeave, ...rest }, forwardedRef) {
5407
5439
  const internalRef = React.useRef(null);
5408
5440
  const ref = forwardedRef ?? internalRef;
5409
- const isInteractive = !!(ariaButtonProps.onPress ?? ariaButtonProps.href);
5410
- const { buttonProps } = reactAria.useButton({ elementType: "div", ...ariaButtonProps }, ref);
5411
- const { focusProps, isFocusVisible } = reactAria.useFocusRing();
5441
+ const isInteractive = !!(rest.onPress ?? rest.href);
5442
+ const { buttonProps } = reactAria.useButton({ elementType: "div", ...rest }, ref);
5443
+ const {
5444
+ isDisabled: _isDisabled,
5445
+ onPress: _onPress,
5446
+ onPressStart: _onPressStart,
5447
+ onPressEnd: _onPressEnd,
5448
+ onPressChange: _onPressChange,
5449
+ onPressUp: _onPressUp,
5450
+ href: _href,
5451
+ target: _target,
5452
+ rel: _rel,
5453
+ ...htmlAttrs
5454
+ } = rest;
5455
+ const mouseHandlers = { onMouseDown, onMouseUp, onMouseLeave };
5412
5456
  if (isInteractive) {
5413
- const interactiveProps = utils.mergeProps(buttonProps, focusProps, {
5414
- className,
5415
- "data-focus-visible": isFocusVisible ? "true" : void 0
5416
- });
5457
+ const interactiveProps = utils.mergeProps(buttonProps, mouseHandlers, htmlAttrs, { className });
5417
5458
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ...interactiveProps, ref, children });
5418
5459
  }
5419
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "article", className, ref, children });
5460
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "article", className, ref, ...mouseHandlers, ...htmlAttrs, children });
5420
5461
  });
5421
5462
  CardHeadless.displayName = "CardHeadless";
5422
5463
  var cardVariants = classVarianceAuthority.cva(
5423
5464
  [
5424
5465
  // Shape: MD3 medium corner = 12dp
5425
- "relative overflow-hidden rounded-md",
5426
- // Shadow transition (effects propertyuse spring standard fast effects)
5427
- "transition-shadow duration-spring-standard-fast-effects ease-spring-standard-fast-effects"
5466
+ "relative overflow-hidden rounded-md text-on-surface",
5467
+ // Transition: effects propertiesstandard default tier (cards are standard-size, not <48dp)
5468
+ // Covers shadow (elevation), opacity (disabled fade), border-color (outlined state)
5469
+ "transition-[box-shadow,opacity,border-color] duration-spring-standard-default-effects ease-spring-standard-default-effects",
5470
+ // Interactive affordance (content flag set by the component)
5471
+ "data-[interactive]:cursor-pointer",
5472
+ // Disabled — self-targeting selectors (38% container, no interaction)
5473
+ "data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none data-[disabled]:opacity-38"
5428
5474
  ],
5429
5475
  {
5430
5476
  variants: {
@@ -5432,92 +5478,74 @@ var cardVariants = classVarianceAuthority.cva(
5432
5478
  * Card visual variant per MD3 specification.
5433
5479
  */
5434
5480
  variant: {
5435
- elevated: ["shadow-elevation-1", "hover:shadow-elevation-2"],
5436
- filled: ["shadow-elevation-0"],
5437
- outlined: ["border", "border-outline-variant", "shadow-elevation-0"]
5438
- },
5439
- /**
5440
- * Whether the card is interactive (has onPress or href).
5441
- * Interactive cards gain a cursor, keyboard focus ring, and state layer.
5442
- */
5443
- isInteractive: {
5444
- true: [
5445
- "cursor-pointer",
5446
- "focus-visible:outline-2",
5447
- "focus-visible:outline-primary",
5448
- "focus-visible:outline-offset-2"
5481
+ /**
5482
+ * Elevated — separation via shadow.
5483
+ * MD3: container=surface-container-low.
5484
+ * Elevation: 1 base → 2 hover → 1 focus → 1 pressed → 4 dragged.
5485
+ */
5486
+ elevated: [
5487
+ "bg-surface-container-low",
5488
+ "shadow-elevation-1",
5489
+ "data-[hovered]:shadow-elevation-2",
5490
+ "data-[focus-visible]:shadow-elevation-1",
5491
+ "data-[pressed]:data-[pressed]:shadow-elevation-1",
5492
+ "data-[dragged]:data-[dragged]:data-[dragged]:shadow-elevation-4",
5493
+ "data-[disabled]:shadow-none"
5449
5494
  ],
5450
- false: "cursor-default"
5451
- },
5452
- /**
5453
- * Whether the card is currently being dragged.
5454
- * Applies elevated shadow level 4 with a slower, more intentional transition
5455
- * to communicate physical lift per MD3 motion spec.
5456
- */
5457
- isDragged: {
5458
- true: [
5459
- "shadow-elevation-4",
5460
- // Override base transition to use a slower, decelerate curve for drag onset
5461
- "duration-medium2",
5462
- "ease-emphasized-decelerate"
5495
+ /**
5496
+ * Filled — subtle container fill, no resting shadow.
5497
+ * MD3: container=surface-container-highest.
5498
+ * Elevation: 0 base 1 hover → 0 focus → 0 pressed → 3 dragged.
5499
+ */
5500
+ filled: [
5501
+ "bg-surface-container-highest",
5502
+ "shadow-none",
5503
+ "data-[hovered]:shadow-elevation-1",
5504
+ "data-[focus-visible]:shadow-none",
5505
+ "data-[pressed]:data-[pressed]:shadow-none",
5506
+ "data-[dragged]:data-[dragged]:data-[dragged]:shadow-elevation-3",
5507
+ "data-[disabled]:shadow-none"
5463
5508
  ],
5464
- false: ""
5465
- },
5466
- /**
5467
- * Whether the card is disabled.
5468
- * MD3 spec: 38% opacity, no pointer events.
5469
- */
5470
- isDisabled: {
5471
- true: ["opacity-38", "pointer-events-none"],
5472
- false: ""
5509
+ /**
5510
+ * Outlined — visual boundary via border, no resting shadow.
5511
+ * MD3: container=surface, outline=outline-variant.
5512
+ * Elevation: 0 base 1 hover → 0 focus → 0 pressed → 3 dragged.
5513
+ */
5514
+ outlined: [
5515
+ "bg-surface border border-outline-variant",
5516
+ "shadow-none",
5517
+ "data-[hovered]:shadow-elevation-1",
5518
+ "data-[focus-visible]:shadow-none",
5519
+ "data-[pressed]:data-[pressed]:shadow-none",
5520
+ "data-[dragged]:data-[dragged]:data-[dragged]:shadow-elevation-3",
5521
+ "data-[disabled]:shadow-none"
5522
+ ]
5473
5523
  }
5474
5524
  },
5475
- compoundVariants: [
5476
- // Filled + enabled
5477
- {
5478
- variant: "filled",
5479
- isDisabled: false,
5480
- class: "bg-surface-container-highest"
5481
- },
5482
- // Filled + disabled
5483
- {
5484
- variant: "filled",
5485
- isDisabled: true,
5486
- class: "bg-surface-container-variant"
5487
- },
5488
- // Elevated + enabled
5489
- {
5490
- variant: "elevated",
5491
- isDisabled: true,
5492
- class: "bg-surface"
5493
- },
5494
- // Elevated + disabled
5495
- {
5496
- variant: "elevated",
5497
- isDisabled: false,
5498
- class: "bg-surface-container-low"
5499
- },
5500
- // Outlined + enabled
5501
- {
5502
- variant: "outlined",
5503
- isDisabled: true,
5504
- class: "bg-surface"
5505
- },
5506
- // Outlined + disabled
5507
- {
5508
- variant: "outlined",
5509
- isDisabled: false,
5510
- class: "bg-surface"
5511
- }
5512
- ],
5513
5525
  defaultVariants: {
5514
- variant: "elevated",
5515
- isInteractive: false,
5516
- isDragged: false,
5517
- isDisabled: false
5526
+ variant: "elevated"
5518
5527
  }
5519
5528
  }
5520
5529
  );
5530
+ var cardStateLayerVariants = classVarianceAuthority.cva([
5531
+ "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0",
5532
+ "bg-on-surface",
5533
+ // Effects transition for opacity — standard default tier (200ms, no overshoot)
5534
+ "transition-opacity duration-spring-standard-default-effects ease-spring-standard-default-effects",
5535
+ "group-data-[hovered]/card:opacity-8",
5536
+ "group-data-[focus-visible]/card:opacity-10",
5537
+ "group-data-[pressed]/card:group-data-[pressed]/card:opacity-10",
5538
+ "group-data-[dragged]/card:group-data-[dragged]/card:group-data-[dragged]/card:opacity-16",
5539
+ "group-data-[disabled]/card:hidden"
5540
+ ]);
5541
+ var cardFocusRingVariants = classVarianceAuthority.cva([
5542
+ "pointer-events-none absolute inset-0 z-20 rounded-[inherit]",
5543
+ "outline outline-2 -outline-offset-2 outline-secondary",
5544
+ // Effects transition — standard default tier, opacity must not overshoot
5545
+ "transition-opacity duration-spring-standard-default-effects ease-spring-standard-default-effects",
5546
+ "opacity-0",
5547
+ "group-data-[focus-visible]/card:opacity-100"
5548
+ ]);
5521
5549
  var Card = React.forwardRef(function Card2({
5522
5550
  variant = "elevated",
5523
5551
  onPress,
@@ -5528,9 +5556,15 @@ var Card = React.forwardRef(function Card2({
5528
5556
  children,
5529
5557
  "aria-label": ariaLabel
5530
5558
  }, ref) {
5559
+ const internalRef = React.useRef(null);
5560
+ const resolvedRef = ref ?? internalRef;
5531
5561
  const isInteractive = !!(onPress ?? href);
5532
5562
  const [isDragged, setIsDragged] = React.useState(false);
5533
5563
  const [isPressed, setIsPressed] = React.useState(false);
5564
+ const { isHovered, hoverProps } = reactAria.useHover({ isDisabled: !isInteractive || isDisabled });
5565
+ const { isFocusVisible, focusProps } = reactAria.useFocusRing();
5566
+ const handlePressStart = React.useCallback(() => setIsPressed(true), []);
5567
+ const handlePressEnd = React.useCallback(() => setIsPressed(false), []);
5534
5568
  const { onMouseDown: handleRipple, ripples } = useRipple({
5535
5569
  disabled: !isInteractive || isDisabled
5536
5570
  });
@@ -5544,42 +5578,41 @@ var Card = React.forwardRef(function Card2({
5544
5578
  const handleMouseLeave = () => {
5545
5579
  if (isDraggable) setIsDragged(false);
5546
5580
  };
5547
- const handlePressStart = () => setIsPressed(true);
5548
- const handlePressEnd = () => setIsPressed(false);
5581
+ const interactionAttrs = isInteractive ? getInteractionDataAttributes({ isHovered, isFocusVisible, isPressed, isDisabled }) : {};
5582
+ const interactiveHandlers = isInteractive ? reactAria.mergeProps(hoverProps, focusProps, {
5583
+ onPressStart: handlePressStart,
5584
+ onPressEnd: handlePressEnd
5585
+ }) : {};
5549
5586
  return /* @__PURE__ */ jsxRuntime.jsxs(
5550
5587
  CardHeadless,
5551
5588
  {
5552
- ref,
5589
+ ref: resolvedRef,
5553
5590
  ...onPress !== void 0 && { onPress },
5554
5591
  ...href !== void 0 && { href },
5555
5592
  isDisabled,
5556
5593
  ...ariaLabel !== void 0 && { "aria-label": ariaLabel },
5557
- onPressStart: handlePressStart,
5558
- onPressEnd: handlePressEnd,
5559
- onMouseDown: handleMouseDown,
5560
- onMouseUp: handleMouseUp,
5561
- onMouseLeave: handleMouseLeave,
5562
- className: cn(
5563
- cardVariants({ variant, isInteractive, isDragged, isDisabled }),
5564
- "group",
5565
- className
5566
- ),
5594
+ ...interactiveHandlers,
5595
+ ...isInteractive && { onMouseDown: handleMouseDown },
5596
+ ...isInteractive && isDraggable && {
5597
+ onMouseUp: handleMouseUp,
5598
+ onMouseLeave: handleMouseLeave
5599
+ },
5600
+ ...interactionAttrs,
5601
+ "data-interactive": isInteractive ? "" : void 0,
5602
+ "data-dragged": isInteractive && isDragged ? "" : void 0,
5603
+ className: cn(cardVariants({ variant }), "group/card", className),
5567
5604
  children: [
5568
5605
  isInteractive && /* @__PURE__ */ jsxRuntime.jsx(
5569
- "div",
5606
+ "span",
5570
5607
  {
5571
5608
  "data-testid": "card-state-layer",
5572
- "data-pressed": isPressed ? "" : void 0,
5573
5609
  "aria-hidden": "true",
5574
- className: cn(
5575
- "bg-on-surface pointer-events-none absolute inset-0 rounded-md",
5576
- "opacity-0 group-hover:opacity-8 data-[pressed]:opacity-12",
5577
- "duration-spring-standard-fast-effects ease-spring-standard-fast-effects transition-opacity"
5578
- )
5610
+ className: cn(cardStateLayerVariants())
5579
5611
  }
5580
5612
  ),
5581
5613
  isInteractive && ripples,
5582
- children
5614
+ isInteractive && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: cn(cardFocusRingVariants()) }),
5615
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10", children })
5583
5616
  ]
5584
5617
  }
5585
5618
  );
@@ -5589,7 +5622,7 @@ var cardMediaVariants = classVarianceAuthority.cva("w-full object-cover", {
5589
5622
  variants: {
5590
5623
  aspectRatio: {
5591
5624
  "16/9": "aspect-video",
5592
- "4/3": "aspect-video",
5625
+ "4/3": "aspect-[4/3]",
5593
5626
  "1/1": "aspect-square",
5594
5627
  auto: ""
5595
5628
  }
@@ -5613,7 +5646,7 @@ var CardMedia = React.forwardRef(function CardMedia2({ src, alt, aspectRatio = "
5613
5646
  CardMedia.displayName = "CardMedia";
5614
5647
  var CardHeader = React.forwardRef(function CardHeader2({ headline, subheader, className }, ref) {
5615
5648
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn("p-4", className), children: [
5616
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-on-surface text-title-large", children: headline }),
5649
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-on-surface text-title-medium", children: headline }),
5617
5650
  subheader !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-on-surface-variant text-body-medium mt-1", children: subheader })
5618
5651
  ] });
5619
5652
  });