@veracity/vui 4.0.0-alpha.366771.2602181640 → 4.0.0-alpha.375407.2602201014

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 (58) hide show
  1. package/dist/button/button.d.ts +3 -3
  2. package/dist/button/button.d.ts.map +1 -1
  3. package/dist/button/button.js +229 -169
  4. package/dist/button/button.js.map +1 -1
  5. package/dist/button/buttonIcon.js +12 -3
  6. package/dist/button/buttonIcon.js.map +1 -1
  7. package/dist/button/consts.js +3 -3
  8. package/dist/button/consts.js.map +1 -1
  9. package/dist/button/theme.js +155 -166
  10. package/dist/button/theme.js.map +1 -1
  11. package/dist/buttonGroup/buttonGroup.d.ts.map +1 -1
  12. package/dist/buttonGroup/buttonGroup.js +2 -18
  13. package/dist/buttonGroup/buttonGroup.js.map +1 -1
  14. package/dist/core/index.d.ts +1 -1
  15. package/dist/core/links.d.ts.map +1 -1
  16. package/dist/core/links.js +4 -5
  17. package/dist/core/links.js.map +1 -1
  18. package/dist/core/vuiProvider/consts.d.ts +49 -1
  19. package/dist/core/vuiProvider/consts.d.ts.map +1 -1
  20. package/dist/core/vuiProvider/consts.js +651 -1
  21. package/dist/core/vuiProvider/consts.js.map +1 -1
  22. package/dist/core/vuiProvider/globalStyle.js +3 -1
  23. package/dist/core/vuiProvider/globalStyle.js.map +1 -1
  24. package/dist/core/vuiProvider/index.d.ts +1 -1
  25. package/dist/index.d.ts +3 -2
  26. package/dist/index.js +3 -2
  27. package/dist/link/link.d.ts.map +1 -1
  28. package/dist/link/link.js +3 -2
  29. package/dist/link/link.js.map +1 -1
  30. package/dist/message/consts.js +4 -4
  31. package/dist/message/consts.js.map +1 -1
  32. package/dist/notification/consts.js +3 -14
  33. package/dist/notification/consts.js.map +1 -1
  34. package/dist/notification/index.d.ts +1 -0
  35. package/dist/notification/notification.d.ts +2 -0
  36. package/dist/notification/notification.d.ts.map +1 -1
  37. package/dist/notification/notification.js +18 -10
  38. package/dist/notification/notification.js.map +1 -1
  39. package/dist/notification/notification.types.d.ts +4 -3
  40. package/dist/notification/notification.types.d.ts.map +1 -1
  41. package/dist/notification/notificationAction.d.ts +12 -0
  42. package/dist/notification/notificationAction.d.ts.map +1 -0
  43. package/dist/notification/notificationAction.js +37 -0
  44. package/dist/notification/notificationAction.js.map +1 -0
  45. package/dist/notification/notificationButton.d.ts.map +1 -1
  46. package/dist/notification/notificationButton.js +1 -13
  47. package/dist/notification/notificationButton.js.map +1 -1
  48. package/dist/notification/notificationIcon.js +0 -1
  49. package/dist/notification/notificationIcon.js.map +1 -1
  50. package/dist/notification/notificationText.js +0 -5
  51. package/dist/notification/notificationText.js.map +1 -1
  52. package/dist/notification/theme.js +119 -43
  53. package/dist/notification/theme.js.map +1 -1
  54. package/dist/prose/prose.js +1 -1
  55. package/dist/prose/prose.js.map +1 -1
  56. package/dist/theme/components.d.ts +85 -18
  57. package/dist/theme/defaultTheme.d.ts +85 -18
  58. package/package.json +1 -1
@@ -44,11 +44,11 @@ declare const ButtonBase: styled_components0.StyledComponent<"button", styled_co
44
44
  text?: React.ReactNode;
45
45
  }, never>;
46
46
  /**
47
- * Triggers given actions on click. Supports multiple states, sizes and variants.
47
+ * Button Component
48
48
  *
49
- * Comes with additional props for extra behavior, like loading, disabled or elevated.
49
+ * A versatile button component that supports multiple states, sizes, variants, and behaviors.
50
+ * Part of the VUI design system with built-in accessibility features.
50
51
  *
51
- * Exposes some props to the children via context.
52
52
  */
