@hyddenlabs/hydn-ui 0.3.13 → 0.3.15

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.
Files changed (34) hide show
  1. package/dist/components/data-display/code-block/code-block.js +1 -1
  2. package/dist/components/data-display/code-block/code-block.js.map +1 -1
  3. package/dist/components/data-display/data-grid/data-grid.js +1 -1
  4. package/dist/components/data-display/data-grid/data-grid.js.map +1 -1
  5. package/dist/components/feedback/tooltip/tooltip.d.ts +3 -1
  6. package/dist/components/feedback/tooltip/tooltip.d.ts.map +1 -1
  7. package/dist/components/feedback/tooltip/tooltip.js +3 -2
  8. package/dist/components/feedback/tooltip/tooltip.js.map +1 -1
  9. package/dist/components/forms/button/button-with-icon.d.ts +3 -0
  10. package/dist/components/forms/button/button-with-icon.d.ts.map +1 -1
  11. package/dist/components/forms/button/button-with-icon.js +23 -2
  12. package/dist/components/forms/button/button-with-icon.js.map +1 -1
  13. package/dist/components/forms/button/button.d.ts +6 -2
  14. package/dist/components/forms/button/button.d.ts.map +1 -1
  15. package/dist/components/forms/button/button.js +22 -6
  16. package/dist/components/forms/button/button.js.map +1 -1
  17. package/dist/components/forms/button/icon-button.d.ts +2 -0
  18. package/dist/components/forms/button/icon-button.d.ts.map +1 -1
  19. package/dist/components/forms/button/icon-button.js +3 -0
  20. package/dist/components/forms/button/icon-button.js.map +1 -1
  21. package/dist/components/forms/button/inline-button.d.ts +2 -0
  22. package/dist/components/forms/button/inline-button.d.ts.map +1 -1
  23. package/dist/components/forms/button/inline-button.js +8 -3
  24. package/dist/components/forms/button/inline-button.js.map +1 -1
  25. package/dist/components/forms/switch/switch.js +1 -1
  26. package/dist/components/forms/switch/switch.js.map +1 -1
  27. package/dist/components/typography/code/code.js +1 -1
  28. package/dist/components/typography/code/code.js.map +1 -1
  29. package/dist/style.css +1 -1
  30. package/dist/theme/tokens.d.ts +1 -0
  31. package/dist/theme/tokens.d.ts.map +1 -1
  32. package/dist/theme/tokens.js +38 -28
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/package.json +1 -1
@@ -27,6 +27,8 @@ const Button = React.forwardRef(
27
27
  wide = false,
28
28
  align,
29
29
  active = false,
30
+ hoverVariant,
31
+ hoverStyle: hoverStyleProp,
30
32
  "aria-expanded": ariaExpanded,
31
33
  "aria-haspopup": ariaHaspopup,
32
34
  "aria-controls": ariaControls,
@@ -36,12 +38,24 @@ const Button = React.forwardRef(
36
38
  rel
37
39
  }, ref) => {
38
40
  const isIconOnly = icon && !children;
41
+ const hasHoverOverride = Boolean(hoverVariant || hoverStyleProp);
42
+ const [isHovered, setIsHovered] = React.useState(false);
43
+ const effectiveVariant = hasHoverOverride && isHovered ? hoverVariant ?? variant : variant;
44
+ const effectiveStyle = hasHoverOverride && isHovered ? hoverStyleProp ?? style : style;
45
+ const handleMouseEnter = hasHoverOverride ? (e) => {
46
+ setIsHovered(true);
47
+ onMouseEnter?.(e);
48
+ } : onMouseEnter;
49
+ const handleMouseLeave = hasHoverOverride ? (e) => {
50
+ setIsHovered(false);
51
+ onMouseLeave?.(e);
52
+ } : onMouseLeave;
39
53
  if (isIconOnly && !ariaLabel) {
40
54
  console.warn("Button: Icon-only buttons require an ariaLabel for accessibility");
41
55
  }
42
56
  const getStyleClasses = () => {
43
- const variantKey = variant;
44
- switch (style) {
57
+ const variantKey = effectiveVariant;
58
+ switch (effectiveStyle) {
45
59
  case "outline":
46
60
  return `${colorVariants.outline[variantKey]}`;
47
61
  case "ghost":
@@ -50,6 +64,8 @@ const Button = React.forwardRef(
50
64
  return colorVariants.link[variantKey];
51
65
  case "soft":
52
66
  return colorVariants.soft[variantKey];
67
+ case "none":
68
+ return colorVariants.none[variantKey];
53
69
  case "solid":
54
70
  default:
55
71
  return `${colorVariants.solid[variantKey]} shadow-sm hover:shadow-md`;
@@ -111,9 +127,9 @@ const Button = React.forwardRef(
111
127
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
128
  onClick,
113
129
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
- onMouseEnter,
130
+ onMouseEnter: handleMouseEnter,
115
131
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
- onMouseLeave,
132
+ onMouseLeave: handleMouseLeave,
117
133
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
134
  onMouseDown,
119
135
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -131,8 +147,8 @@ const Button = React.forwardRef(
131
147
  ref,
132
148
  type,
133
149
  onClick,
134
- onMouseEnter,
135
- onMouseLeave,
150
+ onMouseEnter: handleMouseEnter,
151
+ onMouseLeave: handleMouseLeave,
136
152
  onMouseDown,
137
153
  onBlur,
138
154
  "aria-label": ariaLabel,
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","sources":["../../../../src/components/forms/button/button.tsx"],"sourcesContent":["import React from 'react';\nimport { Link } from 'react-router-dom';\n\nimport { interactiveSizes, InteractiveSize, InputWidth, inputWidthSizes } from '../../../theme/size-tokens';\nimport { Alignment, colorVariants, inputAlignClasses, ColorVariant } from '../../../theme/tokens';\n\nexport type ButtonProps = {\n /** Button label content - text, icons, or other elements */\n children?: React.ReactNode;\n /** Click event handler */\n onClick?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse enter event handler */\n onMouseEnter?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse leave event handler */\n onMouseLeave?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse down event handler */\n onMouseDown?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Blur event handler */\n onBlur?: (e: React.FocusEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Accessible label for icon-only buttons (required when no children) */\n ariaLabel?: string;\n /** Disables button interaction and applies disabled styling */\n disabled?: boolean;\n /** HTML button type attribute (ignored when `to` or `href` is set) */\n type?: 'button' | 'submit' | 'reset';\n /** Additional CSS classes applied to the button */\n className?: string;\n /** Icon element to display alongside button text */\n icon?: React.ReactNode;\n /** Position of the icon relative to button text */\n iconPosition?: 'left' | 'right';\n /** Color variant affecting button appearance */\n variant?: ColorVariant;\n /** Visual style modifier - solid (filled), outline (bordered), ghost (transparent), link (text-only), or soft (subtle background) */\n style?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft';\n /** Size variant - uses unified size system (xs, sm, md, lg, xl) */\n size?: InteractiveSize;\n /** Border radius style - default (rounded), pill (fully rounded), square (sharp corners), circle (for icon-only buttons) */\n rounded?: 'default' | 'pill' | 'square' | 'circle';\n /** Shows loading spinner and disables interaction */\n loading?: boolean;\n /** Width of the button */\n width?: InputWidth;\n /** When true, button spans full width of container (same as width=\"full\") */\n fullWidth?: boolean;\n /** When true, button uses wider min-width (good for prominent actions) */\n wide?: boolean;\n /** Aligns the button within its container: left, center, or right */\n align?: Alignment;\n /** Applies active/pressed state styling */\n active?: boolean;\n /** ARIA expanded state - indicates if controlled element is expanded */\n 'aria-expanded'?: boolean;\n /** ARIA haspopup - indicates element has popup menu/dialog */\n 'aria-haspopup'?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n /** ARIA controls - ID of element controlled by this button */\n 'aria-controls'?: string;\n /**\n * React Router path. When provided the component renders as a `<Link>` (anchor tag)\n * instead of a `<button>`, giving you router-aware navigation with full button styling.\n */\n to?: string;\n /**\n * Plain URL. When provided (and `to` is not set) the component renders as an `<a>` tag.\n * Use `to` instead when navigating within the app so React Router manages the history.\n */\n href?: string;\n /** Link target attribute (`_blank`, `_self`, etc.) – only used when `to` or `href` is set */\n target?: React.HTMLAttributeAnchorTarget;\n /** Rel attribute for the anchor – defaults to `\"noreferrer noopener\"` when `target=\"_blank\"` */\n rel?: string;\n};\n\n/**\n * Accessible Button component\n * - Color variants: neutral, primary, accent, info, success, warning, error\n * - Style modifiers: solid (default), outline, ghost, link, soft\n * - Sizes: xs, sm, md (default), lg, xl\n * - Supports icons with flexible positioning (left/right)\n * - Icon-only buttons require `ariaLabel` for accessibility\n * - Pass `to` to render as a React Router `<Link>`, or `href` for a plain `<a>`, keeping full button styling\n */\nconst Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n (\n {\n children,\n onClick,\n onMouseEnter,\n onMouseLeave,\n onMouseDown,\n onBlur,\n ariaLabel,\n disabled = false,\n type = 'button',\n className = '',\n icon,\n iconPosition = 'left',\n variant = 'neutral',\n style = 'solid',\n size = 'md',\n rounded = 'default',\n loading = false,\n width = 'auto',\n fullWidth = false,\n wide = false,\n align,\n active = false,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHaspopup,\n 'aria-controls': ariaControls,\n to,\n href,\n target,\n rel\n },\n ref\n ) => {\n const isIconOnly = icon && !children;\n\n // Icon-only buttons MUST have an aria-label for accessibility\n if (isIconOnly && !ariaLabel) {\n // eslint-disable-next-line no-console\n console.warn('Button: Icon-only buttons require an ariaLabel for accessibility');\n }\n\n // Style modifiers\n const getStyleClasses = () => {\n const variantKey = variant as keyof typeof colorVariants.solid;\n\n switch (style) {\n case 'outline':\n return `${colorVariants.outline[variantKey]}`;\n case 'ghost':\n return colorVariants.ghost[variantKey];\n case 'link':\n return colorVariants.link[variantKey];\n case 'soft':\n return colorVariants.soft[variantKey];\n case 'solid':\n default:\n return `${colorVariants.solid[variantKey]} shadow-sm hover:shadow-md`;\n }\n };\n\n // Sizes - from unified size system\n const sizeConfig = interactiveSizes[size];\n const sizeClasses = `${sizeConfig.height} ${sizeConfig.padding} ${sizeConfig.text}`;\n\n const roundedClasses = {\n default: 'rounded-md',\n pill: 'rounded-full',\n square: 'rounded-none aspect-square',\n circle: 'rounded-full aspect-square'\n };\n\n // Show loading spinner or icon\n const displayIcon = loading ? (\n <svg className=\"animate-spin h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n ) : (\n icon\n );\n\n const styleClasses = getStyleClasses();\n const effectiveWidth = fullWidth ? 'full' : wide ? 'xl' : width;\n const widthClasses = inputWidthSizes[effectiveWidth];\n const alignmentClass = align ? inputAlignClasses[align] : '';\n const activeClasses = active ? 'active:scale-95' : '';\n\n const shouldRenderIconSlots = !isIconOnly && Boolean(displayIcon);\n const cloneIcon = () => {\n if (!displayIcon) return null;\n if (React.isValidElement(displayIcon)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return React.cloneElement(displayIcon as React.ReactElement<any>, { 'aria-hidden': false });\n }\n return displayIcon;\n };\n\n const showLeftIcon = shouldRenderIconSlots && iconPosition === 'left';\n const showRightIcon = shouldRenderIconSlots && iconPosition === 'right';\n\n // Check if className contains display utilities to avoid conflicts\n const hasDisplayOverride =\n className.includes('hidden') ||\n className.includes('inline') ||\n className.includes('block') ||\n className.includes('flex');\n const baseDisplayClass = hasDisplayOverride ? '' : 'inline-flex';\n\n const isDisabled = disabled || loading;\n\n const combinedClassName = `${baseDisplayClass} items-center justify-center ${alignmentClass} ${roundedClasses[rounded]} font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 ${styleClasses} ${sizeClasses} ${\n isIconOnly ? 'p-0' : ''\n } ${widthClasses} ${activeClasses} ${isDisabled && (to || href) ? 'pointer-events-none opacity-50' : ''} ${className}`;\n\n const innerContent = (\n <>\n {showLeftIcon && <span className=\"flex shrink-0 items-center mr-2\">{cloneIcon()}</span>}\n {isIconOnly ? (\n <span className=\"inline-flex items-center justify-center\">{displayIcon}</span>\n ) : (\n <span className=\"inline-flex flex-1 justify-center text-center\">{children}</span>\n )}\n {showRightIcon && <span className=\"flex shrink-0 items-center ml-2\">{cloneIcon()}</span>}\n </>\n );\n\n const sharedLinkProps = {\n 'aria-label': ariaLabel,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHaspopup,\n 'aria-controls': ariaControls,\n 'aria-disabled': isDisabled || undefined,\n tabIndex: isDisabled ? -1 : undefined,\n target,\n rel: rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined),\n className: combinedClassName,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onClick: onClick as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseEnter: onMouseEnter as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseLeave: onMouseLeave as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseDown: onMouseDown as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onBlur: onBlur as any\n };\n\n if (to) {\n return (\n <Link to={to} ref={ref as React.Ref<HTMLAnchorElement>} {...sharedLinkProps}>\n {innerContent}\n </Link>\n );\n }\n\n if (href) {\n return (\n <a href={href} ref={ref as React.Ref<HTMLAnchorElement>} {...sharedLinkProps}>\n {innerContent}\n </a>\n );\n }\n\n return (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n type={type}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n onMouseDown={onMouseDown}\n onBlur={onBlur}\n aria-label={ariaLabel}\n aria-expanded={ariaExpanded}\n aria-haspopup={ariaHaspopup}\n aria-controls={ariaControls}\n disabled={isDisabled}\n className={combinedClassName}\n >\n {innerContent}\n </button>\n );\n }\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"],"names":[],"mappings":";;;;;AAkFA,MAAM,SAAS,MAAM;AAAA,EACnB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,OAAO;AAAA,IACP,YAAY;AAAA,IACZ;AAAA,IACA,eAAe;AAAA,IACf,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,UAAU;AAAA,IACV,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,OAAO;AAAA,IACP;AAAA,IACA,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,GAEF,QACG;AACH,UAAM,aAAa,QAAQ,CAAC;AAG5B,QAAI,cAAc,CAAC,WAAW;AAE5B,cAAQ,KAAK,kEAAkE;AAAA,IACjF;AAGA,UAAM,kBAAkB,MAAM;AAC5B,YAAM,aAAa;AAEnB,cAAQ,OAAA;AAAA,QACN,KAAK;AACH,iBAAO,GAAG,cAAc,QAAQ,UAAU,CAAC;AAAA,QAC7C,KAAK;AACH,iBAAO,cAAc,MAAM,UAAU;AAAA,QACvC,KAAK;AACH,iBAAO,cAAc,KAAK,UAAU;AAAA,QACtC,KAAK;AACH,iBAAO,cAAc,KAAK,UAAU;AAAA,QACtC,KAAK;AAAA,QACL;AACE,iBAAO,GAAG,cAAc,MAAM,UAAU,CAAC;AAAA,MAAA;AAAA,IAE/C;AAGA,UAAM,aAAa,iBAAiB,IAAI;AACxC,UAAM,cAAc,GAAG,WAAW,MAAM,IAAI,WAAW,OAAO,IAAI,WAAW,IAAI;AAEjF,UAAM,iBAAiB;AAAA,MACrB,SAAS;AAAA,MACT,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ;AAAA,IAAA;AAIV,UAAM,cAAc,UAClB,qBAAC,OAAA,EAAI,WAAU,wBAAuB,OAAM,8BAA6B,MAAK,QAAO,SAAQ,aAC3F,UAAA;AAAA,MAAA,oBAAC,UAAA,EAAO,WAAU,cAAa,IAAG,MAAK,IAAG,MAAK,GAAE,MAAK,QAAO,gBAAe,aAAY,KAAI;AAAA,MAC5F;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAU;AAAA,UACV,MAAK;AAAA,UACL,GAAE;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,EAAA,CACH,IAEA;AAGF,UAAM,eAAe,gBAAA;AACrB,UAAM,iBAAiB,YAAY,SAAS,OAAO,OAAO;AAC1D,UAAM,eAAe,gBAAgB,cAAc;AACnD,UAAM,iBAAiB,QAAQ,kBAAkB,KAAK,IAAI;AAC1D,UAAM,gBAAgB,SAAS,oBAAoB;AAEnD,UAAM,wBAAwB,CAAC,cAAc,QAAQ,WAAW;AAChE,UAAM,YAAY,MAAM;AACtB,UAAI,CAAC,YAAa,QAAO;AACzB,UAAI,MAAM,eAAe,WAAW,GAAG;AAErC,eAAO,MAAM,aAAa,aAAwC,EAAE,eAAe,OAAO;AAAA,MAC5F;AACA,aAAO;AAAA,IACT;AAEA,UAAM,eAAe,yBAAyB,iBAAiB;AAC/D,UAAM,gBAAgB,yBAAyB,iBAAiB;AAGhE,UAAM,qBACJ,UAAU,SAAS,QAAQ,KAC3B,UAAU,SAAS,QAAQ,KAC3B,UAAU,SAAS,OAAO,KAC1B,UAAU,SAAS,MAAM;AAC3B,UAAM,mBAAmB,qBAAqB,KAAK;AAEnD,UAAM,aAAa,YAAY;AAE/B,UAAM,oBAAoB,GAAG,gBAAgB,gCAAgC,cAAc,IAAI,eAAe,OAAO,CAAC,+MAA+M,YAAY,IAAI,WAAW,IAC9V,aAAa,QAAQ,EACvB,IAAI,YAAY,IAAI,aAAa,IAAI,eAAe,MAAM,QAAQ,mCAAmC,EAAE,IAAI,SAAS;AAEpH,UAAM,eACJ,qBAAA,UAAA,EACG,UAAA;AAAA,MAAA,gBAAgB,oBAAC,QAAA,EAAK,WAAU,mCAAmC,uBAAY;AAAA,MAC/E,aACC,oBAAC,QAAA,EAAK,WAAU,2CAA2C,UAAA,YAAA,CAAY,IAEvE,oBAAC,QAAA,EAAK,WAAU,iDAAiD,SAAA,CAAS;AAAA,MAE3E,iBAAiB,oBAAC,QAAA,EAAK,WAAU,mCAAmC,sBAAU,CAAE;AAAA,IAAA,GACnF;AAGF,UAAM,kBAAkB;AAAA,MACtB,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,iBAAiB,cAAc;AAAA,MAC/B,UAAU,aAAa,KAAK;AAAA,MAC5B;AAAA,MACA,KAAK,QAAQ,WAAW,WAAW,wBAAwB;AAAA,MAC3D,WAAW;AAAA;AAAA,MAEX;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA,IAAA;AAGF,QAAI,IAAI;AACN,iCACG,MAAA,EAAK,IAAQ,KAA2C,GAAG,iBACzD,UAAA,cACH;AAAA,IAEJ;AAEA,QAAI,MAAM;AACR,iCACG,KAAA,EAAE,MAAY,KAA2C,GAAG,iBAC1D,UAAA,cACH;AAAA,IAEJ;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAY;AAAA,QACZ,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,UAAU;AAAA,QACV,WAAW;AAAA,QAEV,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,OAAO,cAAc;"}
1
+ {"version":3,"file":"button.js","sources":["../../../../src/components/forms/button/button.tsx"],"sourcesContent":["import React from 'react';\nimport { Link } from 'react-router-dom';\n\nimport { interactiveSizes, InteractiveSize, InputWidth, inputWidthSizes } from '../../../theme/size-tokens';\nimport { Alignment, colorVariants, inputAlignClasses, ColorVariant } from '../../../theme/tokens';\n\nexport type ButtonProps = {\n /** Button label content - text, icons, or other elements */\n children?: React.ReactNode;\n /** Click event handler */\n onClick?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse enter event handler */\n onMouseEnter?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse leave event handler */\n onMouseLeave?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Mouse down event handler */\n onMouseDown?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Blur event handler */\n onBlur?: (e: React.FocusEvent<HTMLButtonElement | HTMLAnchorElement>) => void;\n /** Accessible label for icon-only buttons (required when no children) */\n ariaLabel?: string;\n /** Disables button interaction and applies disabled styling */\n disabled?: boolean;\n /** HTML button type attribute (ignored when `to` or `href` is set) */\n type?: 'button' | 'submit' | 'reset';\n /** Additional CSS classes applied to the button */\n className?: string;\n /** Icon element to display alongside button text */\n icon?: React.ReactNode;\n /** Position of the icon relative to button text */\n iconPosition?: 'left' | 'right';\n /** Color variant affecting button appearance */\n variant?: ColorVariant;\n /** Visual style modifier - solid (filled), outline (bordered), ghost (transparent), link (text-only), soft (subtle background), or none (no background at all) */\n style?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft' | 'none';\n /** Size variant - uses unified size system (xs, sm, md, lg, xl) */\n size?: InteractiveSize;\n /** Border radius style - default (rounded), pill (fully rounded), square (sharp corners), circle (for icon-only buttons) */\n rounded?: 'default' | 'pill' | 'square' | 'circle';\n /** Shows loading spinner and disables interaction */\n loading?: boolean;\n /** Width of the button */\n width?: InputWidth;\n /** When true, button spans full width of container (same as width=\"full\") */\n fullWidth?: boolean;\n /** When true, button uses wider min-width (good for prominent actions) */\n wide?: boolean;\n /** Aligns the button within its container: left, center, or right */\n align?: Alignment;\n /** Applies active/pressed state styling */\n active?: boolean;\n /** Color variant to use when the button is hovered - overrides `variant` on hover */\n hoverVariant?: ColorVariant;\n /** Visual style to use when the button is hovered - overrides `style` on hover */\n hoverStyle?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft' | 'none';\n /** ARIA expanded state - indicates if controlled element is expanded */\n 'aria-expanded'?: boolean;\n /** ARIA haspopup - indicates element has popup menu/dialog */\n 'aria-haspopup'?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n /** ARIA controls - ID of element controlled by this button */\n 'aria-controls'?: string;\n /**\n * React Router path. When provided the component renders as a `<Link>` (anchor tag)\n * instead of a `<button>`, giving you router-aware navigation with full button styling.\n */\n to?: string;\n /**\n * Plain URL. When provided (and `to` is not set) the component renders as an `<a>` tag.\n * Use `to` instead when navigating within the app so React Router manages the history.\n */\n href?: string;\n /** Link target attribute (`_blank`, `_self`, etc.) – only used when `to` or `href` is set */\n target?: React.HTMLAttributeAnchorTarget;\n /** Rel attribute for the anchor – defaults to `\"noreferrer noopener\"` when `target=\"_blank\"` */\n rel?: string;\n};\n\n/**\n * Accessible Button component\n * - Color variants: neutral, primary, accent, info, success, warning, error\n * - Style modifiers: solid (default), outline, ghost, link, soft\n * - Sizes: xs, sm, md (default), lg, xl\n * - Supports icons with flexible positioning (left/right)\n * - Icon-only buttons require `ariaLabel` for accessibility\n * - Pass `to` to render as a React Router `<Link>`, or `href` for a plain `<a>`, keeping full button styling\n */\nconst Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n (\n {\n children,\n onClick,\n onMouseEnter,\n onMouseLeave,\n onMouseDown,\n onBlur,\n ariaLabel,\n disabled = false,\n type = 'button',\n className = '',\n icon,\n iconPosition = 'left',\n variant = 'neutral',\n style = 'solid',\n size = 'md',\n rounded = 'default',\n loading = false,\n width = 'auto',\n fullWidth = false,\n wide = false,\n align,\n active = false,\n hoverVariant,\n hoverStyle: hoverStyleProp,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHaspopup,\n 'aria-controls': ariaControls,\n to,\n href,\n target,\n rel\n },\n ref\n ) => {\n const isIconOnly = icon && !children;\n\n // Hover override tracking\n const hasHoverOverride = Boolean(hoverVariant || hoverStyleProp);\n const [isHovered, setIsHovered] = React.useState(false);\n const effectiveVariant = hasHoverOverride && isHovered ? (hoverVariant ?? variant) : variant;\n const effectiveStyle = hasHoverOverride && isHovered ? (hoverStyleProp ?? style) : style;\n\n const handleMouseEnter = hasHoverOverride\n ? (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n setIsHovered(true);\n onMouseEnter?.(e);\n }\n : onMouseEnter;\n\n const handleMouseLeave = hasHoverOverride\n ? (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n setIsHovered(false);\n onMouseLeave?.(e);\n }\n : onMouseLeave;\n\n // Icon-only buttons MUST have an aria-label for accessibility\n if (isIconOnly && !ariaLabel) {\n // eslint-disable-next-line no-console\n console.warn('Button: Icon-only buttons require an ariaLabel for accessibility');\n }\n\n // Style modifiers\n const getStyleClasses = () => {\n const variantKey = effectiveVariant as keyof typeof colorVariants.solid;\n\n switch (effectiveStyle) {\n case 'outline':\n return `${colorVariants.outline[variantKey]}`;\n case 'ghost':\n return colorVariants.ghost[variantKey];\n case 'link':\n return colorVariants.link[variantKey];\n case 'soft':\n return colorVariants.soft[variantKey];\n case 'none':\n return colorVariants.none[variantKey];\n case 'solid':\n default:\n return `${colorVariants.solid[variantKey]} shadow-sm hover:shadow-md`;\n }\n };\n\n // Sizes - from unified size system\n const sizeConfig = interactiveSizes[size];\n const sizeClasses = `${sizeConfig.height} ${sizeConfig.padding} ${sizeConfig.text}`;\n\n const roundedClasses = {\n default: 'rounded-md',\n pill: 'rounded-full',\n square: 'rounded-none aspect-square',\n circle: 'rounded-full aspect-square'\n };\n\n // Show loading spinner or icon\n const displayIcon = loading ? (\n <svg className=\"animate-spin h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n ) : (\n icon\n );\n\n const styleClasses = getStyleClasses();\n const effectiveWidth = fullWidth ? 'full' : wide ? 'xl' : width;\n const widthClasses = inputWidthSizes[effectiveWidth];\n const alignmentClass = align ? inputAlignClasses[align] : '';\n const activeClasses = active ? 'active:scale-95' : '';\n\n const shouldRenderIconSlots = !isIconOnly && Boolean(displayIcon);\n const cloneIcon = () => {\n if (!displayIcon) return null;\n if (React.isValidElement(displayIcon)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return React.cloneElement(displayIcon as React.ReactElement<any>, { 'aria-hidden': false });\n }\n return displayIcon;\n };\n\n const showLeftIcon = shouldRenderIconSlots && iconPosition === 'left';\n const showRightIcon = shouldRenderIconSlots && iconPosition === 'right';\n\n // Check if className contains display utilities to avoid conflicts\n const hasDisplayOverride =\n className.includes('hidden') ||\n className.includes('inline') ||\n className.includes('block') ||\n className.includes('flex');\n const baseDisplayClass = hasDisplayOverride ? '' : 'inline-flex';\n\n const isDisabled = disabled || loading;\n\n const combinedClassName = `${baseDisplayClass} items-center justify-center ${alignmentClass} ${roundedClasses[rounded]} font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 ${styleClasses} ${sizeClasses} ${\n isIconOnly ? 'p-0' : ''\n } ${widthClasses} ${activeClasses} ${isDisabled && (to || href) ? 'pointer-events-none opacity-50' : ''} ${className}`;\n\n const innerContent = (\n <>\n {showLeftIcon && <span className=\"flex shrink-0 items-center mr-2\">{cloneIcon()}</span>}\n {isIconOnly ? (\n <span className=\"inline-flex items-center justify-center\">{displayIcon}</span>\n ) : (\n <span className=\"inline-flex flex-1 justify-center text-center\">{children}</span>\n )}\n {showRightIcon && <span className=\"flex shrink-0 items-center ml-2\">{cloneIcon()}</span>}\n </>\n );\n\n const sharedLinkProps = {\n 'aria-label': ariaLabel,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHaspopup,\n 'aria-controls': ariaControls,\n 'aria-disabled': isDisabled || undefined,\n tabIndex: isDisabled ? -1 : undefined,\n target,\n rel: rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined),\n className: combinedClassName,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onClick: onClick as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseEnter: handleMouseEnter as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseLeave: handleMouseLeave as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onMouseDown: onMouseDown as any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n onBlur: onBlur as any\n };\n\n if (to) {\n return (\n <Link to={to} ref={ref as React.Ref<HTMLAnchorElement>} {...sharedLinkProps}>\n {innerContent}\n </Link>\n );\n }\n\n if (href) {\n return (\n <a href={href} ref={ref as React.Ref<HTMLAnchorElement>} {...sharedLinkProps}>\n {innerContent}\n </a>\n );\n }\n\n return (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n type={type}\n onClick={onClick}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onMouseDown={onMouseDown}\n onBlur={onBlur}\n aria-label={ariaLabel}\n aria-expanded={ariaExpanded}\n aria-haspopup={ariaHaspopup}\n aria-controls={ariaControls}\n disabled={isDisabled}\n className={combinedClassName}\n >\n {innerContent}\n </button>\n );\n }\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"],"names":[],"mappings":"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"}
@@ -8,6 +8,8 @@ export interface IconButtonProps extends Omit<BaseButtonProps, 'icon' | 'style'
8
8
  iconSize?: IconSize;
9
9
  iconColor?: ColorVariant | 'currentColor';
10
10
  buttonStyle?: BaseButtonProps['style'];
11
+ /** Visual style to apply on hover - alias of `hoverStyle`, consistent with `buttonStyle` naming */
12
+ hoverButtonStyle?: BaseButtonProps['hoverStyle'];
11
13
  /** Remove the default button sizing so the icon renders without extra padding */
12
14
  noPadding?: boolean;
13
15
  /** Icon to display on hover (if not provided, hover will keep the same icon) */
@@ -1 +1 @@
1
- {"version":3,"file":"icon-button.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/button/icon-button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAAQ,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAe,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,SAAS,CAAC,EAAE,YAAY,GAAG,cAAc,CAAC;IAC1C,WAAW,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACvC,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED;;;GAGG;AACH,QAAA,MAAM,UAAU,2FAgGf,CAAC;AAIF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"icon-button.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/button/icon-button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAAQ,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAe,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,SAAS,CAAC,EAAE,YAAY,GAAG,cAAc,CAAC;IAC1C,WAAW,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IACvC,mGAAmG;IACnG,gBAAgB,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IACjD,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED;;;GAGG;AACH,QAAA,MAAM,UAAU,2FAmGf,CAAC;AAIF,eAAe,UAAU,CAAC"}
@@ -8,6 +8,8 @@ const IconButton = React.forwardRef(
8
8
  iconSize = "md",
9
9
  iconColor = "currentColor",
10
10
  buttonStyle,
11
+ hoverButtonStyle,
12
+ hoverStyle,
11
13
  noPadding = false,
12
14
  ariaLabel,
13
15
  hoverIcon,
@@ -68,6 +70,7 @@ const IconButton = React.forwardRef(
68
70
  ref: mergedRef,
69
71
  icon: iconNode,
70
72
  style: buttonStyle,
73
+ hoverStyle: hoverButtonStyle ?? hoverStyle,
71
74
  ariaLabel,
72
75
  onMouseEnter: handleMouseEnter,
73
76
  onMouseLeave: handleMouseLeave,
@@ -1 +1 @@
1
- {"version":3,"file":"icon-button.js","sources":["../../../../src/components/forms/button/icon-button.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useCallback } from 'react';\nimport { Icon, IconSize } from '../../system/icon';\nimport Button, { ButtonProps as BaseButtonProps } from './button';\nimport { ColorVariant } from '../../../theme/tokens';\n\nexport interface IconButtonProps extends Omit<BaseButtonProps, 'icon' | 'style' | 'children'> {\n icon: string;\n ariaLabel: string;\n iconSize?: IconSize;\n iconColor?: ColorVariant | 'currentColor';\n buttonStyle?: BaseButtonProps['style'];\n /** Remove the default button sizing so the icon renders without extra padding */\n noPadding?: boolean;\n /** Icon to display on hover (if not provided, hover will keep the same icon) */\n hoverIcon?: string;\n children?: never;\n}\n\n/**\n * IconButton - Icon-only button using Icon wrapper for consistent icon sizing\n * Usage: <IconButton icon=\"plus\" iconSize=\"md\" ariaLabel=\"Add item\" />\n */\nconst IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(\n (\n {\n icon,\n iconSize = 'md',\n iconColor = 'currentColor',\n buttonStyle,\n noPadding = false,\n ariaLabel,\n hoverIcon,\n onMouseEnter,\n onMouseLeave,\n onClick,\n className = '',\n ...rest\n },\n ref\n ) => {\n const [isHovered, setIsHovered] = useState(false);\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n\n // Merged callback ref that handles both internal and forwarded refs\n const mergedRef = useCallback(\n (node: HTMLButtonElement | null) => {\n // Update internal ref\n buttonRef.current = node;\n\n // Forward to external ref\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref]\n );\n\n const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {\n setIsHovered(true);\n onMouseEnter?.(e);\n };\n\n const handleMouseLeave = (e: React.MouseEvent<HTMLButtonElement>) => {\n setIsHovered(false);\n onMouseLeave?.(e);\n };\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n // Reset hover state on click - handles click-drag scenarios\n // where mouseLeave might not fire\n setIsHovered(false);\n onClick?.(e);\n };\n\n // Global mousemove listener to detect when mouse leaves button during drag\n useEffect(() => {\n if (!hoverIcon || !isHovered) return;\n\n const handleGlobalMouseMove = (e: MouseEvent) => {\n if (!buttonRef.current) return;\n\n const rect = buttonRef.current.getBoundingClientRect();\n const isOutside =\n e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom;\n\n if (isOutside) {\n setIsHovered(false);\n }\n };\n\n document.addEventListener('mousemove', handleGlobalMouseMove);\n return () => {\n document.removeEventListener('mousemove', handleGlobalMouseMove);\n };\n }, [hoverIcon, isHovered]);\n\n const displayIcon = isHovered && hoverIcon ? hoverIcon : icon;\n const iconNode = displayIcon ? <Icon name={displayIcon} size={iconSize} color={iconColor} /> : null;\n const paddingClasses = noPadding ? '!px-0 !py-0 !h-auto !min-h-0 !w-auto !min-w-0' : '';\n const hoverClasses = noPadding ? 'hover:!bg-transparent active:!bg-transparent' : '';\n const mergedClassName = [paddingClasses, hoverClasses, className].filter(Boolean).join(' ');\n\n return (\n <Button\n ref={mergedRef}\n icon={iconNode}\n style={buttonStyle}\n ariaLabel={ariaLabel}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onClick={handleClick}\n className={mergedClassName}\n {...rest}\n />\n );\n }\n);\n\nIconButton.displayName = 'IconButton';\n\nexport default IconButton;\n"],"names":[],"mappings":";;;;AAsBA,MAAM,aAAa,MAAM;AAAA,EACvB,CACE;AAAA,IACE;AAAA,IACA,WAAW;AAAA,IACX,YAAY;AAAA,IACZ;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,UAAM,YAAY,OAAiC,IAAI;AAGvD,UAAM,YAAY;AAAA,MAChB,CAAC,SAAmC;AAElC,kBAAU,UAAU;AAGpB,YAAI,OAAO,QAAQ,YAAY;AAC7B,cAAI,IAAI;AAAA,QACV,WAAW,KAAK;AACd,cAAI,UAAU;AAAA,QAChB;AAAA,MACF;AAAA,MACA,CAAC,GAAG;AAAA,IAAA;AAGN,UAAM,mBAAmB,CAAC,MAA2C;AACnE,mBAAa,IAAI;AACjB,qBAAe,CAAC;AAAA,IAClB;AAEA,UAAM,mBAAmB,CAAC,MAA2C;AACnE,mBAAa,KAAK;AAClB,qBAAe,CAAC;AAAA,IAClB;AAEA,UAAM,cAAc,CAAC,MAA2C;AAG9D,mBAAa,KAAK;AAClB,gBAAU,CAAC;AAAA,IACb;AAGA,cAAU,MAAM;AACd,UAAI,CAAC,aAAa,CAAC,UAAW;AAE9B,YAAM,wBAAwB,CAAC,MAAkB;AAC/C,YAAI,CAAC,UAAU,QAAS;AAExB,cAAM,OAAO,UAAU,QAAQ,sBAAA;AAC/B,cAAM,YACJ,EAAE,UAAU,KAAK,QAAQ,EAAE,UAAU,KAAK,SAAS,EAAE,UAAU,KAAK,OAAO,EAAE,UAAU,KAAK;AAE9F,YAAI,WAAW;AACb,uBAAa,KAAK;AAAA,QACpB;AAAA,MACF;AAEA,eAAS,iBAAiB,aAAa,qBAAqB;AAC5D,aAAO,MAAM;AACX,iBAAS,oBAAoB,aAAa,qBAAqB;AAAA,MACjE;AAAA,IACF,GAAG,CAAC,WAAW,SAAS,CAAC;AAEzB,UAAM,cAAc,aAAa,YAAY,YAAY;AACzD,UAAM,WAAW,cAAc,oBAAC,MAAA,EAAK,MAAM,aAAa,MAAM,UAAU,OAAO,UAAA,CAAW,IAAK;AAC/F,UAAM,iBAAiB,YAAY,kDAAkD;AACrF,UAAM,eAAe,YAAY,iDAAiD;AAClF,UAAM,kBAAkB,CAAC,gBAAgB,cAAc,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAE1F,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,QACP;AAAA,QACA,cAAc;AAAA,QACd,cAAc;AAAA,QACd,SAAS;AAAA,QACT,WAAW;AAAA,QACV,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AACF;AAEA,WAAW,cAAc;"}
1
+ {"version":3,"file":"icon-button.js","sources":["../../../../src/components/forms/button/icon-button.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useCallback } from 'react';\nimport { Icon, IconSize } from '../../system/icon';\nimport Button, { ButtonProps as BaseButtonProps } from './button';\nimport { ColorVariant } from '../../../theme/tokens';\n\nexport interface IconButtonProps extends Omit<BaseButtonProps, 'icon' | 'style' | 'children'> {\n icon: string;\n ariaLabel: string;\n iconSize?: IconSize;\n iconColor?: ColorVariant | 'currentColor';\n buttonStyle?: BaseButtonProps['style'];\n /** Visual style to apply on hover - alias of `hoverStyle`, consistent with `buttonStyle` naming */\n hoverButtonStyle?: BaseButtonProps['hoverStyle'];\n /** Remove the default button sizing so the icon renders without extra padding */\n noPadding?: boolean;\n /** Icon to display on hover (if not provided, hover will keep the same icon) */\n hoverIcon?: string;\n children?: never;\n}\n\n/**\n * IconButton - Icon-only button using Icon wrapper for consistent icon sizing\n * Usage: <IconButton icon=\"plus\" iconSize=\"md\" ariaLabel=\"Add item\" />\n */\nconst IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(\n (\n {\n icon,\n iconSize = 'md',\n iconColor = 'currentColor',\n buttonStyle,\n hoverButtonStyle,\n hoverStyle,\n noPadding = false,\n ariaLabel,\n hoverIcon,\n onMouseEnter,\n onMouseLeave,\n onClick,\n className = '',\n ...rest\n },\n ref\n ) => {\n const [isHovered, setIsHovered] = useState(false);\n const buttonRef = useRef<HTMLButtonElement | null>(null);\n\n // Merged callback ref that handles both internal and forwarded refs\n const mergedRef = useCallback(\n (node: HTMLButtonElement | null) => {\n // Update internal ref\n buttonRef.current = node;\n\n // Forward to external ref\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref]\n );\n\n const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {\n setIsHovered(true);\n onMouseEnter?.(e);\n };\n\n const handleMouseLeave = (e: React.MouseEvent<HTMLButtonElement>) => {\n setIsHovered(false);\n onMouseLeave?.(e);\n };\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n // Reset hover state on click - handles click-drag scenarios\n // where mouseLeave might not fire\n setIsHovered(false);\n onClick?.(e);\n };\n\n // Global mousemove listener to detect when mouse leaves button during drag\n useEffect(() => {\n if (!hoverIcon || !isHovered) return;\n\n const handleGlobalMouseMove = (e: MouseEvent) => {\n if (!buttonRef.current) return;\n\n const rect = buttonRef.current.getBoundingClientRect();\n const isOutside =\n e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom;\n\n if (isOutside) {\n setIsHovered(false);\n }\n };\n\n document.addEventListener('mousemove', handleGlobalMouseMove);\n return () => {\n document.removeEventListener('mousemove', handleGlobalMouseMove);\n };\n }, [hoverIcon, isHovered]);\n\n const displayIcon = isHovered && hoverIcon ? hoverIcon : icon;\n const iconNode = displayIcon ? <Icon name={displayIcon} size={iconSize} color={iconColor} /> : null;\n const paddingClasses = noPadding ? '!px-0 !py-0 !h-auto !min-h-0 !w-auto !min-w-0' : '';\n const hoverClasses = noPadding ? 'hover:!bg-transparent active:!bg-transparent' : '';\n const mergedClassName = [paddingClasses, hoverClasses, className].filter(Boolean).join(' ');\n\n return (\n <Button\n ref={mergedRef}\n icon={iconNode}\n style={buttonStyle}\n hoverStyle={hoverButtonStyle ?? hoverStyle}\n ariaLabel={ariaLabel}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onClick={handleClick}\n className={mergedClassName}\n {...rest}\n />\n );\n }\n);\n\nIconButton.displayName = 'IconButton';\n\nexport default IconButton;\n"],"names":[],"mappings":";;;;AAwBA,MAAM,aAAa,MAAM;AAAA,EACvB,CACE;AAAA,IACE;AAAA,IACA,WAAW;AAAA,IACX,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,UAAM,YAAY,OAAiC,IAAI;AAGvD,UAAM,YAAY;AAAA,MAChB,CAAC,SAAmC;AAElC,kBAAU,UAAU;AAGpB,YAAI,OAAO,QAAQ,YAAY;AAC7B,cAAI,IAAI;AAAA,QACV,WAAW,KAAK;AACd,cAAI,UAAU;AAAA,QAChB;AAAA,MACF;AAAA,MACA,CAAC,GAAG;AAAA,IAAA;AAGN,UAAM,mBAAmB,CAAC,MAA2C;AACnE,mBAAa,IAAI;AACjB,qBAAe,CAAC;AAAA,IAClB;AAEA,UAAM,mBAAmB,CAAC,MAA2C;AACnE,mBAAa,KAAK;AAClB,qBAAe,CAAC;AAAA,IAClB;AAEA,UAAM,cAAc,CAAC,MAA2C;AAG9D,mBAAa,KAAK;AAClB,gBAAU,CAAC;AAAA,IACb;AAGA,cAAU,MAAM;AACd,UAAI,CAAC,aAAa,CAAC,UAAW;AAE9B,YAAM,wBAAwB,CAAC,MAAkB;AAC/C,YAAI,CAAC,UAAU,QAAS;AAExB,cAAM,OAAO,UAAU,QAAQ,sBAAA;AAC/B,cAAM,YACJ,EAAE,UAAU,KAAK,QAAQ,EAAE,UAAU,KAAK,SAAS,EAAE,UAAU,KAAK,OAAO,EAAE,UAAU,KAAK;AAE9F,YAAI,WAAW;AACb,uBAAa,KAAK;AAAA,QACpB;AAAA,MACF;AAEA,eAAS,iBAAiB,aAAa,qBAAqB;AAC5D,aAAO,MAAM;AACX,iBAAS,oBAAoB,aAAa,qBAAqB;AAAA,MACjE;AAAA,IACF,GAAG,CAAC,WAAW,SAAS,CAAC;AAEzB,UAAM,cAAc,aAAa,YAAY,YAAY;AACzD,UAAM,WAAW,cAAc,oBAAC,MAAA,EAAK,MAAM,aAAa,MAAM,UAAU,OAAO,UAAA,CAAW,IAAK;AAC/F,UAAM,iBAAiB,YAAY,kDAAkD;AACrF,UAAM,eAAe,YAAY,iDAAiD;AAClF,UAAM,kBAAkB,CAAC,gBAAgB,cAAc,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAE1F,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,QACP,YAAY,oBAAoB;AAAA,QAChC;AAAA,QACA,cAAc;AAAA,QACd,cAAc;AAAA,QACd,SAAS;AAAA,QACT,WAAW;AAAA,QACV,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AACF;AAEA,WAAW,cAAc;"}
@@ -23,6 +23,8 @@ export type InlineButtonProps = {
23
23
  underline?: boolean;
24
24
  /** Icon size token - defaults to 'xs' to match text size */
25
25
  iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
26
+ /** Color variant to use when the button is hovered - overrides `variant` on hover */
27
+ hoverVariant?: ColorVariant;
26
28
  };
27
29
  /**
28
30
  * Inline Button - a minimal button designed to be embedded within text
@@ -1 +1 @@
1
- {"version":3,"file":"inline-button.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/button/inline-button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGrD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4CAA4C;IAC5C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,0BAA0B;IAC1B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC3D,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC7C,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,YAAY,6FA2DjB,CAAC;AAIF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"inline-button.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/button/inline-button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGrD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4CAA4C;IAC5C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,0BAA0B;IAC1B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC3D,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC5C,qFAAqF;IACrF,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,YAAY,6FAiEjB,CAAC;AAIF,eAAe,YAAY,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import React from "react";
2
+ import React, { useState } from "react";
3
3
  import { Icon } from "../../system/icon/icon.js";
4
4
  const InlineButton = React.forwardRef(
5
5
  ({
@@ -13,8 +13,11 @@ const InlineButton = React.forwardRef(
13
13
  variant = "primary",
14
14
  loading = false,
15
15
  underline = true,
16
- iconSize = "xs"
16
+ iconSize = "xs",
17
+ hoverVariant
17
18
  }, ref) => {
19
+ const [isHovered, setIsHovered] = useState(false);
20
+ const effectiveVariant = hoverVariant && isHovered ? hoverVariant : variant;
18
21
  const variantClasses = {
19
22
  neutral: "text-neutral hover:text-neutral-hover active:text-neutral-active",
20
23
  primary: "text-primary hover:text-primary-hover active:text-primary-active",
@@ -31,7 +34,7 @@ const InlineButton = React.forwardRef(
31
34
  "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
32
35
  disabled || loading ? "cursor-not-allowed opacity-50" : "cursor-pointer",
33
36
  underline ? "hover:underline" : "",
34
- variantClasses[variant],
37
+ variantClasses[effectiveVariant],
35
38
  className
36
39
  ].filter(Boolean).join(" ");
37
40
  const iconElement = icon ? /* @__PURE__ */ jsx(Icon, { name: icon, size: iconSize }) : null;
@@ -41,6 +44,8 @@ const InlineButton = React.forwardRef(
41
44
  ref,
42
45
  type,
43
46
  onClick,
47
+ onMouseEnter: hoverVariant ? () => setIsHovered(true) : void 0,
48
+ onMouseLeave: hoverVariant ? () => setIsHovered(false) : void 0,
44
49
  disabled: disabled || loading,
45
50
  "aria-label": ariaLabel,
46
51
  className: baseClasses,
@@ -1 +1 @@
1
- {"version":3,"file":"inline-button.js","sources":["../../../../src/components/forms/button/inline-button.tsx"],"sourcesContent":["import React from 'react';\nimport { ColorVariant } from '../../../theme/tokens';\nimport { Icon } from '../../system/icon/icon';\n\nexport type InlineButtonProps = {\n /** Button label content - typically text */\n children: React.ReactNode;\n /** Click event handler */\n onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;\n /** Accessible label (optional, children used by default) */\n ariaLabel?: string;\n /** Disables button interaction and applies disabled styling */\n disabled?: boolean;\n /** HTML button type attribute */\n type?: 'button' | 'submit' | 'reset';\n /** Additional CSS classes applied to the button */\n className?: string;\n /** Icon name string passed to the Icon component to display alongside button text */\n icon?: string;\n /** Color variant - defaults to primary for link-like appearance */\n variant?: ColorVariant;\n /** Shows loading spinner and disables interaction */\n loading?: boolean;\n /** Whether to show underline on hover (default: true) */\n underline?: boolean;\n /** Icon size token - defaults to 'xs' to match text size */\n iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n};\n\n/**\n * Inline Button - a minimal button designed to be embedded within text\n *\n * Perfect for use cases like:\n * - \"By signing in, you agree to our [Terms of Service]\"\n * - \"Learn more about [pricing]\"\n * - Inline actions within paragraphs\n *\n * Features:\n * - No background or padding by default\n * - Inherits surrounding text sizing\n * - Optional icon support\n * - Color variants for different contexts\n * - Hover underline (can be disabled)\n * - Fully accessible\n */\nconst InlineButton = React.forwardRef<HTMLButtonElement, InlineButtonProps>(\n (\n {\n children,\n onClick,\n ariaLabel,\n disabled = false,\n type = 'button',\n className = '',\n icon,\n variant = 'primary',\n loading = false,\n underline = true,\n iconSize = 'xs'\n },\n ref\n ) => {\n // Color variant styles\n const variantClasses: Record<ColorVariant, string> = {\n neutral: 'text-neutral hover:text-neutral-hover active:text-neutral-active',\n primary: 'text-primary hover:text-primary-hover active:text-primary-active',\n accent: 'text-accent hover:text-accent-hover active:text-accent-active',\n info: 'text-info hover:text-info-hover active:text-info-active',\n success: 'text-success hover:text-success-hover active:text-success-active',\n warning: 'text-warning hover:text-warning-hover active:text-warning-active',\n error: 'text-error hover:text-error-hover active:text-error-active'\n };\n\n // Base classes - minimal styling, inline with text\n const baseClasses = [\n 'inline-flex items-center gap-0.5',\n 'font-medium',\n 'transition-colors duration-200',\n 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n disabled || loading ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',\n underline ? 'hover:underline' : '',\n variantClasses[variant],\n className\n ]\n .filter(Boolean)\n .join(' ');\n\n // Clone icon and apply size to make it proportional to text\n const iconElement = icon ? <Icon name={icon} size={iconSize} /> : null;\n\n return (\n <button\n ref={ref}\n type={type}\n onClick={onClick}\n disabled={disabled || loading}\n aria-label={ariaLabel}\n className={baseClasses}\n >\n {children}\n {iconElement}\n </button>\n );\n }\n);\n\nInlineButton.displayName = 'InlineButton';\n\nexport default InlineButton;\n"],"names":[],"mappings":";;;AA6CA,MAAM,eAAe,MAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,OAAO;AAAA,IACP,YAAY;AAAA,IACZ;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA,GAEb,QACG;AAEH,UAAM,iBAA+C;AAAA,MACnD,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,IAAA;AAIT,UAAM,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,UAAU,kCAAkC;AAAA,MACxD,YAAY,oBAAoB;AAAA,MAChC,eAAe,OAAO;AAAA,MACtB;AAAA,IAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,UAAM,cAAc,OAAO,oBAAC,MAAA,EAAK,MAAM,MAAM,MAAM,UAAU,IAAK;AAElE,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,YAAY;AAAA,QACtB,cAAY;AAAA,QACZ,WAAW;AAAA,QAEV,UAAA;AAAA,UAAA;AAAA,UACA;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,aAAa,cAAc;"}
1
+ {"version":3,"file":"inline-button.js","sources":["../../../../src/components/forms/button/inline-button.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport { ColorVariant } from '../../../theme/tokens';\nimport { Icon } from '../../system/icon/icon';\n\nexport type InlineButtonProps = {\n /** Button label content - typically text */\n children: React.ReactNode;\n /** Click event handler */\n onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;\n /** Accessible label (optional, children used by default) */\n ariaLabel?: string;\n /** Disables button interaction and applies disabled styling */\n disabled?: boolean;\n /** HTML button type attribute */\n type?: 'button' | 'submit' | 'reset';\n /** Additional CSS classes applied to the button */\n className?: string;\n /** Icon name string passed to the Icon component to display alongside button text */\n icon?: string;\n /** Color variant - defaults to primary for link-like appearance */\n variant?: ColorVariant;\n /** Shows loading spinner and disables interaction */\n loading?: boolean;\n /** Whether to show underline on hover (default: true) */\n underline?: boolean;\n /** Icon size token - defaults to 'xs' to match text size */\n iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n /** Color variant to use when the button is hovered - overrides `variant` on hover */\n hoverVariant?: ColorVariant;\n};\n\n/**\n * Inline Button - a minimal button designed to be embedded within text\n *\n * Perfect for use cases like:\n * - \"By signing in, you agree to our [Terms of Service]\"\n * - \"Learn more about [pricing]\"\n * - Inline actions within paragraphs\n *\n * Features:\n * - No background or padding by default\n * - Inherits surrounding text sizing\n * - Optional icon support\n * - Color variants for different contexts\n * - Hover underline (can be disabled)\n * - Fully accessible\n */\nconst InlineButton = React.forwardRef<HTMLButtonElement, InlineButtonProps>(\n (\n {\n children,\n onClick,\n ariaLabel,\n disabled = false,\n type = 'button',\n className = '',\n icon,\n variant = 'primary',\n loading = false,\n underline = true,\n iconSize = 'xs',\n hoverVariant\n },\n ref\n ) => {\n const [isHovered, setIsHovered] = useState(false);\n const effectiveVariant = hoverVariant && isHovered ? hoverVariant : variant;\n\n // Color variant styles\n const variantClasses: Record<ColorVariant, string> = {\n neutral: 'text-neutral hover:text-neutral-hover active:text-neutral-active',\n primary: 'text-primary hover:text-primary-hover active:text-primary-active',\n accent: 'text-accent hover:text-accent-hover active:text-accent-active',\n info: 'text-info hover:text-info-hover active:text-info-active',\n success: 'text-success hover:text-success-hover active:text-success-active',\n warning: 'text-warning hover:text-warning-hover active:text-warning-active',\n error: 'text-error hover:text-error-hover active:text-error-active'\n };\n\n // Base classes - minimal styling, inline with text\n const baseClasses = [\n 'inline-flex items-center gap-0.5',\n 'font-medium',\n 'transition-colors duration-200',\n 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',\n disabled || loading ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',\n underline ? 'hover:underline' : '',\n variantClasses[effectiveVariant],\n className\n ]\n .filter(Boolean)\n .join(' ');\n\n // Clone icon and apply size to make it proportional to text\n const iconElement = icon ? <Icon name={icon} size={iconSize} /> : null;\n\n return (\n <button\n ref={ref}\n type={type}\n onClick={onClick}\n onMouseEnter={hoverVariant ? () => setIsHovered(true) : undefined}\n onMouseLeave={hoverVariant ? () => setIsHovered(false) : undefined}\n disabled={disabled || loading}\n aria-label={ariaLabel}\n className={baseClasses}\n >\n {children}\n {iconElement}\n </button>\n );\n }\n);\n\nInlineButton.displayName = 'InlineButton';\n\nexport default InlineButton;\n"],"names":[],"mappings":";;;AA+CA,MAAM,eAAe,MAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,OAAO;AAAA,IACP,YAAY;AAAA,IACZ;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,IACX;AAAA,EAAA,GAEF,QACG;AACH,UAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,UAAM,mBAAmB,gBAAgB,YAAY,eAAe;AAGpE,UAAM,iBAA+C;AAAA,MACnD,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,IAAA;AAIT,UAAM,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,UAAU,kCAAkC;AAAA,MACxD,YAAY,oBAAoB;AAAA,MAChC,eAAe,gBAAgB;AAAA,MAC/B;AAAA,IAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,UAAM,cAAc,OAAO,oBAAC,MAAA,EAAK,MAAM,MAAM,MAAM,UAAU,IAAK;AAElE,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,eAAe,MAAM,aAAa,IAAI,IAAI;AAAA,QACxD,cAAc,eAAe,MAAM,aAAa,KAAK,IAAI;AAAA,QACzD,UAAU,YAAY;AAAA,QACtB,cAAY;AAAA,QACZ,WAAW;AAAA,QAEV,UAAA;AAAA,UAAA;AAAA,UACA;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,aAAa,cAAc;"}
@@ -47,7 +47,7 @@ function Switch({
47
47
  };
48
48
  const getVariantColors = () => {
49
49
  if (!checked) {
50
- return "bg-input border-input";
50
+ return "bg-neutral/50 border-input ";
51
51
  }
52
52
  switch (variant) {
53
53
  case "accent":
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","sources":["../../../../src/components/forms/switch/switch.tsx"],"sourcesContent":["import React from 'react';\n\nexport type SwitchProps = {\n /** Controlled checked state - true for on, false for off */\n checked?: boolean;\n /** Change handler receiving the new boolean checked state */\n onChange?: (checked: boolean) => void;\n /** Disables switch interaction and applies disabled styling */\n disabled?: boolean;\n /** Additional CSS classes applied to the switch container */\n className?: string;\n /** Accessible label for screen readers when visual label is not present */\n ariaLabel?: string;\n /** HTML id attribute for the underlying input element */\n id?: string;\n /** Form input name attribute */\n name?: string;\n /** Size variant affecting switch dimensions - uses unified size system */\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n /** Color variant affecting background when checked - defaults to primary if not specified */\n variant?: 'primary' | 'accent' | 'success' | 'warning' | 'info' | 'error' | 'neutral';\n};\n\n/**\n * Accessible Switch/Toggle component - DaisyUI inspired\n * Uses pure Tailwind classes for styling\n */\nfunction Switch({\n checked = false,\n onChange,\n disabled = false,\n className = '',\n ariaLabel,\n id,\n name,\n size = 'md',\n variant\n}: Readonly<SwitchProps>) {\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n onChange?.(e.target.checked);\n };\n\n // Size configurations\n const sizeStyles = {\n xs: {\n container: 'h-4 w-8',\n handle: 'h-3 w-3',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-4' : 'translate-x-0'\n },\n sm: {\n container: 'h-5 w-10',\n handle: 'h-4 w-4',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-5' : 'translate-x-0'\n },\n md: {\n container: 'h-6 w-12',\n handle: 'h-5 w-5',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-6' : 'translate-x-0'\n },\n lg: {\n container: 'h-7 w-14',\n handle: 'h-6 w-6',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-7' : 'translate-x-0'\n },\n xl: {\n container: 'h-8 w-16',\n handle: 'h-7 w-7',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-8' : 'translate-x-0'\n }\n };\n\n // Color variants\n const getVariantColors = () => {\n if (!checked) {\n return 'bg-input border-input';\n }\n\n switch (variant) {\n case 'accent':\n return 'bg-accent border-accent';\n case 'success':\n return 'bg-success border-success';\n case 'warning':\n return 'bg-warning border-warning';\n case 'info':\n return 'bg-info border-info';\n case 'error':\n return 'bg-error border-error';\n case 'neutral':\n return 'bg-neutral border-neutral';\n case 'primary':\n default:\n return 'bg-primary border-primary';\n }\n };\n\n const styles = sizeStyles[size];\n\n return (\n <label\n className={`relative inline-flex ${styles.container} ${styles.padding} items-center rounded-full border transition-all duration-200 cursor-pointer ${\n disabled ? 'opacity-30 cursor-not-allowed' : ''\n } ${getVariantColors()} ${className}`}\n >\n <input\n type=\"checkbox\"\n role=\"switch\"\n checked={checked}\n onChange={handleChange}\n disabled={disabled}\n aria-label={ariaLabel}\n id={id}\n name={name}\n className=\"sr-only\"\n />\n <span\n className={`inline-block ${styles.handle} rounded-full bg-background shadow-sm transition-transform duration-200 ${styles.translate}`}\n />\n </label>\n );\n}\n\nSwitch.displayName = 'Switch';\n\nexport default Switch;\n"],"names":[],"mappings":";AA2BA,SAAS,OAAO;AAAA,EACd,UAAU;AAAA,EACV;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAA0B;AACxB,QAAM,eAAe,CAAC,MAA2C;AAC/D,eAAW,EAAE,OAAO,OAAO;AAAA,EAC7B;AAGA,QAAM,aAAa;AAAA,IACjB,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,IAEzC,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,IAEzC,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,IAEzC,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,IAEzC,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,EACzC;AAIF,QAAM,mBAAmB,MAAM;AAC7B,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,YAAQ,SAAA;AAAA,MACN,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL;AACE,eAAO;AAAA,IAAA;AAAA,EAEb;AAEA,QAAM,SAAS,WAAW,IAAI;AAE9B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,wBAAwB,OAAO,SAAS,IAAI,OAAO,OAAO,gFACnE,WAAW,kCAAkC,EAC/C,IAAI,iBAAA,CAAkB,IAAI,SAAS;AAAA,MAEnC,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,MAAK;AAAA,YACL;AAAA,YACA,UAAU;AAAA,YACV;AAAA,YACA,cAAY;AAAA,YACZ;AAAA,YACA;AAAA,YACA,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEZ;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,gBAAgB,OAAO,MAAM,2EAA2E,OAAO,SAAS;AAAA,UAAA;AAAA,QAAA;AAAA,MACrI;AAAA,IAAA;AAAA,EAAA;AAGN;AAEA,OAAO,cAAc;"}
1
+ {"version":3,"file":"switch.js","sources":["../../../../src/components/forms/switch/switch.tsx"],"sourcesContent":["import React from 'react';\n\nexport type SwitchProps = {\n /** Controlled checked state - true for on, false for off */\n checked?: boolean;\n /** Change handler receiving the new boolean checked state */\n onChange?: (checked: boolean) => void;\n /** Disables switch interaction and applies disabled styling */\n disabled?: boolean;\n /** Additional CSS classes applied to the switch container */\n className?: string;\n /** Accessible label for screen readers when visual label is not present */\n ariaLabel?: string;\n /** HTML id attribute for the underlying input element */\n id?: string;\n /** Form input name attribute */\n name?: string;\n /** Size variant affecting switch dimensions - uses unified size system */\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n /** Color variant affecting background when checked - defaults to primary if not specified */\n variant?: 'primary' | 'accent' | 'success' | 'warning' | 'info' | 'error' | 'neutral';\n};\n\n/**\n * Accessible Switch/Toggle component - DaisyUI inspired\n * Uses pure Tailwind classes for styling\n */\nfunction Switch({\n checked = false,\n onChange,\n disabled = false,\n className = '',\n ariaLabel,\n id,\n name,\n size = 'md',\n variant\n}: Readonly<SwitchProps>) {\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n onChange?.(e.target.checked);\n };\n\n // Size configurations\n const sizeStyles = {\n xs: {\n container: 'h-4 w-8',\n handle: 'h-3 w-3',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-4' : 'translate-x-0'\n },\n sm: {\n container: 'h-5 w-10',\n handle: 'h-4 w-4',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-5' : 'translate-x-0'\n },\n md: {\n container: 'h-6 w-12',\n handle: 'h-5 w-5',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-6' : 'translate-x-0'\n },\n lg: {\n container: 'h-7 w-14',\n handle: 'h-6 w-6',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-7' : 'translate-x-0'\n },\n xl: {\n container: 'h-8 w-16',\n handle: 'h-7 w-7',\n padding: 'p-0.5',\n translate: checked ? 'translate-x-8' : 'translate-x-0'\n }\n };\n\n // Color variants\n const getVariantColors = () => {\n if (!checked) {\n return 'bg-neutral/50 border-input ';\n }\n\n switch (variant) {\n case 'accent':\n return 'bg-accent border-accent';\n case 'success':\n return 'bg-success border-success';\n case 'warning':\n return 'bg-warning border-warning';\n case 'info':\n return 'bg-info border-info';\n case 'error':\n return 'bg-error border-error';\n case 'neutral':\n return 'bg-neutral border-neutral';\n case 'primary':\n default:\n return 'bg-primary border-primary';\n }\n };\n\n const styles = sizeStyles[size];\n\n return (\n <label\n className={`relative inline-flex ${styles.container} ${styles.padding} items-center rounded-full border transition-all duration-200 cursor-pointer ${\n disabled ? 'opacity-30 cursor-not-allowed' : ''\n } ${getVariantColors()} ${className}`}\n >\n <input\n type=\"checkbox\"\n role=\"switch\"\n checked={checked}\n onChange={handleChange}\n disabled={disabled}\n aria-label={ariaLabel}\n id={id}\n name={name}\n className=\"sr-only\"\n />\n <span\n className={`inline-block ${styles.handle} rounded-full bg-background shadow-sm transition-transform duration-200 ${styles.translate}`}\n />\n </label>\n );\n}\n\nSwitch.displayName = 'Switch';\n\nexport default Switch;\n"],"names":[],"mappings":";AA2BA,SAAS,OAAO;AAAA,EACd,UAAU;AAAA,EACV;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAA0B;AACxB,QAAM,eAAe,CAAC,MAA2C;AAC/D,eAAW,EAAE,OAAO,OAAO;AAAA,EAC7B;AAGA,QAAM,aAAa;AAAA,IACjB,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,IAEzC,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,IAEzC,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,IAEzC,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,IAEzC,IAAI;AAAA,MACF,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW,UAAU,kBAAkB;AAAA,IAAA;AAAA,EACzC;AAIF,QAAM,mBAAmB,MAAM;AAC7B,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,YAAQ,SAAA;AAAA,MACN,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL;AACE,eAAO;AAAA,IAAA;AAAA,EAEb;AAEA,QAAM,SAAS,WAAW,IAAI;AAE9B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,wBAAwB,OAAO,SAAS,IAAI,OAAO,OAAO,gFACnE,WAAW,kCAAkC,EAC/C,IAAI,iBAAA,CAAkB,IAAI,SAAS;AAAA,MAEnC,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,MAAK;AAAA,YACL;AAAA,YACA,UAAU;AAAA,YACV;AAAA,YACA,cAAY;AAAA,YACZ;AAAA,YACA;AAAA,YACA,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEZ;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,gBAAgB,OAAO,MAAM,2EAA2E,OAAO,SAAS;AAAA,UAAA;AAAA,QAAA;AAAA,MACrI;AAAA,IAAA;AAAA,EAAA;AAGN;AAEA,OAAO,cAAc;"}
@@ -16,7 +16,7 @@ function Code({ children, block = false, variant = "default", copy = false, clas
16
16
  }
17
17
  };
18
18
  const variantClasses = {
19
- default: "bg-muted-active text-foreground",
19
+ default: "bg-muted-active/50 text-foreground",
20
20
  primary: "text-primary",
21
21
  muted: "text-muted-foreground"
22
22
  };
@@ -1 +1 @@
1
- {"version":3,"file":"code.js","sources":["../../../../src/components/typography/code/code.tsx"],"sourcesContent":["import { ReactNode, useRef, useState } from 'react';\nimport Icon from '../../system/icon/icon';\n\nexport type CodeProps = {\n /** Code content to be displayed */\n children: ReactNode;\n /** Whether to render as a code block (pre + code) or inline code\n * @default false\n */\n block?: boolean;\n /** Visual style variant\n * @default 'default'\n */\n variant?: 'default' | 'primary' | 'muted';\n /** Show copy-to-clipboard button\n * @default false\n */\n copy?: boolean;\n /** Additional CSS classes for custom styling */\n className?: string;\n};\n\n/**\n * Code - Styled code snippets (inline or block)\n */\nfunction Code({ children, block = false, variant = 'default', copy = false, className = '' }: Readonly<CodeProps>) {\n const codeRef = useRef<HTMLElement>(null);\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n if (!codeRef.current) return;\n\n try {\n const textContent = codeRef.current.textContent || '';\n await navigator.clipboard.writeText(textContent);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('Failed to copy code:', err);\n }\n };\n\n const variantClasses = {\n default: 'bg-muted-active text-foreground',\n primary: 'text-primary',\n muted: 'text-muted-foreground'\n };\n\n const baseClasses = `font-mono ${variantClasses[variant]}`;\n\n if (block) {\n const blockElement = (\n <pre className={`${baseClasses} p-4 rounded-lg overflow-x-auto border border-border ${className}`} ref={codeRef}>\n <code>{children}</code>\n </pre>\n );\n\n if (copy) {\n return (\n <div className=\"relative group\">\n {blockElement}\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"absolute top-2 right-2 inline-flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity focus:opacity-100 cursor-pointer bg-background/80 backdrop-blur-sm p-1.5 rounded border border-border hover:bg-muted-hover\"\n aria-label=\"Copy to clipboard\"\n >\n <Icon\n name={copied ? 'check' : 'copy'}\n size=\"sm\"\n color={copied ? 'success' : 'currentColor'}\n className=\"transition-all\"\n />\n </button>\n </div>\n );\n }\n\n return blockElement;\n }\n\n const inlineElement = (\n <code className={`${baseClasses} px-1.5 py-0.5 rounded text-sm ${className}`} ref={codeRef}>\n {children}\n </code>\n );\n\n if (copy) {\n return (\n <span className=\"inline-flex items-center gap-2 group\">\n {inlineElement}\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"inline-flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity focus:opacity-100 cursor-pointer\"\n aria-label=\"Copy to clipboard\"\n >\n <Icon\n name={copied ? 'check' : 'copy'}\n size=\"sm\"\n color={copied ? 'success' : 'currentColor'}\n className=\"transition-all\"\n />\n </button>\n </span>\n );\n }\n\n return inlineElement;\n}\n\nCode.displayName = 'Code';\n\nexport default Code;\n"],"names":[],"mappings":";;;AAyBA,SAAS,KAAK,EAAE,UAAU,QAAQ,OAAO,UAAU,WAAW,OAAO,OAAO,YAAY,GAAA,GAA2B;AACjH,QAAM,UAAU,OAAoB,IAAI;AACxC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAE1C,QAAM,aAAa,YAAY;AAC7B,QAAI,CAAC,QAAQ,QAAS;AAEtB,QAAI;AACF,YAAM,cAAc,QAAQ,QAAQ,eAAe;AACnD,YAAM,UAAU,UAAU,UAAU,WAAW;AAC/C,gBAAU,IAAI;AACd,iBAAW,MAAM,UAAU,KAAK,GAAG,GAAI;AAAA,IACzC,SAAS,KAAK;AAEZ,cAAQ,MAAM,wBAAwB,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,iBAAiB;AAAA,IACrB,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,EAAA;AAGT,QAAM,cAAc,aAAa,eAAe,OAAO,CAAC;AAExD,MAAI,OAAO;AACT,UAAM,eACJ,oBAAC,OAAA,EAAI,WAAW,GAAG,WAAW,wDAAwD,SAAS,IAAI,KAAK,SACtG,UAAA,oBAAC,QAAA,EAAM,UAAS,GAClB;AAGF,QAAI,MAAM;AACR,aACE,qBAAC,OAAA,EAAI,WAAU,kBACZ,UAAA;AAAA,QAAA;AAAA,QACD;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YACV,cAAW;AAAA,YAEX,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAM,SAAS,UAAU;AAAA,gBACzB,MAAK;AAAA,gBACL,OAAO,SAAS,YAAY;AAAA,gBAC5B,WAAU;AAAA,cAAA;AAAA,YAAA;AAAA,UACZ;AAAA,QAAA;AAAA,MACF,GACF;AAAA,IAEJ;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,gBACJ,oBAAC,QAAA,EAAK,WAAW,GAAG,WAAW,kCAAkC,SAAS,IAAI,KAAK,SAChF,SAAA,CACH;AAGF,MAAI,MAAM;AACR,WACE,qBAAC,QAAA,EAAK,WAAU,wCACb,UAAA;AAAA,MAAA;AAAA,MACD;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,SAAS,UAAU;AAAA,cACzB,MAAK;AAAA,cACL,OAAO,SAAS,YAAY;AAAA,cAC5B,WAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACZ;AAAA,MAAA;AAAA,IACF,GACF;AAAA,EAEJ;AAEA,SAAO;AACT;AAEA,KAAK,cAAc;"}
1
+ {"version":3,"file":"code.js","sources":["../../../../src/components/typography/code/code.tsx"],"sourcesContent":["import { ReactNode, useRef, useState } from 'react';\nimport Icon from '../../system/icon/icon';\n\nexport type CodeProps = {\n /** Code content to be displayed */\n children: ReactNode;\n /** Whether to render as a code block (pre + code) or inline code\n * @default false\n */\n block?: boolean;\n /** Visual style variant\n * @default 'default'\n */\n variant?: 'default' | 'primary' | 'muted';\n /** Show copy-to-clipboard button\n * @default false\n */\n copy?: boolean;\n /** Additional CSS classes for custom styling */\n className?: string;\n};\n\n/**\n * Code - Styled code snippets (inline or block)\n */\nfunction Code({ children, block = false, variant = 'default', copy = false, className = '' }: Readonly<CodeProps>) {\n const codeRef = useRef<HTMLElement>(null);\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n if (!codeRef.current) return;\n\n try {\n const textContent = codeRef.current.textContent || '';\n await navigator.clipboard.writeText(textContent);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('Failed to copy code:', err);\n }\n };\n\n const variantClasses = {\n default: 'bg-muted-active/50 text-foreground',\n primary: 'text-primary',\n muted: 'text-muted-foreground'\n };\n\n const baseClasses = `font-mono ${variantClasses[variant]}`;\n\n if (block) {\n const blockElement = (\n <pre className={`${baseClasses} p-4 rounded-lg overflow-x-auto border border-border ${className}`} ref={codeRef}>\n <code>{children}</code>\n </pre>\n );\n\n if (copy) {\n return (\n <div className=\"relative group\">\n {blockElement}\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"absolute top-2 right-2 inline-flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity focus:opacity-100 cursor-pointer bg-background/80 backdrop-blur-sm p-1.5 rounded border border-border hover:bg-muted-hover\"\n aria-label=\"Copy to clipboard\"\n >\n <Icon\n name={copied ? 'check' : 'copy'}\n size=\"sm\"\n color={copied ? 'success' : 'currentColor'}\n className=\"transition-all\"\n />\n </button>\n </div>\n );\n }\n\n return blockElement;\n }\n\n const inlineElement = (\n <code className={`${baseClasses} px-1.5 py-0.5 rounded text-sm ${className}`} ref={codeRef}>\n {children}\n </code>\n );\n\n if (copy) {\n return (\n <span className=\"inline-flex items-center gap-2 group\">\n {inlineElement}\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"inline-flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity focus:opacity-100 cursor-pointer\"\n aria-label=\"Copy to clipboard\"\n >\n <Icon\n name={copied ? 'check' : 'copy'}\n size=\"sm\"\n color={copied ? 'success' : 'currentColor'}\n className=\"transition-all\"\n />\n </button>\n </span>\n );\n }\n\n return inlineElement;\n}\n\nCode.displayName = 'Code';\n\nexport default Code;\n"],"names":[],"mappings":";;;AAyBA,SAAS,KAAK,EAAE,UAAU,QAAQ,OAAO,UAAU,WAAW,OAAO,OAAO,YAAY,GAAA,GAA2B;AACjH,QAAM,UAAU,OAAoB,IAAI;AACxC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAE1C,QAAM,aAAa,YAAY;AAC7B,QAAI,CAAC,QAAQ,QAAS;AAEtB,QAAI;AACF,YAAM,cAAc,QAAQ,QAAQ,eAAe;AACnD,YAAM,UAAU,UAAU,UAAU,WAAW;AAC/C,gBAAU,IAAI;AACd,iBAAW,MAAM,UAAU,KAAK,GAAG,GAAI;AAAA,IACzC,SAAS,KAAK;AAEZ,cAAQ,MAAM,wBAAwB,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,iBAAiB;AAAA,IACrB,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,EAAA;AAGT,QAAM,cAAc,aAAa,eAAe,OAAO,CAAC;AAExD,MAAI,OAAO;AACT,UAAM,eACJ,oBAAC,OAAA,EAAI,WAAW,GAAG,WAAW,wDAAwD,SAAS,IAAI,KAAK,SACtG,UAAA,oBAAC,QAAA,EAAM,UAAS,GAClB;AAGF,QAAI,MAAM;AACR,aACE,qBAAC,OAAA,EAAI,WAAU,kBACZ,UAAA;AAAA,QAAA;AAAA,QACD;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YACV,cAAW;AAAA,YAEX,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAM,SAAS,UAAU;AAAA,gBACzB,MAAK;AAAA,gBACL,OAAO,SAAS,YAAY;AAAA,gBAC5B,WAAU;AAAA,cAAA;AAAA,YAAA;AAAA,UACZ;AAAA,QAAA;AAAA,MACF,GACF;AAAA,IAEJ;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,gBACJ,oBAAC,QAAA,EAAK,WAAW,GAAG,WAAW,kCAAkC,SAAS,IAAI,KAAK,SAChF,SAAA,CACH;AAGF,MAAI,MAAM;AACR,WACE,qBAAC,QAAA,EAAK,WAAU,wCACb,UAAA;AAAA,MAAA;AAAA,MACD;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,SAAS,UAAU;AAAA,cACzB,MAAK;AAAA,cACL,OAAO,SAAS,YAAY;AAAA,cAC5B,WAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QACZ;AAAA,MAAA;AAAA,IACF,GACF;AAAA,EAEJ;AAEA,SAAO;AACT;AAEA,KAAK,cAAc;"}