53
53
  declare const Button: VuiComponent<"button", ButtonProps> & {
54
54
  Icon: typeof ButtonIcon;
@@ -1 +1 @@
1
- {"version":3,"file":"button.d.ts","names":[],"sources":["../../src/button/button.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiCa,UAAA,qBAAU,eAAA,WAsCtB,kBAAA,CAtCsB,YAAA,EAAA,cAAA,GAAA,oBAAA,GAAA,WAAA,GAAA,cAAA,GAAA,gBAAA,GAAA,iBAAA,GAAA,eAAA,GAAA,gBAAA,GAAA,mBAAA,GAAA,qBAAA,GAAA,sBAAA,GAAA,oBAAA,GAAA,gBAAA,GAAA,mBAAA,GAAA,qBAAA,GAAA,sBAAA,GAAA,oBAAA,GAAA,gBAAA,GAAA,iBAAA,GAAA,YAAA,GAAA,iBAAA,GAAA,iBAAA,GAAA,iBAAA,GAAA,YAAA,GAAA,YAAA,GAAA,mBAAA,GAAA,mBAAA,GAAA,gBAAA,GAAA,gBAAA,GAAA,SAAA,GAAA,cAAA,GAAA,YAAA,GAAA,cAAA,GAAA,eAAA,GAAA,YAAA,GAAA,eAAA,GAAA,iBAAA,GAAA,mBAAA,GAAA,iBAAA,GAAA,aAAA,GAAA,aAAA,GAAA,eAAA,GAAA,cAAA,GAAA,kBAAA,GAAA,SAAA,GAAA,gBAAA,GAAA,cAAA,GAAA,UAAA,GAAA,QAAA,GAAA,QAAA,GAAA,QAAA,GAAA,cAAA,GAAA,WAAA,GAAA,eAAA,GAAA,YAAA,GAAA,iBAAA,GAAA,oBAAA,GAAA,iBAAA,GAAA,wBAAA,GAAA,qBAAA,GAAA,sBAAA,GAAA,aAAA,GAAA,eAAA,GAAA,WAAA,GAAA,kBAAA,GAAA,WAAA,GAAA,eAAA,GAAA,gBAAA,GAAA,UAAA,GAAA,cAAA,GAAA,aAAA,GAAA,cAAA,GAAA,cAAA,GAAA,aAAA,GAAA,WAAA,GAAA,QAAA,GAAA,UAAA,GAAA,WAAA,GAAA,SAAA,GAAA,eAAA,GAAA,uBAAA,GAAA,cAAA,GAAA,aAAA,GAAA,UAAA,GAAA,WAAA,GAAA,aAAA,GAAA,cAAA,GAAA,aAAA,GAAA,cAAA,GAAA,WAAA,GAAA,cAAA,GAAA,gBAAA,GAAA,iBAAA,GAAA,eAAA,GAAA,YAAA,GAAA,YAAA,GAAA,YAAA,GAAA,eAAA,GAAA,iBAAA,GAAA,kBAAA,GAAA,gBAAA,GAAA,aAAA,GAAA,aAAA,GAAA,WAAA,GAAA,WAAA,GAAA,kBAAA,GAAA,kBAAA,GAAA,mBAAA,GAAA,gBAAA,GAAA,cAAA,GAAA,oBAAA,GAAA,eAAA,GAAA,eAAA,GAAA,WAAA,GAAA,UAAA,GAAA,UAAA,GAAA,UAAA,GAAA,WAAA,GAAA,WAAA,GAAA,eAAA,GAAA,uBAAA,GAAA,uBAAA,GAAA,6BAAA,GAAA,oBAAA,GAAA,eAAA,GAAA,aAAA,GAAA,cAAA,GAAA,eAAA,GAAA,eAAA,GAAA,UAAA,GAAA,kBAAA,GAAA,mBAAA,GAAA,cAAA,GAAA,kBAAA,GAAA,kBAAA,GAAA,eAAA,GAAA,iBAAA,GAAA,kBAAA,GAAA,sBAAA,GAAA,cAAA,GAAA,YAAA;;iBAAA,QAAA;;;;;;;;;;;;;;;;;;;;;;cAiMV,MAAA,EA6JP,YAAA,WAAuB,WAAA;EAC3B,IAAA,SAAa,UAAA;EACb,IAAA,SAAa,UAAA;AAAA"}
1
+ {"version":3,"file":"button.d.ts","names":[],"sources":["../../src/button/button.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiFa,UAAA,qBAAU,eAAA,WAmCtB,kBAAA,CAnCsB,YAAA,EAAA,cAAA,GAAA,oBAAA,GAAA,WAAA,GAAA,cAAA,GAAA,gBAAA,GAAA,iBAAA,GAAA,eAAA,GAAA,gBAAA,GAAA,mBAAA,GAAA,qBAAA,GAAA,sBAAA,GAAA,oBAAA,GAAA,gBAAA,GAAA,mBAAA,GAAA,qBAAA,GAAA,sBAAA,GAAA,oBAAA,GAAA,gBAAA,GAAA,iBAAA,GAAA,YAAA,GAAA,iBAAA,GAAA,iBAAA,GAAA,iBAAA,GAAA,YAAA,GAAA,YAAA,GAAA,mBAAA,GAAA,mBAAA,GAAA,gBAAA,GAAA,gBAAA,GAAA,SAAA,GAAA,cAAA,GAAA,YAAA,GAAA,cAAA,GAAA,eAAA,GAAA,YAAA,GAAA,eAAA,GAAA,iBAAA,GAAA,mBAAA,GAAA,iBAAA,GAAA,aAAA,GAAA,aAAA,GAAA,eAAA,GAAA,cAAA,GAAA,kBAAA,GAAA,SAAA,GAAA,gBAAA,GAAA,cAAA,GAAA,UAAA,GAAA,QAAA,GAAA,QAAA,GAAA,QAAA,GAAA,cAAA,GAAA,WAAA,GAAA,eAAA,GAAA,YAAA,GAAA,iBAAA,GAAA,oBAAA,GAAA,iBAAA,GAAA,wBAAA,GAAA,qBAAA,GAAA,sBAAA,GAAA,aAAA,GAAA,eAAA,GAAA,WAAA,GAAA,kBAAA,GAAA,WAAA,GAAA,eAAA,GAAA,gBAAA,GAAA,UAAA,GAAA,cAAA,GAAA,aAAA,GAAA,cAAA,GAAA,cAAA,GAAA,aAAA,GAAA,WAAA,GAAA,QAAA,GAAA,UAAA,GAAA,WAAA,GAAA,SAAA,GAAA,eAAA,GAAA,uBAAA,GAAA,cAAA,GAAA,aAAA,GAAA,UAAA,GAAA,WAAA,GAAA,aAAA,GAAA,cAAA,GAAA,aAAA,GAAA,cAAA,GAAA,WAAA,GAAA,cAAA,GAAA,gBAAA,GAAA,iBAAA,GAAA,eAAA,GAAA,YAAA,GAAA,YAAA,GAAA,YAAA,GAAA,eAAA,GAAA,iBAAA,GAAA,kBAAA,GAAA,gBAAA,GAAA,aAAA,GAAA,aAAA,GAAA,WAAA,GAAA,WAAA,GAAA,kBAAA,GAAA,kBAAA,GAAA,mBAAA,GAAA,gBAAA,GAAA,cAAA,GAAA,oBAAA,GAAA,eAAA,GAAA,eAAA,GAAA,WAAA,GAAA,UAAA,GAAA,UAAA,GAAA,UAAA,GAAA,WAAA,GAAA,WAAA,GAAA,eAAA,GAAA,uBAAA,GAAA,uBAAA,GAAA,6BAAA,GAAA,oBAAA,GAAA,eAAA,GAAA,aAAA,GAAA,cAAA,GAAA,eAAA,GAAA,eAAA,GAAA,UAAA,GAAA,kBAAA,GAAA,mBAAA,GAAA,cAAA,GAAA,kBAAA,GAAA,kBAAA,GAAA,eAAA,GAAA,iBAAA,GAAA,kBAAA,GAAA,sBAAA,GAAA,cAAA,GAAA,YAAA;;iBAAA,QAAA;;;;;;;;;;;;;;;;;;;;;;cAmQV,MAAA,EA4KP,YAAA,WAAuB,WAAA;EAC3B,IAAA,SAAa,UAAA;EACb,IAAA,SAAa,UAAA;AAAA"}
@@ -19,6 +19,7 @@ const DEFAULT_BOX_SHADOW = "md";
19
19
  const DEFAULT_DISABLED_OPACITY = .5;
20
20
  const BUTTON_TYPE = "button";
21
21
  const FOCUS_RING_SIZE = 3;
22
+ const MENU_FOCUS_BORDER_WIDTH = 3;
22
23
  const ContentWrapper = styled.span`
23
24
  visibility: hidden;
24
25
  `;
@@ -34,168 +35,203 @@ const ButtonBase = styled.buttonBox`
34
35
  align-items: center;
35
36
  display: inline-flex;
36
37
  flex-shrink: 0;
37
- font-weight: medium;
38
+ font-weight: var(--vui-button-font-weight, medium);
38
39
  justify-content: center;
39
40
  line-height: normal;
40
41
  overflow: hidden;
41
42
  position: relative;
42
- transition-duration: fast;
43
+ transition-duration: var(--vui-button-transition-duration, fast);
43
44
  user-select: none;
44
45
  width: fit-content;
45
46
 
46
- &.vui-button-variant-menuLight.vui-button-variant-menuLight.vui-active:hover {
47
- background-color: #d3eef8;
48
- color: #003591;
49
- border-color-left: #003591;
50
- }
51
- &.vui-button-variant-menuDark.vui-button-variant-menuDark.vui-active:hover {
52
- background-color: #003591;
53
- color: #91ffba;
54
- border-color-left: #91ffba;
55
- }
56
- &.vui-button-variant-menuDark.vui-button-variant-menuDark:focus,
57
- &.vui-button-variant-menuDark.vui-button-variant-menuDark:focus-visible,
58
- &.vui-button-variant-menuLight.vui-button-variant-menuLight:focus,
59
- &.vui-button-variant-menuLight.vui-button-variant-menuLight:focus-visible {
47
+ /* Menu variant focus states - default removes focus ring
48
+ * Used when focusAsBorder class is NOT present (standard focus ring behavior)
49
+ */
50
+ &.vui-button-variant-menuDark:not(.focusAsBorder):focus,
51
+ &.vui-button-variant-menuDark:not(.focusAsBorder):focus-visible,
52
+ &.vui-button-variant-menuLight:not(.focusAsBorder):focus,
53
+ &.vui-button-variant-menuLight:not(.focusAsBorder):focus-visible {
60
54
  box-shadow: none;
61
55
  outline: none;
62
- border-bottom: 0 solid transparent;
63
56
  }
64
- &.vui-button-variant-menuDark.vui-button-variant-menuDark.focusAsBorder:focus,
65
- &.vui-button-variant-menuDark.vui-button-variant-menuDark.focusAsBorder:focus-visible,
66
- &.vui-button-variant-menuLight.vui-button-variant-menuLight.focusAsBorder:focus,
67
- &.vui-button-variant-menuLight.vui-button-variant-menuLight.focusAsBorder:focus-visible {
57
+
58
+ /* Menu variant focus states - with border indicator for accessibility
59
+ * Used when focusAsBorder class IS present (alternative focus indication via border)
60
+ * Color: theme menuLight/menuDark.focusBorderBottomColor
61
+ */
62
+ &.vui-button-variant-menuDark.focusAsBorder:focus,
63
+ &.vui-button-variant-menuDark.focusAsBorder:focus-visible,
64
+ &.vui-button-variant-menuLight.focusAsBorder:focus,
65
+ &.vui-button-variant-menuLight.focusAsBorder:focus-visible {
68
66
  transition: border-bottom 0s linear;
69
- border-bottom: 3px solid #d3eef8;
67
+ border-bottom: ${MENU_FOCUS_BORDER_WIDTH}px solid var(--color-sky-blue-90); /* theme: focusBorderBottomColor */
70
68
  }
71
69
  `;
72
- const useButtonStyles = (mergedProps, variant) => {
73
- const styles = useStyleConfig("Button", {
74
- ...mergedProps,
75
- ...variant
76
- }) || {};
77
- return useMemo(() => {
78
- const { borderWidth = DEFAULT_BORDER_WIDTH, h = 32, spaceX = 0, ...buttonStyles } = styles.container || {};
79
- return {
80
- styles,
81
- borderWidth,
82
- h,
83
- spaceX,
84
- buttonStyles
85
- };
86
- }, [mergedProps, variant]);
87
- };
88
- const extractNumericValue = (value) => {
70
+ /**
71
+ * Extracts a numeric value from various input types (number, string, etc.)
72
+ * @param value - Value to extract number from
73
+ * @param fallback - Default value if extraction fails
74
+ * @returns Extracted number or fallback
75
+ */
76
+ const extractNumericValue = (value, fallback = DEFAULT_BORDER_WIDTH) => {
89
77
  if (typeof value === "number") return value;
90
78
  if (typeof value === "string") {
91
79
  const parsed = parseInt(value, 10);
92
- return isNaN(parsed) ? DEFAULT_BORDER_WIDTH : parsed;
80
+ if (!isNaN(parsed)) return parsed;
93
81
  }
94
- return DEFAULT_BORDER_WIDTH;
82
+ return fallback;
95
83
  };
96
- const useSpacingProps = (borderWidthProp, borderWidth, spaceX, icon, iconLeft, iconRight, isLoading, isSplit) => {
84
+ /**
85
+ * Consolidated hook for computing all button styles and props.
86
+ * Combines theme resolution, spacing, state-based styles, and className generation.
87
+ * Reduces memoization overhead by computing all derived values in a single pass.
88
+ *
89
+ * @param mergedProps - Combined props from ButtonGroup context and component props
90
+ * @param variant - Resolved variant object with theme overrides
91
+ * @param borderWidthProp - Optional border width override from props
92
+ * @param icon - Main icon (replaces all content when present)
93
+ * @param iconLeft - Left icon slot
94
+ * @param iconRight - Right icon slot
95
+ * @param isLoading - Loading state flag
96
+ * @param isSplit - Deprecated: Split layout mode
97
+ * @param disabled - Disabled state flag
98
+ * @param isActive - Active state flag
99
+ * @param isRound - Round/pill-shaped borders flag
100
+ * @param isElevated - Box shadow elevation flag
101
+ * @param hasState - Whether button has a state overlay
102
+ * @param isFullWidth - Full width layout flag
103
+ * @param className - Additional CSS classes
104
+ * @param variantProp - Variant name for class generation
105
+ * @returns Computed styles, spacing, props, and className
106
+ */
107
+ const useButtonComputed = (mergedProps, variant, borderWidthProp, icon, iconLeft, iconRight, isLoading, isSplit, disabled, isActive, isRound, isElevated, hasState, isFullWidth, className, variantProp) => {
108
+ const styles = useStyleConfig("Button", {
109
+ ...mergedProps,
110
+ ...variant
111
+ }) || {};
97
112
  return useMemo(() => {
113
+ const container = styles.container || {};
114
+ const { borderWidth = DEFAULT_BORDER_WIDTH, h = 32, spaceX = 0, ...buttonStyles } = container;
115
+ const normalizedStyles = {
116
+ ...buttonStyles,
117
+ activeColor: buttonStyles.activeColor ?? buttonStyles.color,
118
+ hoverColor: buttonStyles.hoverColor ?? buttonStyles.color
119
+ };
98
120
  const border = extractNumericValue(borderWidthProp) || borderWidth || DEFAULT_BORDER_WIDTH;
99
121
  const spaceXItem = spaceX - border;
100
122
  const spaceXText = spaceX * 2 - border;
101
123
  return {
102
- border,
103
- iconLeftMr: `${spaceXItem + 1}px`,
104
- iconRightMl: isSplit ? "auto" : `${spaceXItem + 1}px`,
105
- pl: `${icon || iconLeft || isLoading ? spaceXItem : spaceXText}px`,
106
- pr: `${icon || iconRight ? spaceXItem : spaceXText}px`
124
+ styles,
125
+ borderWidth,
126
+ h,
127
+ spaceX,
128
+ buttonStyles: normalizedStyles,
129
+ spacing: {
130
+ borderWidth: border,
131
+ pl: `var(--vui-button-px-start, ${icon || iconLeft || isLoading ? spaceXItem : spaceXText}px)`,
132
+ pr: `var(--vui-button-px-end, ${icon || iconRight ? spaceXItem : spaceXText}px)`
133
+ },
134
+ disabledProps: disabled ? filterUndefined({
135
+ "aria-disabled": disabled,
136
+ bg: container?.disabledBg ?? void 0,
137
+ borderColor: container?.disabledBorderColor ?? void 0,
138
+ color: container?.disabledColor ?? void 0,
139
+ hoverBg: container?.disabledBg ?? normalizedStyles.bg,
140
+ hoverBorderColor: container?.disabledBorderColor ?? normalizedStyles.borderColor,
141
+ hoverColor: container?.disabledColor ?? normalizedStyles.color,
142
+ activeBg: container?.disabledBg ?? normalizedStyles.bg,
143
+ cursor: "not-allowed",
144
+ opacity: container?.disabledOpacity ?? DEFAULT_DISABLED_OPACITY
145
+ }) : {},
146
+ activeProps: isActive ? filterUndefined({
147
+ bg: container?.activeBg,
148
+ borderColor: container?.activeBorderColor,
149
+ color: container?.activeColor,
150
+ hoverBg: container?.activeBg,
151
+ hoverBorderColor: container?.activeBorderColor,
152
+ hoverColor: container?.activeColor,
153
+ cursor: "default"
154
+ }) : {},
155
+ aliasedProps: filterUndefined({
156
+ borderRadius: isRound ? h / 2 : `var(--vui-button-border-radius, ${container?.borderRadius || 0}px)`,
157
+ boxShadow: isElevated ? `var(--vui-button-box-shadow, ${DEFAULT_BOX_SHADOW})` : void 0,
158
+ minW: icon ? 0 : `var(--vui-button-min-w, auto)`,
159
+ pointerEvents: hasState ? "none" : void 0,
160
+ w: isFullWidth ? "100%" : void 0
161
+ }),
162
+ buttonClassName: cs("vui-button", className, isActive && activeClassName, variantProp ? `vui-button-variant-${variantProp}` : void 0)
107
163
  };
108
164
  }, [
165
+ styles,
166
+ mergedProps,
167
+ variant,
109
168
  borderWidthProp,
110
- borderWidth,
111
- spaceX,
112
169
  icon,
113
170
  iconLeft,
114
171
  iconRight,
115
172
  isLoading,
116
- isSplit
117
- ]);
118
- };
119
- const useDisabledProps = (disabled, styles) => {
120
- return useMemo(() => disabled ? {
121
- "aria-disabled": disabled,
122
- disabledBg: styles?.container?.disabledBg ?? "bg",
123
- disabledBorderColor: styles?.container?.disabledBorderColor ?? "borderColor",
124
- disabledColor: styles?.container?.disabledColor ?? "color",
125
- hoverBg: styles?.container?.disabledBg ?? "bg",
126
- hoverBorderColor: styles?.container?.disabledBorderColor ?? "borderColor",
127
- hoverColor: styles?.container?.disabledColor ?? "color",
128
- activeBg: styles?.container?.disabledBg ?? "bg",
129
- cursor: "not-allowed",
130
- opacity: styles?.container?.disabledOpacity ?? DEFAULT_DISABLED_OPACITY
131
- } : {}, [disabled, styles?.container]);
132
- };
133
- const useActiveProps = (isActive, styles) => {
134
- return useMemo(() => isActive ? filterUndefined({
135
- bg: styles?.container?.activeBg,
136
- borderBg: styles?.container?.activeBorderBg,
137
- color: styles?.container?.activeColor
138
- }) : {}, [isActive, styles?.container]);
139
- };
140
- const useAliasedProps = (isRound, h, styles, isElevated, icon, hasState, isFullWidth) => {
141
- return useMemo(() => filterUndefined({
142
- borderRadius: isRound ? h / 2 : styles?.container?.borderRadius || 0,
143
- boxShadow: isElevated ? DEFAULT_BOX_SHADOW : void 0,
144
- minW: icon ? 0 : void 0,
145
- pointerEvents: hasState ? "none" : void 0,
146
- w: isFullWidth ? "100%" : void 0
147
- }), [
173
+ isSplit,
174
+ disabled,
175
+ isActive,
148
176
  isRound,
149
- h,
150
- styles?.container?.borderRadius,
151
177
  isElevated,
152
- icon,
153
178
  hasState,
154
- isFullWidth
155
- ]);
156
- };
157
- const useButtonClassName = (className, isActive, variantProp) => {
158
- return useMemo(() => cs("vui-button", className, isActive && activeClassName, variantProp ? `vui-button-variant-${variantProp}` : void 0), [
179
+ isFullWidth,
159
180
  className,
160
- isActive,
161
181
  variantProp
162
182
  ]);
163
183
  };
164
- const useIconRenderer = (icon, iconLeft, iconRight, isLoading, spacingProps, children, text, isTruncated) => {
165
- return useMemo(() => ({
166
- main: isString(icon) ? /* @__PURE__ */ jsx(ButtonIcon, { name: icon }) : icon,
167
- left: isLoading ? /* @__PURE__ */ jsx(ButtonIcon, {
168
- mr: spacingProps.iconLeftMr,
169
- ...loadingIconProps
170
- }) : isString(iconLeft) ? /* @__PURE__ */ jsx(ButtonIcon, {
171
- mr: spacingProps.iconLeftMr,
172
- name: iconLeft
173
- }) : iconLeft,
174
- right: isString(iconRight) ? /* @__PURE__ */ jsx(ButtonIcon, {
175
- ml: !children && !text ? 0 : spacingProps.iconRightMl,
176
- name: iconRight
177
- }) : iconRight,
178
- content: children ?? (isReactText(text) ? /* @__PURE__ */ jsx(ButtonText, {
179
- isTruncated,
180
- text
181
- }) : text)
182
- }), [
183
- icon,
184
- iconLeft,
185
- iconRight,
186
- isLoading,
187
- spacingProps,
188
- children,
189
- text,
190
- isTruncated
191
- ]);
184
+ /**
185
+ * Unified icon renderer for all button icon positions.
186
+ * Converts string icon names to ButtonIcon components with appropriate margins.
187
+ *
188
+ * @param iconProp - Icon name string or React element
189
+ * @param position - Icon position: 'main' (center), 'left', or 'right'
190
+ * @param isLoading - Whether to show loading spinner (only applies to left position)
191
+ * @returns Rendered icon or null
192
+ */
193
+ const renderButtonIcon = (iconProp, position = "main", isLoading = false) => {
194
+ if (position === "left" && isLoading) return /* @__PURE__ */ jsx(ButtonIcon, {
195
+ ...loadingIconProps,
196
+ ml: 0
197
+ });
198
+ if (!iconProp) return null;
199
+ if (!isString(iconProp)) return iconProp;
200
+ switch (position) {
201
+ case "left": return /* @__PURE__ */ jsx(ButtonIcon, {
202
+ name: iconProp,
203
+ ml: 0
204
+ });
205
+ case "right": return /* @__PURE__ */ jsx(ButtonIcon, {
206
+ name: iconProp,
207
+ mr: 0
208
+ });
209
+ default: return /* @__PURE__ */ jsx(ButtonIcon, { name: iconProp });
210
+ }
211
+ };
212
+ /**
213
+ * Renders the button content (text or children).
214
+ * Wraps text strings in ButtonText component with optional truncation.
215
+ *
216
+ * @param children - React children content
217
+ * @param text - Text string to render
218
+ * @param isTruncated - Whether to enable text truncation
219
+ * @returns Rendered content
220
+ */
221
+ const renderContent = (children, text, isTruncated) => {
222
+ if (children) return children;
223
+ if (isReactText(text)) return /* @__PURE__ */ jsx(ButtonText, {
224
+ isTruncated,
225
+ text
226
+ });
227
+ return text;
192
228
  };
193
229
  /**
194
- * Triggers given actions on click. Supports multiple states, sizes and variants.
230
+ * Button Component
195
231
  *
196
- * Comes with additional props for extra behavior, like loading, disabled or elevated.
232
+ * A versatile button component that supports multiple states, sizes, variants, and behaviors.
233
+ * Part of the VUI design system with built-in accessibility features.
197
234
  *
198
- * Exposes some props to the children via context.
199
235
  */
200
236
  const Button = vui((props, ref) => {
201
237
  const mergedProps = {
@@ -211,63 +247,83 @@ const Button = vui((props, ref) => {
211
247
  const stateData = useMemo(() => states[state] ?? {}, [states, state]);
212
248
  const { iconProps: stateIconProps, ...stateProps } = stateData;
213
249
  const iconRight = useMemo(() => isDropDown ? "falAngleDown" : iconRightProp, [isDropDown, iconRightProp]);
214
- const variant = useMemo(() => filterUndefined({ variant: stateData?.variant ?? variantProp }), [stateData?.variant, variantProp]);
215
- const { styles, borderWidth, h, spaceX, buttonStyles } = useButtonStyles(mergedProps, variant);
216
- const spacingProps = useSpacingProps(borderWidthProp, borderWidth, spaceX, icon, iconLeft, iconRight, isLoading ?? false, isSplit ?? false);
217
- const disabledProps = useDisabledProps(disabled ?? false, styles);
218
- const isActiveProps = useActiveProps(isActive ?? false, styles);
219
- const aliasedProps = useAliasedProps(isRound ?? false, h, styles, isElevated ?? false, icon, hasState, isFullWidth ?? false);
220
- const buttonClassName = useButtonClassName(className, isActive ?? false, variantProp);
221
- const IconRenderer = useIconRenderer(icon, iconLeft, iconRight, isLoading ?? false, spacingProps, children, text, isTruncated ?? false);
250
+ const variantForTheme = useMemo(() => filterUndefined({ variant: stateData?.variant ?? variantProp }), [stateData?.variant, variantProp]);
251
+ const computed = useButtonComputed(mergedProps, variantForTheme, borderWidthProp, icon, iconLeft, iconRight, isLoading ?? false, isSplit ?? false, disabled ?? false, isActive ?? false, isRound ?? false, isElevated ?? false, hasState, isFullWidth ?? false, className, variantProp);
252
+ const hasTextContent = Boolean(children || text);
222
253
  const contextValue = useMemo(() => filterUndefined({
223
254
  disabled,
224
255
  size,
225
- variant: variant.variant
256
+ variant: variantForTheme.variant,
257
+ isDropDown,
258
+ hasTextContent
226
259
  }), [
227
260
  disabled,
228
261
  size,
229
- variant.variant
262
+ variantForTheme.variant,
263
+ isDropDown,
264
+ hasTextContent
230
265
  ]);
231
266
  const Content = useMemo(() => hasState ? ContentWrapper : Fragment, [hasState]);
232
- const ariaLabel = useMemo(() => title || "", [title]);
233
267
  const handleStateClick = useCallback((e) => {
234
268
  e.stopPropagation();
235
269
  }, []);
270
+ const finalVariant = stateData?.variant ?? variantProp;
271
+ const borderWidth = computed.spacing.borderWidth;
272
+ const plValue = computed.spacing.pl;
273
+ const prValue = computed.spacing.pr;
274
+ const hValue = computed.h;
275
+ const disabledPropsWithVars = useMemo(() => {
276
+ if (!computed.disabledProps || Object.keys(computed.disabledProps).length === 0) return {};
277
+ const { bg, borderColor, color, opacity, ...otherDisabledProps } = computed.disabledProps;
278
+ return {
279
+ ...otherDisabledProps,
280
+ ...bg !== void 0 && { bg: `var(--vui-button-disabled-bg, ${bg})` },
281
+ ...borderColor !== void 0 && { borderColor: `var(--vui-button-disabled-border-color, ${borderColor})` },
282
+ ...color !== void 0 && { color: `var(--vui-button-disabled-color, ${color})` },
283
+ ...opacity !== void 0 && { opacity: `var(--vui-button-disabled-opacity, ${opacity})` }
284
+ };
285
+ }, [computed.disabledProps]);
286
+ const finalProps = useMemo(() => ({
287
+ borderWidth: `var(--vui-button-border-width, ${borderWidth}px)`,
288
+ className: computed.buttonClassName,
289
+ disabled,
290
+ focusRing: FOCUS_RING_SIZE,
291
+ h: `var(--vui-button-h, ${hValue}px)`,
292
+ pl: plValue,
293
+ pr: prValue,
294
+ ref,
295
+ title: title || "",
296
+ type: BUTTON_TYPE,
297
+ variant: finalVariant,
298
+ ...computed.buttonStyles,
299
+ ...computed.activeProps,
300
+ ...disabledPropsWithVars,
301
+ ...computed.aliasedProps,
302
+ ...rest
303
+ }), [
304
+ borderWidth,
305
+ plValue,
306
+ prValue,
307
+ computed.buttonClassName,
308
+ computed.buttonStyles,
309
+ computed.activeProps,
310
+ disabledPropsWithVars,
311
+ computed.aliasedProps,
312
+ hValue,
313
+ disabled,
314
+ ref,
315
+ title,
316
+ finalVariant,
317
+ rest
318
+ ]);
319
+ const mainIcon = renderButtonIcon(icon, "main");
320
+ const leftIcon = renderButtonIcon(iconLeft, "left", isLoading ?? false);
321
+ const rightIcon = renderButtonIcon(iconRight, "right");
322
+ const contentElement = renderContent(children, text, isTruncated ?? false);
236
323
  return /* @__PURE__ */ jsx(ButtonProvider, {
237
324
  value: contextValue,
238
325
  children: /* @__PURE__ */ jsxs(ButtonBase, {
239
- ...useMemo(() => ({
240
- borderWidth: spacingProps.border,
241
- className: buttonClassName,
242
- disabled,
243
- focusRing: FOCUS_RING_SIZE,
244
- h,
245
- pl: spacingProps.pl,
246
- pr: spacingProps.pr,
247
- ref,
248
- title: title || ariaLabel,
249
- type: BUTTON_TYPE,
250
- variant,
251
- ...buttonStyles,
252
- ...isActiveProps,
253
- ...disabledProps,
254
- ...aliasedProps,
255
- ...rest
256
- }), [
257
- spacingProps,
258
- buttonClassName,
259
- disabled,
260
- h,
261
- ref,
262
- title,
263
- ariaLabel,
264
- variant,
265
- buttonStyles,
266
- isActiveProps,
267
- disabledProps,
268
- aliasedProps,
269
- rest
270
- ]),
326
+ ...finalProps,
271
327
  children: [hasState && /* @__PURE__ */ jsx(StateWrapper, {
272
328
  onClick: handleStateClick,
273
329
  ...stateProps,
@@ -275,11 +331,15 @@ const Button = vui((props, ref) => {
275
331
  m: "auto",
276
332
  ...stateIconProps
277
333
  })
278
- }), /* @__PURE__ */ jsxs(Content, { children: [IconRenderer.main, !icon && /* @__PURE__ */ jsxs(Fragment$1, { children: [
279
- IconRenderer.left,
280
- IconRenderer.content,
281
- IconRenderer.right
282
- ] })] })]
334
+ }), /* @__PURE__ */ jsxs(Content, { children: [
335
+ mainIcon,
336
+ !icon && /* @__PURE__ */ jsxs(Fragment$1, { children: [
337
+ leftIcon,
338
+ contentElement,
339
+ rightIcon
340
+ ] }),
341
+ icon && isDropDown && rightIcon
342
+ ] })]
283
343
  })
284
344
  });
285
345
  });
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","names":[],"sources":["../../src/button/button.tsx"],"sourcesContent":["import { Fragment, useMemo, useCallback } from 'react'\n\nimport { useButtonGroupContext } from '../buttonGroup/context'\nimport type { VuiComponent } from '../core'\nimport { styled, useStyleConfig, vui } from '../core'\nimport { activeClassName, cs, filterUndefined, isReactText, isString } from '../utils'\nimport type { ButtonProps } from './button.types'\nimport ButtonIcon from './buttonIcon'\nimport ButtonText from './buttonText'\nimport { buttonStateMapping, loadingIconProps } from './consts'\nimport { ButtonProvider } from './context'\n\n// Constants\nconst DEFAULT_BORDER_WIDTH = 1\nconst DEFAULT_BOX_SHADOW = 'md'\nconst DEFAULT_DISABLED_OPACITY = 0.5\nconst BUTTON_TYPE = 'button'\nconst FOCUS_RING_SIZE = 3\n\n// Memoized styled components\nconst ContentWrapper = styled.span`\n visibility: hidden;\n`\n\nconst StateWrapper = styled.span`\n bottom: 0;\n display: flex;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n`\n\nexport const ButtonBase = styled.buttonBox<ButtonProps>`\n align-items: center;\n display: inline-flex;\n flex-shrink: 0;\n font-weight: medium;\n justify-content: center;\n line-height: normal;\n overflow: hidden;\n position: relative;\n transition-duration: fast;\n user-select: none;\n width: fit-content;\n\n &.vui-button-variant-menuLight.vui-button-variant-menuLight.vui-active:hover {\n background-color: #d3eef8;\n color: #003591;\n border-color-left: #003591;\n }\n &.vui-button-variant-menuDark.vui-button-variant-menuDark.vui-active:hover {\n background-color: #003591;\n color: #91ffba;\n border-color-left: #91ffba;\n }\n &.vui-button-variant-menuDark.vui-button-variant-menuDark:focus,\n &.vui-button-variant-menuDark.vui-button-variant-menuDark:focus-visible,\n &.vui-button-variant-menuLight.vui-button-variant-menuLight:focus,\n &.vui-button-variant-menuLight.vui-button-variant-menuLight:focus-visible {\n box-shadow: none;\n outline: none;\n border-bottom: 0 solid transparent;\n }\n &.vui-button-variant-menuDark.vui-button-variant-menuDark.focusAsBorder:focus,\n &.vui-button-variant-menuDark.vui-button-variant-menuDark.focusAsBorder:focus-visible,\n &.vui-button-variant-menuLight.vui-button-variant-menuLight.focusAsBorder:focus,\n &.vui-button-variant-menuLight.vui-button-variant-menuLight.focusAsBorder:focus-visible {\n transition: border-bottom 0s linear;\n border-bottom: 3px solid #d3eef8;\n }\n`\n\nconst useButtonStyles = (mergedProps: any, variant: any) => {\n const styles = useStyleConfig('Button', { ...mergedProps, ...variant }) || {}\n\n return useMemo(() => {\n const container = styles.container || {}\n const { borderWidth = DEFAULT_BORDER_WIDTH, h = 32, spaceX = 0, ...buttonStyles } = container\n return { styles, borderWidth, h, spaceX, buttonStyles }\n }, [mergedProps, variant])\n}\n\n// Utility function to extract numeric value from responsive or themed props\nconst extractNumericValue = (value: any): number => {\n if (typeof value === 'number') return value\n if (typeof value === 'string') {\n const parsed = parseInt(value, 10)\n return isNaN(parsed) ? DEFAULT_BORDER_WIDTH : parsed\n }\n return DEFAULT_BORDER_WIDTH\n}\n\nconst useSpacingProps = (\n borderWidthProp: any,\n borderWidth: number | undefined,\n spaceX: number,\n icon: any,\n iconLeft: any,\n iconRight: any,\n isLoading: boolean,\n isSplit: boolean,\n) => {\n return useMemo(() => {\n const border = extractNumericValue(borderWidthProp) || borderWidth || DEFAULT_BORDER_WIDTH\n const spaceXItem = spaceX - border\n const spaceXText = spaceX * 2 - border\n\n return {\n border,\n iconLeftMr: `${spaceXItem + 1}px`,\n iconRightMl: isSplit ? 'auto' : `${spaceXItem + 1}px`,\n pl: `${icon || iconLeft || isLoading ? spaceXItem : spaceXText}px`,\n pr: `${icon || iconRight ? spaceXItem : spaceXText}px`,\n }\n }, [borderWidthProp, borderWidth, spaceX, icon, iconLeft, iconRight, isLoading, isSplit])\n}\n\nconst useDisabledProps = (disabled: boolean, styles: any) => {\n return useMemo(\n () =>\n disabled\n ? {\n 'aria-disabled': disabled,\n disabledBg: styles?.container?.disabledBg ?? 'bg',\n disabledBorderColor: styles?.container?.disabledBorderColor ?? 'borderColor',\n disabledColor: styles?.container?.disabledColor ?? 'color',\n hoverBg: styles?.container?.disabledBg ?? 'bg',\n hoverBorderColor: styles?.container?.disabledBorderColor ?? 'borderColor',\n hoverColor: styles?.container?.disabledColor ?? 'color',\n activeBg: styles?.container?.disabledBg ?? 'bg',\n cursor: 'not-allowed',\n opacity: styles?.container?.disabledOpacity ?? DEFAULT_DISABLED_OPACITY,\n }\n : {},\n [disabled, styles?.container],\n )\n}\n\nconst useActiveProps = (isActive: boolean, styles: any) => {\n return useMemo(\n () =>\n isActive\n ? filterUndefined({\n bg: styles?.container?.activeBg,\n borderBg: styles?.container?.activeBorderBg,\n color: styles?.container?.activeColor,\n })\n : {},\n [isActive, styles?.container],\n )\n}\n\nconst useAliasedProps = (\n isRound: boolean,\n h: number,\n styles: any,\n isElevated: boolean,\n icon: any,\n hasState: boolean,\n isFullWidth: boolean,\n) => {\n return useMemo(\n () =>\n filterUndefined({\n borderRadius: isRound ? h / 2 : styles?.container?.borderRadius || 0,\n boxShadow: isElevated ? DEFAULT_BOX_SHADOW : undefined,\n minW: icon ? 0 : undefined,\n pointerEvents: hasState ? 'none' : undefined,\n w: isFullWidth ? '100%' : undefined,\n }),\n [isRound, h, styles?.container?.borderRadius, isElevated, icon, hasState, isFullWidth],\n )\n}\n\nconst useButtonClassName = (className: string | undefined, isActive: boolean, variantProp: string | undefined) => {\n return useMemo(\n () =>\n cs(\n 'vui-button',\n className,\n isActive && activeClassName,\n variantProp ? `vui-button-variant-${variantProp}` : undefined,\n ),\n [className, isActive, variantProp],\n )\n}\n\nconst useIconRenderer = (\n icon: any,\n iconLeft: any,\n iconRight: any,\n isLoading: boolean,\n spacingProps: any,\n children: any,\n text: any,\n isTruncated: boolean,\n) => {\n return useMemo(\n () => ({\n main: isString(icon) ? <ButtonIcon name={icon} /> : icon,\n left: isLoading ? (\n <ButtonIcon mr={spacingProps.iconLeftMr} {...loadingIconProps} />\n ) : isString(iconLeft) ? (\n <ButtonIcon mr={spacingProps.iconLeftMr} name={iconLeft} />\n ) : (\n iconLeft\n ),\n right: isString(iconRight) ? (\n <ButtonIcon ml={!children && !text ? 0 : spacingProps.iconRightMl} name={iconRight} />\n ) : (\n iconRight\n ),\n content: children ?? (isReactText(text) ? <ButtonText {...{ isTruncated, text }} /> : text),\n }),\n [icon, iconLeft, iconRight, isLoading, spacingProps, children, text, isTruncated],\n )\n}\n\n/**\n * Triggers given actions on click. Supports multiple states, sizes and variants.\n *\n * Comes with additional props for extra behavior, like loading, disabled or elevated.\n *\n * Exposes some props to the children via context.\n */\nexport const Button = vui<'button', ButtonProps>((props, ref) => {\n const buttonGroupProps = useButtonGroupContext()\n const mergedProps = { ...buttonGroupProps, ...props }\n const {\n borderWidth: borderWidthProp,\n children,\n className,\n colorScheme,\n disabled,\n dropDownIcon = 'falAngleDown',\n icon,\n iconLeft,\n iconRight: iconRightProp,\n isActive,\n isDropDown,\n isElevated,\n isFullWidth,\n isLoading,\n isRound,\n isSplit,\n isTruncated,\n size,\n state = '',\n stateMapping,\n text,\n title,\n variant: variantProp,\n ...rest\n } = mergedProps\n\n // Memoized calculations\n const hasState = useMemo(() => Boolean(state) && state !== 'idle', [state])\n const states = useMemo(() => ({ ...buttonStateMapping, ...stateMapping }), [stateMapping])\n const stateData = useMemo(() => states[state] ?? {}, [states, state])\n const { iconProps: stateIconProps, ...stateProps } = stateData\n\n const iconRight = useMemo(() => (isDropDown ? 'falAngleDown' : iconRightProp), [isDropDown, iconRightProp])\n\n const variant = useMemo(\n () =>\n filterUndefined({\n variant: stateData?.variant ?? variantProp,\n }),\n [stateData?.variant, variantProp],\n )\n\n // Custom hooks for optimization\n const { styles, borderWidth, h, spaceX, buttonStyles } = useButtonStyles(mergedProps, variant)\n const spacingProps = useSpacingProps(\n borderWidthProp,\n borderWidth,\n spaceX,\n icon,\n iconLeft,\n iconRight,\n isLoading ?? false,\n isSplit ?? false,\n )\n const disabledProps = useDisabledProps(disabled ?? false, styles)\n const isActiveProps = useActiveProps(isActive ?? false, styles)\n const aliasedProps = useAliasedProps(\n isRound ?? false,\n h,\n styles,\n isElevated ?? false,\n icon,\n hasState,\n isFullWidth ?? false,\n )\n const buttonClassName = useButtonClassName(className, isActive ?? false, variantProp)\n const IconRenderer = useIconRenderer(\n icon,\n iconLeft,\n iconRight,\n isLoading ?? false,\n spacingProps,\n children,\n text,\n isTruncated ?? false,\n )\n\n // Memoized context value\n const contextValue = useMemo(\n () => filterUndefined({ disabled, size, variant: variant.variant }),\n [disabled, size, variant.variant],\n )\n\n // Memoized content wrapper\n const Content = useMemo(() => (hasState ? ContentWrapper : Fragment), [hasState])\n\n // Memoized aria label\n const ariaLabel = useMemo(() => title || '', [title])\n\n // Optimized event handler\n const handleStateClick = useCallback((e: { stopPropagation: () => void }) => {\n e.stopPropagation()\n }, [])\n\n // Memoized final props\n const finalProps = useMemo(\n () => ({\n borderWidth: spacingProps.border,\n className: buttonClassName,\n disabled,\n focusRing: FOCUS_RING_SIZE,\n h,\n pl: spacingProps.pl,\n pr: spacingProps.pr,\n ref,\n title: title || ariaLabel,\n type: BUTTON_TYPE,\n variant,\n ...buttonStyles,\n ...isActiveProps,\n ...disabledProps,\n ...aliasedProps,\n ...rest,\n }),\n [\n spacingProps,\n buttonClassName,\n disabled,\n h,\n ref,\n title,\n ariaLabel,\n variant,\n buttonStyles,\n isActiveProps,\n disabledProps,\n aliasedProps,\n rest,\n ],\n )\n\n return (\n <ButtonProvider value={contextValue}>\n <ButtonBase {...finalProps}>\n {hasState && (\n <StateWrapper onClick={handleStateClick} {...stateProps}>\n <ButtonIcon m=\"auto\" {...stateIconProps} />\n </StateWrapper>\n )}\n\n <Content>\n {IconRenderer.main}\n {!icon && (\n <>\n {IconRenderer.left}\n {IconRenderer.content}\n {IconRenderer.right}\n </>\n )}\n </Content>\n </ButtonBase>\n </ButtonProvider>\n )\n}) as VuiComponent<'button', ButtonProps> & {\n Icon: typeof ButtonIcon\n Text: typeof ButtonText\n}\n\nButton.Icon = ButtonIcon\nButton.Text = ButtonText\nButton.displayName = 'Button'\n\nexport default Button\n"],"mappings":"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"}
1
+ {"version":3,"file":"button.js","names":[],"sources":["../../src/button/button.tsx"],"sourcesContent":["import { Fragment, useMemo, useCallback } from 'react'\n\nimport { useButtonGroupContext } from '../buttonGroup/context'\nimport type { VuiComponent } from '../core'\nimport { styled, useStyleConfig, vui } from '../core'\nimport type { SystemProps } from '../system'\nimport { activeClassName, cs, filterUndefined, isReactText, isString } from '../utils'\nimport type { ButtonProps } from './button.types'\nimport ButtonIcon from './buttonIcon'\nimport ButtonText from './buttonText'\nimport { buttonStateMapping, loadingIconProps } from './consts'\nimport { ButtonProvider } from './context'\n\n// Types for internal use\ninterface ButtonStyleContainer {\n borderWidth?: number\n h?: number\n spaceX?: number\n disabledBg?: string\n disabledBorderColor?: string\n disabledColor?: string\n disabledOpacity?: number\n activeBg?: string\n activeBorderBg?: string\n activeColor?: string\n borderRadius?: number\n [key: string]: any\n}\n\ninterface ButtonComputedStyles {\n borderColor: string\n activeBorderColor: string\n activeColor: string\n hoverBorderColor: string\n hoverColor: string\n [key: string]: any\n}\n\ninterface SpacingProps {\n borderWidth: number\n pl: string\n pr: string\n}\n\ninterface ComputedResult {\n styles: { container?: ButtonStyleContainer; [key: string]: any }\n borderWidth: number\n h: number\n spaceX: number\n buttonStyles: ButtonComputedStyles\n spacing: SpacingProps\n disabledProps: SystemProps\n activeProps: SystemProps\n aliasedProps: SystemProps\n buttonClassName: string\n}\n\n// Constants - TODO: Move to theme tokens for better maintainability\nconst DEFAULT_BORDER_WIDTH = 1\nconst DEFAULT_BOX_SHADOW = 'md'\nconst DEFAULT_DISABLED_OPACITY = 0.5\nconst BUTTON_TYPE = 'button'\nconst FOCUS_RING_SIZE = 3\nconst MENU_FOCUS_BORDER_WIDTH = 3 // Width of focus border for menu variants\n\n// Memoized styled components\nconst ContentWrapper = styled.span`\n visibility: hidden;\n`\n\n// StateWrapper overlays the button content during state transitions (loading, success, error)\n// Positioned absolutely to avoid layout shifts and uses pointer-events: none on parent\nconst StateWrapper = styled.span`\n bottom: 0;\n display: flex;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n`\n\nexport const ButtonBase = styled.buttonBox<ButtonProps>`\n align-items: center;\n display: inline-flex;\n flex-shrink: 0;\n font-weight: var(--vui-button-font-weight, medium);\n justify-content: center;\n line-height: normal;\n overflow: hidden;\n position: relative;\n transition-duration: var(--vui-button-transition-duration, fast);\n user-select: none;\n width: fit-content;\n\n /* Menu variant focus states - default removes focus ring\n * Used when focusAsBorder class is NOT present (standard focus ring behavior)\n */\n &.vui-button-variant-menuDark:not(.focusAsBorder):focus,\n &.vui-button-variant-menuDark:not(.focusAsBorder):focus-visible,\n &.vui-button-variant-menuLight:not(.focusAsBorder):focus,\n &.vui-button-variant-menuLight:not(.focusAsBorder):focus-visible {\n box-shadow: none;\n outline: none;\n }\n\n /* Menu variant focus states - with border indicator for accessibility\n * Used when focusAsBorder class IS present (alternative focus indication via border)\n * Color: theme menuLight/menuDark.focusBorderBottomColor\n */\n &.vui-button-variant-menuDark.focusAsBorder:focus,\n &.vui-button-variant-menuDark.focusAsBorder:focus-visible,\n &.vui-button-variant-menuLight.focusAsBorder:focus,\n &.vui-button-variant-menuLight.focusAsBorder:focus-visible {\n transition: border-bottom 0s linear;\n border-bottom: ${MENU_FOCUS_BORDER_WIDTH}px solid var(--color-sky-blue-90); /* theme: focusBorderBottomColor */\n }\n`\n\n/**\n * Extracts a numeric value from various input types (number, string, etc.)\n * @param value - Value to extract number from\n * @param fallback - Default value if extraction fails\n * @returns Extracted number or fallback\n */\nconst extractNumericValue = (value: any, fallback: number = DEFAULT_BORDER_WIDTH): number => {\n if (typeof value === 'number') return value\n if (typeof value === 'string') {\n const parsed = parseInt(value, 10)\n if (!isNaN(parsed)) return parsed\n }\n return fallback\n}\n\n/**\n * Consolidated hook for computing all button styles and props.\n * Combines theme resolution, spacing, state-based styles, and className generation.\n * Reduces memoization overhead by computing all derived values in a single pass.\n *\n * @param mergedProps - Combined props from ButtonGroup context and component props\n * @param variant - Resolved variant object with theme overrides\n * @param borderWidthProp - Optional border width override from props\n * @param icon - Main icon (replaces all content when present)\n * @param iconLeft - Left icon slot\n * @param iconRight - Right icon slot\n * @param isLoading - Loading state flag\n * @param isSplit - Deprecated: Split layout mode\n * @param disabled - Disabled state flag\n * @param isActive - Active state flag\n * @param isRound - Round/pill-shaped borders flag\n * @param isElevated - Box shadow elevation flag\n * @param hasState - Whether button has a state overlay\n * @param isFullWidth - Full width layout flag\n * @param className - Additional CSS classes\n * @param variantProp - Variant name for class generation\n * @returns Computed styles, spacing, props, and className\n */\nconst useButtonComputed = (\n mergedProps: ButtonProps & SystemProps,\n variant: { variant?: string },\n borderWidthProp: any, // Keep as any due to complex BorderWidthProp type from xstyled\n icon: any,\n iconLeft: any,\n iconRight: any,\n isLoading: boolean,\n isSplit: boolean,\n disabled: boolean,\n isActive: boolean,\n isRound: boolean,\n isElevated: boolean,\n hasState: boolean,\n isFullWidth: boolean,\n className: string | undefined,\n variantProp: string | undefined,\n): ComputedResult => {\n const styles = useStyleConfig('Button', { ...mergedProps, ...variant }) || {}\n\n return useMemo(() => {\n // Theme and style resolution\n const container = styles.container || {}\n const { borderWidth = DEFAULT_BORDER_WIDTH, h = 32, spaceX = 0, ...buttonStyles } = container\n\n // Normalize button styles with fallbacks for text colors only\n const normalizedStyles = {\n ...buttonStyles,\n activeColor: buttonStyles.activeColor ?? buttonStyles.color,\n hoverColor: buttonStyles.hoverColor ?? buttonStyles.color,\n }\n\n // Spacing calculations\n // borderWidth value used for spacing calculations (affects padding adjustments)\n const border = extractNumericValue(borderWidthProp) || borderWidth || DEFAULT_BORDER_WIDTH\n const spaceXItem = spaceX - border\n const spaceXText = spaceX * 2 - border\n\n const spacing = {\n borderWidth: border, // Store borderWidth for use in finalProps\n pl: `var(--vui-button-px-start, ${icon || iconLeft || isLoading ? spaceXItem : spaceXText}px)`,\n pr: `var(--vui-button-px-end, ${icon || iconRight ? spaceXItem : spaceXText}px)`,\n }\n\n // Disabled state props with CSS variable overrides\n // Theme tokens (like landGreen.70) are resolved by styled system, then wrapped in CSS variables\n // This allows both theme resolution and CSS variable overrides to work together\n const disabledProps = disabled\n ? filterUndefined({\n 'aria-disabled': disabled,\n bg: container?.disabledBg ?? undefined,\n borderColor: container?.disabledBorderColor ?? undefined,\n color: container?.disabledColor ?? undefined,\n hoverBg: container?.disabledBg ?? normalizedStyles.bg,\n hoverBorderColor: container?.disabledBorderColor ?? normalizedStyles.borderColor,\n hoverColor: container?.disabledColor ?? normalizedStyles.color,\n activeBg: container?.disabledBg ?? normalizedStyles.bg,\n cursor: 'not-allowed',\n opacity: container?.disabledOpacity ?? DEFAULT_DISABLED_OPACITY,\n })\n : {}\n\n // Active state props - prevents hover effects when button is active\n // Active state (also called \"loading\" in VUI Next) should not be interactive\n const activeProps = isActive\n ? filterUndefined({\n bg: container?.activeBg,\n borderColor: container?.activeBorderColor,\n color: container?.activeColor,\n hoverBg: container?.activeBg,\n hoverBorderColor: container?.activeBorderColor,\n hoverColor: container?.activeColor,\n cursor: 'default',\n })\n : {}\n\n // Aliased props (computed properties)\n const aliasedProps = filterUndefined({\n borderRadius: isRound ? h / 2 : `var(--vui-button-border-radius, ${container?.borderRadius || 0}px)`,\n boxShadow: isElevated ? `var(--vui-button-box-shadow, ${DEFAULT_BOX_SHADOW})` : undefined,\n minW: icon ? 0 : `var(--vui-button-min-w, auto)`,\n pointerEvents: hasState ? 'none' : undefined,\n w: isFullWidth ? '100%' : undefined,\n })\n\n // Class name generation\n const buttonClassName = cs(\n 'vui-button',\n className,\n isActive && activeClassName,\n variantProp ? `vui-button-variant-${variantProp}` : undefined,\n )\n\n return {\n styles,\n borderWidth,\n h,\n spaceX,\n buttonStyles: normalizedStyles,\n spacing,\n disabledProps,\n activeProps,\n aliasedProps,\n buttonClassName,\n }\n }, [\n styles,\n mergedProps,\n variant,\n borderWidthProp,\n icon,\n iconLeft,\n iconRight,\n isLoading,\n isSplit,\n disabled,\n isActive,\n isRound,\n isElevated,\n hasState,\n isFullWidth,\n className,\n variantProp,\n ])\n}\n\n/**\n * Unified icon renderer for all button icon positions.\n * Converts string icon names to ButtonIcon components with appropriate margins.\n *\n * @param iconProp - Icon name string or React element\n * @param position - Icon position: 'main' (center), 'left', or 'right'\n * @param isLoading - Whether to show loading spinner (only applies to left position)\n * @returns Rendered icon or null\n */\nconst renderButtonIcon = (\n iconProp: any,\n position: 'main' | 'left' | 'right' = 'main',\n isLoading: boolean = false,\n): React.ReactElement | null => {\n // Handle loading state for left icon\n if (position === 'left' && isLoading) {\n return <ButtonIcon {...loadingIconProps} ml={0} />\n }\n\n if (!iconProp) return null\n\n // Return existing React elements as-is\n if (!isString(iconProp)) return iconProp\n\n // Apply position-specific margins for string icon names\n switch (position) {\n case 'left':\n return <ButtonIcon name={iconProp} ml={0} />\n case 'right':\n return <ButtonIcon name={iconProp} mr={0} />\n case 'main':\n default:\n return <ButtonIcon name={iconProp} />\n }\n}\n\n/**\n * Renders the button content (text or children).\n * Wraps text strings in ButtonText component with optional truncation.\n *\n * @param children - React children content\n * @param text - Text string to render\n * @param isTruncated - Whether to enable text truncation\n * @returns Rendered content\n */\nconst renderContent = (children: any, text: any, isTruncated: boolean): React.ReactNode => {\n if (children) return children\n if (isReactText(text)) return <ButtonText isTruncated={isTruncated} text={text} />\n return text\n}\n\n/**\n * Button Component\n *\n * A versatile button component that supports multiple states, sizes, variants, and behaviors.\n * Part of the VUI design system with built-in accessibility features.\n *\n */\nexport const Button = vui<'button', ButtonProps>((props, ref) => {\n const buttonGroupProps = useButtonGroupContext()\n const mergedProps = { ...buttonGroupProps, ...props }\n const {\n borderWidth: borderWidthProp,\n children,\n className,\n colorScheme,\n disabled,\n dropDownIcon = 'falAngleDown',\n icon,\n iconLeft,\n iconRight: iconRightProp,\n isActive,\n isDropDown,\n isElevated,\n isFullWidth,\n isLoading,\n isRound,\n isSplit,\n isTruncated,\n size,\n state = '',\n stateMapping,\n text,\n title,\n variant: variantProp,\n ...rest\n } = mergedProps\n\n // Memoized calculations\n const hasState = useMemo(() => Boolean(state) && state !== 'idle', [state])\n const states = useMemo(() => ({ ...buttonStateMapping, ...stateMapping }), [stateMapping])\n const stateData = useMemo(() => states[state] ?? {}, [states, state])\n const { iconProps: stateIconProps, ...stateProps } = stateData\n\n const iconRight = useMemo(() => (isDropDown ? 'falAngleDown' : iconRightProp), [isDropDown, iconRightProp])\n\n const variantForTheme = useMemo(\n () =>\n filterUndefined({\n variant: stateData?.variant ?? variantProp,\n }),\n [stateData?.variant, variantProp],\n )\n\n // Consolidated computation of all button styles and props\n const computed = useButtonComputed(\n mergedProps,\n variantForTheme,\n borderWidthProp,\n icon,\n iconLeft,\n iconRight,\n isLoading ?? false,\n isSplit ?? false,\n disabled ?? false,\n isActive ?? false,\n isRound ?? false,\n isElevated ?? false,\n hasState,\n isFullWidth ?? false,\n className,\n variantProp,\n )\n\n // Memoized context value\n const hasTextContent = Boolean(children || text)\n const contextValue = useMemo(\n () => filterUndefined({ disabled, size, variant: variantForTheme.variant, isDropDown, hasTextContent }),\n [disabled, size, variantForTheme.variant, isDropDown, hasTextContent],\n )\n\n // Memoized content wrapper\n const Content = useMemo(() => (hasState ? ContentWrapper : Fragment), [hasState])\n\n // Optimized event handler - prevents event bubbling during state overlay interactions\n // This ensures clicks on the state overlay (loading/success/error) don't trigger button actions\n const handleStateClick = useCallback((e: { stopPropagation: () => void }) => {\n e.stopPropagation()\n }, [])\n\n // Memoized final props - using primitives to avoid object reference changes\n const finalVariant = (stateData?.variant ?? variantProp) as string | undefined\n\n // Extract primitive values for stable dependencies\n const borderWidth = computed.spacing.borderWidth\n const plValue = computed.spacing.pl\n const prValue = computed.spacing.pr\n const hValue = computed.h\n\n // Wrap disabled props with CSS variables for customization\n const disabledPropsWithVars = useMemo(() => {\n if (!computed.disabledProps || Object.keys(computed.disabledProps).length === 0) return {}\n\n const { bg, borderColor, color, opacity, ...otherDisabledProps } = computed.disabledProps\n\n return {\n ...otherDisabledProps,\n ...(bg !== undefined && { bg: `var(--vui-button-disabled-bg, ${bg})` }),\n ...(borderColor !== undefined && { borderColor: `var(--vui-button-disabled-border-color, ${borderColor})` }),\n ...(color !== undefined && { color: `var(--vui-button-disabled-color, ${color})` }),\n ...(opacity !== undefined && { opacity: `var(--vui-button-disabled-opacity, ${opacity})` }),\n }\n }, [computed.disabledProps])\n\n const finalProps = useMemo(\n () => ({\n borderWidth: `var(--vui-button-border-width, ${borderWidth}px)`,\n className: computed.buttonClassName,\n disabled,\n focusRing: FOCUS_RING_SIZE,\n h: `var(--vui-button-h, ${hValue}px)`,\n pl: plValue,\n pr: prValue,\n ref,\n title: title || '',\n type: BUTTON_TYPE as 'button',\n variant: finalVariant,\n ...computed.buttonStyles,\n ...computed.activeProps,\n ...disabledPropsWithVars,\n ...computed.aliasedProps,\n ...rest,\n }),\n [\n borderWidth,\n plValue,\n prValue,\n computed.buttonClassName,\n computed.buttonStyles,\n computed.activeProps,\n disabledPropsWithVars,\n computed.aliasedProps,\n hValue,\n disabled,\n ref,\n title,\n finalVariant,\n rest,\n ],\n )\n\n // Render icon elements (not memoized to avoid stale references)\n const mainIcon = renderButtonIcon(icon, 'main')\n const leftIcon = renderButtonIcon(iconLeft, 'left', isLoading ?? false)\n const rightIcon = renderButtonIcon(iconRight, 'right')\n const contentElement = renderContent(children, text, isTruncated ?? false)\n\n return (\n <ButtonProvider value={contextValue}>\n <ButtonBase {...finalProps}>\n {hasState && (\n <StateWrapper onClick={handleStateClick} {...stateProps}>\n <ButtonIcon m=\"auto\" {...stateIconProps} />\n </StateWrapper>\n )}\n\n <Content>\n {mainIcon}\n {!icon && (\n <>\n {leftIcon}\n {contentElement}\n {rightIcon}\n </>\n )}\n {icon && isDropDown && rightIcon}\n </Content>\n </ButtonBase>\n </ButtonProvider>\n )\n}) as VuiComponent<'button', ButtonProps> & {\n Icon: typeof ButtonIcon\n Text: typeof ButtonText\n}\n\nButton.Icon = ButtonIcon\nButton.Text = ButtonText\nButton.displayName = 'Button'\n\nexport default Button\n"],"mappings":"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"}
@@ -9,14 +9,23 @@ import { jsx } from "react/jsx-runtime";
9
9
  const isConceptualIcon = (name) => name?.startsWith("con");
10
10
  /** Displays an icon within the Button. */
11
11
  const ButtonIcon = vui((props, ref) => {
12
- const { className, name, ...rest } = props;
13
- const styles = useStyleConfig("Button", useButtonContext());
12
+ const { className, name, mr, ml, ...rest } = props;
13
+ const buttonContext = useButtonContext();
14
+ const styles = useStyleConfig("Button", buttonContext);
14
15
  const verifiedName = isConceptualIcon(name) ? void 0 : name;
16
+ const isIconOnly = !buttonContext?.hasTextContent;
17
+ const defaultMarginRight = isIconOnly ? 0 : styles.icon?.mr || 0;
18
+ const defaultMarginLeft = isIconOnly ? 0 : styles.icon?.ml || 0;
19
+ const marginRight = mr !== void 0 ? mr : `var(--vui-button-icon-mr, ${defaultMarginRight})`;
20
+ const marginLeft = ml !== void 0 ? ml : `var(--vui-button-icon-ml, ${defaultMarginLeft})`;
15
21
  return /* @__PURE__ */ jsx(Icon, {
16
22
  className: cs("vui-buttonIcon", className),
23
+ ...styles.icon,
24
+ fontSize: `var(--vui-button-icon-size, ${styles.icon?.fontSize || "1rem"})`,
25
+ ml: marginLeft,
26
+ mr: marginRight,
17
27
  name: verifiedName,
18
28
  ref,
19
- ...styles.icon,
20
29
  ...rest
21
30
  });
22
31
  });
@@ -1 +1 @@
1
- {"version":3,"file":"buttonIcon.js","names":[],"sources":["../../src/button/buttonIcon.tsx"],"sourcesContent":["import { useStyleConfig, vui } from '../core'\nimport type { IconProp, IconProps } from '../icon'\nimport Icon from '../icon'\nimport { cs } from '../utils'\nimport { useButtonContext } from './context'\n\nconst isConceptualIcon = (name?: IconProp) => name?.startsWith('con')\n\n/** Displays an icon within the Button. */\nexport const ButtonIcon = vui<'svg', IconProps>((props, ref) => {\n const { className, name, ...rest } = props\n const styles = useStyleConfig('Button', useButtonContext())\n const verifiedName = isConceptualIcon(name) ? undefined : name\n\n return <Icon className={cs('vui-buttonIcon', className)} name={verifiedName} ref={ref} {...styles.icon} {...rest} />\n})\n\nButtonIcon.displayName = 'ButtonIcon'\nexport default ButtonIcon\n"],"mappings":";;;;;;;;AAMA,MAAM,oBAAoB,SAAoB,MAAM,WAAW,MAAM;;AAGrE,MAAa,aAAa,KAAuB,OAAO,QAAQ;CAC9D,MAAM,EAAE,WAAW,MAAM,GAAG,SAAS;CACrC,MAAM,SAAS,eAAe,UAAU,kBAAkB,CAAC;CAC3D,MAAM,eAAe,iBAAiB,KAAK,GAAG,SAAY;AAE1D,QAAO,oBAAC;EAAK,WAAW,GAAG,kBAAkB,UAAU;EAAE,MAAM;EAAmB;EAAK,GAAI,OAAO;EAAM,GAAI;GAAQ;EACpH;AAEF,WAAW,cAAc"}
1
+ {"version":3,"file":"buttonIcon.js","names":[],"sources":["../../src/button/buttonIcon.tsx"],"sourcesContent":["import { useStyleConfig, vui } from '../core'\nimport type { IconProp, IconProps } from '../icon'\nimport Icon from '../icon'\nimport { cs } from '../utils'\nimport { useButtonContext } from './context'\n\nconst isConceptualIcon = (name?: IconProp) => name?.startsWith('con')\n\n/** Displays an icon within the Button. */\nexport const ButtonIcon = vui<'svg', IconProps>((props, ref) => {\n const { className, name, mr, ml, ...rest } = props\n const buttonContext = useButtonContext()\n const styles = useStyleConfig('Button', buttonContext)\n const verifiedName = isConceptualIcon(name) ? undefined : name\n\n // For icon-only buttons (no text), remove margins\n // This applies to both icon-only dropdown buttons and regular icon-only buttons\n const isIconOnly = !buttonContext?.hasTextContent\n const defaultMarginRight = isIconOnly ? 0 : styles.icon?.mr || 0\n const defaultMarginLeft = isIconOnly ? 0 : styles.icon?.ml || 0\n\n // Apply CSS variable overrides for icon spacing\n const marginRight = mr !== undefined ? mr : `var(--vui-button-icon-mr, ${defaultMarginRight})`\n const marginLeft = ml !== undefined ? ml : `var(--vui-button-icon-ml, ${defaultMarginLeft})`\n\n return (\n <Icon\n className={cs('vui-buttonIcon', className)}\n {...styles.icon}\n fontSize={`var(--vui-button-icon-size, ${styles.icon?.fontSize || '1rem'})`}\n ml={marginLeft}\n mr={marginRight}\n name={verifiedName}\n ref={ref}\n {...rest}\n />\n )\n})\n\nButtonIcon.displayName = 'ButtonIcon'\nexport default ButtonIcon\n"],"mappings":";;;;;;;;AAMA,MAAM,oBAAoB,SAAoB,MAAM,WAAW,MAAM;;AAGrE,MAAa,aAAa,KAAuB,OAAO,QAAQ;CAC9D,MAAM,EAAE,WAAW,MAAM,IAAI,IAAI,GAAG,SAAS;CAC7C,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,SAAS,eAAe,UAAU,cAAc;CACtD,MAAM,eAAe,iBAAiB,KAAK,GAAG,SAAY;CAI1D,MAAM,aAAa,CAAC,eAAe;CACnC,MAAM,qBAAqB,aAAa,IAAI,OAAO,MAAM,MAAM;CAC/D,MAAM,oBAAoB,aAAa,IAAI,OAAO,MAAM,MAAM;CAG9D,MAAM,cAAc,OAAO,SAAY,KAAK,6BAA6B,mBAAmB;CAC5F,MAAM,aAAa,OAAO,SAAY,KAAK,6BAA6B,kBAAkB;AAE1F,QACE,oBAAC;EACC,WAAW,GAAG,kBAAkB,UAAU;EAC1C,GAAI,OAAO;EACX,UAAU,+BAA+B,OAAO,MAAM,YAAY,OAAO;EACzE,IAAI;EACJ,IAAI;EACJ,MAAM;EACD;EACL,GAAI;GACJ;EAEJ;AAEF,WAAW,cAAc"}