@versini/ui-button 12.0.4 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{287.js → 188.js} +17 -9
- package/dist/370.js +220 -0
- package/dist/{285.js → 795.js} +3 -4
- package/dist/components/Button/Button.d.ts +1 -1
- package/dist/components/Button/Button.js +6 -7
- package/dist/components/Button/ButtonCopy.js +4 -6
- package/dist/components/Button/ButtonIcon.d.ts +1 -1
- package/dist/components/Button/ButtonIcon.js +2 -218
- package/dist/components/Button/ButtonLink.d.ts +1 -1
- package/dist/components/Button/ButtonLink.js +5 -8
- package/dist/components/Button/utilities.d.ts +1 -1
- package/package.json +3 -3
package/dist/{287.js → 188.js}
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-button v12.0
|
|
2
|
+
@versini/ui-button v12.1.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import clsx from "clsx";
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
const BUTTON_CLASSNAME = "av-button";
|
|
10
11
|
|
|
11
12
|
|
|
@@ -188,12 +189,17 @@ const getButtonTextCopyClasses = ({ mode, noBackground, truncate, variant })=>{
|
|
|
188
189
|
return clsx(NOT_PLUME, TEXT_COPY_LIGHTER, truncateClass);
|
|
189
190
|
}
|
|
190
191
|
/* v8 ignore stop */ };
|
|
191
|
-
|
|
192
|
+
// Aqua-style split background overlay:
|
|
193
|
+
// - Top half: flat white overlay (light, uniform)
|
|
194
|
+
// - Bottom half: starts darker, gradually lightens toward the bottom edge
|
|
195
|
+
const BG_SPLIT_OVERLAY = "bg-[linear-gradient(to_bottom,oklch(1_0_0/0.2)_50%,oklch(0_0_0/0.1)_50%,oklch(1_0_0/0.05)_100%)]";
|
|
196
|
+
const getButtonBackgroundClasses = ({ mode, noBackground, variant, splitBackground })=>{
|
|
192
197
|
if (noBackground) {
|
|
193
198
|
return;
|
|
194
199
|
}
|
|
200
|
+
const splitClass = splitBackground ? BG_SPLIT_OVERLAY : "";
|
|
195
201
|
if (isPrimary(variant)) {
|
|
196
|
-
return clsx({
|
|
202
|
+
return clsx(splitClass, {
|
|
197
203
|
[BG_ACTION_LIGHT]: isLight(mode),
|
|
198
204
|
[BG_ACTION_DARK]: isDark(mode),
|
|
199
205
|
[BG_LIGHT_DARK_DARK]: isSystem(mode),
|
|
@@ -201,7 +207,7 @@ const getButtonBackgroundClasses = ({ mode, noBackground, variant })=>{
|
|
|
201
207
|
});
|
|
202
208
|
}
|
|
203
209
|
if (isSecondary(variant)) {
|
|
204
|
-
return clsx({
|
|
210
|
+
return clsx(splitClass, {
|
|
205
211
|
[BG_ACTION_DARK]: isLight(mode),
|
|
206
212
|
[BG_ACTION_LIGHT]: isDark(mode),
|
|
207
213
|
[BG_DARK_DARK_LIGHT]: isSystem(mode),
|
|
@@ -209,7 +215,7 @@ const getButtonBackgroundClasses = ({ mode, noBackground, variant })=>{
|
|
|
209
215
|
});
|
|
210
216
|
}
|
|
211
217
|
if (isDanger(variant)) {
|
|
212
|
-
return clsx({
|
|
218
|
+
return clsx(splitClass, {
|
|
213
219
|
[BG_DANGER_DARK]: isDark(mode),
|
|
214
220
|
[BG_DANGER_LIGHT]: isLight(mode),
|
|
215
221
|
[BG_DANGER_DARK_DARK_LIGHT]: isSystem(mode),
|
|
@@ -217,7 +223,7 @@ const getButtonBackgroundClasses = ({ mode, noBackground, variant })=>{
|
|
|
217
223
|
});
|
|
218
224
|
}
|
|
219
225
|
/* v8 ignore start - selected variant edge case */ if (isSelected(variant)) {
|
|
220
|
-
return "bg-action-selected-dark";
|
|
226
|
+
return clsx(splitClass, "bg-action-selected-dark");
|
|
221
227
|
}
|
|
222
228
|
/* v8 ignore stop */ };
|
|
223
229
|
const getButtonRadiusClasses = ({ radius })=>{
|
|
@@ -412,7 +418,7 @@ const getBadgeClasses = ({ size, badge })=>{
|
|
|
412
418
|
displayValue
|
|
413
419
|
};
|
|
414
420
|
};
|
|
415
|
-
const getButtonClasses = ({ type, className, raw, mode, focusMode, disabled, fullWidth, size, noBorder, labelRight, labelLeft, noBackground, variant, truncate, align, radius, animated, badge })=>{
|
|
421
|
+
const getButtonClasses = ({ type, className, raw, mode, focusMode, disabled, fullWidth, size, noBorder, labelRight, labelLeft, noBackground, variant, truncate, align, radius, animated, badge, splitBackground })=>{
|
|
416
422
|
if (!variant) {
|
|
417
423
|
variant = "primary";
|
|
418
424
|
}
|
|
@@ -425,7 +431,8 @@ const getButtonClasses = ({ type, className, raw, mode, focusMode, disabled, ful
|
|
|
425
431
|
}), getButtonBackgroundClasses({
|
|
426
432
|
mode,
|
|
427
433
|
noBackground,
|
|
428
|
-
variant
|
|
434
|
+
variant,
|
|
435
|
+
splitBackground
|
|
429
436
|
}), getButtonRadiusClasses({
|
|
430
437
|
radius
|
|
431
438
|
}), getButtonSizesClasses({
|
|
@@ -464,4 +471,5 @@ const getButtonClasses = ({ type, className, raw, mode, focusMode, disabled, ful
|
|
|
464
471
|
}), className);
|
|
465
472
|
};
|
|
466
473
|
|
|
467
|
-
export { TYPE_BUTTON, TYPE_ICON, TYPE_LINK, getBadgeClasses, getButtonClasses, getButtonIconLabelClasses, getIconClasses };
|
|
474
|
+
export { TYPE_BUTTON, TYPE_ICON, TYPE_LINK, clsx, getBadgeClasses, getButtonClasses, getButtonIconLabelClasses, getIconClasses };
|
|
475
|
+
export { jsx, jsxs } from "react/jsx-runtime";
|
package/dist/370.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
@versini/ui-button v12.1.0
|
|
3
|
+
© 2026 gizmette.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useMergeRefs } from "@versini/ui-hooks/use-merge-refs";
|
|
7
|
+
import { useResizeObserver } from "@versini/ui-hooks/use-resize-observer";
|
|
8
|
+
import { jsxs, getButtonClasses, getIconClasses, getButtonIconLabelClasses, TYPE_ICON, getBadgeClasses, jsx } from "./188.js";
|
|
9
|
+
import { useLayoutEffect, useRef, useEffect, BaseButton_private } from "./795.js";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const WIDTH = {
|
|
20
|
+
small: 24,
|
|
21
|
+
medium: 32,
|
|
22
|
+
large: 48
|
|
23
|
+
};
|
|
24
|
+
const PADDINGS = {
|
|
25
|
+
small: 8 * 2,
|
|
26
|
+
medium: 12 * 2,
|
|
27
|
+
large: 16 * 2
|
|
28
|
+
};
|
|
29
|
+
const BORDERS = 2; // border x 2
|
|
30
|
+
const ANIMATION_DURATION = 300; // ms - should match the CSS transition duration
|
|
31
|
+
function ButtonIcon({ children, disabled = false, mode = "system", focusMode = "system", fullWidth = false, className, type = "button", raw = false, noBorder = false, "aria-label": ariaLabel, label, size = "medium", labelRight, labelLeft, noBackground = false, align = "center", radius = "large", variant = "secondary", iconClassName, animated = false, badge, splitBackground = false, ref, ...otherProps }) {
|
|
32
|
+
const buttonClass = getButtonClasses({
|
|
33
|
+
type: TYPE_ICON,
|
|
34
|
+
mode,
|
|
35
|
+
focusMode,
|
|
36
|
+
fullWidth,
|
|
37
|
+
disabled,
|
|
38
|
+
raw,
|
|
39
|
+
className,
|
|
40
|
+
noBorder,
|
|
41
|
+
size,
|
|
42
|
+
labelRight,
|
|
43
|
+
labelLeft,
|
|
44
|
+
noBackground,
|
|
45
|
+
align,
|
|
46
|
+
radius,
|
|
47
|
+
variant,
|
|
48
|
+
animated,
|
|
49
|
+
badge,
|
|
50
|
+
splitBackground
|
|
51
|
+
});
|
|
52
|
+
const iconClass = getIconClasses({
|
|
53
|
+
mode,
|
|
54
|
+
raw,
|
|
55
|
+
iconClassName,
|
|
56
|
+
variant
|
|
57
|
+
});
|
|
58
|
+
const labelClass = getButtonIconLabelClasses({
|
|
59
|
+
animated
|
|
60
|
+
});
|
|
61
|
+
// Create a combined class for the button content wrapper
|
|
62
|
+
const contentWrapperClass = "flex items-center justify-center relative w-full h-full overflow-hidden";
|
|
63
|
+
const [labelRightRef, rectR] = useResizeObserver();
|
|
64
|
+
const [labelLeftRef, rectL] = useResizeObserver();
|
|
65
|
+
const [iconRef, rectIcon] = useResizeObserver();
|
|
66
|
+
const bufferRef = useRef(0);
|
|
67
|
+
const buttonRef = useRef(null);
|
|
68
|
+
const timeoutRef = useRef(null);
|
|
69
|
+
const mergedRef = useMergeRefs([
|
|
70
|
+
ref,
|
|
71
|
+
buttonRef
|
|
72
|
+
]);
|
|
73
|
+
/**
|
|
74
|
+
* Effect to calculate the buffer to add to the width of the button to
|
|
75
|
+
* account for the icon, paddings and borders.
|
|
76
|
+
*/ useLayoutEffect(()=>{
|
|
77
|
+
/* v8 ignore start */ if (iconRef && iconRef.current && animated) {
|
|
78
|
+
bufferRef.current = rectIcon.width + PADDINGS[size] + (noBorder ? 0 : BORDERS);
|
|
79
|
+
// Set initial button width if it hasn't been set yet
|
|
80
|
+
if (buttonRef.current && !buttonRef.current.style.width) {
|
|
81
|
+
buttonRef.current.style.width = `${WIDTH[size]}px`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/* v8 ignore stop */ }, [
|
|
85
|
+
rectIcon,
|
|
86
|
+
iconRef,
|
|
87
|
+
size,
|
|
88
|
+
noBorder,
|
|
89
|
+
animated
|
|
90
|
+
]);
|
|
91
|
+
/**
|
|
92
|
+
* Effect to update the width of the button based on the visibility of
|
|
93
|
+
* the right and left labels.
|
|
94
|
+
*/ useLayoutEffect(()=>{
|
|
95
|
+
/* v8 ignore start */ if (buttonRef && buttonRef.current && animated) {
|
|
96
|
+
// Calculate the target width first
|
|
97
|
+
let newWidth = WIDTH[size];
|
|
98
|
+
if (labelRight && labelRightRef && rectR.width > 0) {
|
|
99
|
+
newWidth = rectR.width + bufferRef.current;
|
|
100
|
+
} else if (labelLeft && labelLeftRef && rectL.width > 0) {
|
|
101
|
+
newWidth = rectL.width + bufferRef.current;
|
|
102
|
+
}
|
|
103
|
+
// Clear any existing timeout to prevent race conditions
|
|
104
|
+
if (timeoutRef.current) {
|
|
105
|
+
clearTimeout(timeoutRef.current);
|
|
106
|
+
}
|
|
107
|
+
// Start transition - expand button first
|
|
108
|
+
if (newWidth !== parseInt(buttonRef.current.style.width || "0", 10)) {
|
|
109
|
+
// Make sure labels are hidden during width transition
|
|
110
|
+
if (labelRightRef.current) {
|
|
111
|
+
labelRightRef.current.style.opacity = "0";
|
|
112
|
+
}
|
|
113
|
+
if (labelLeftRef.current) {
|
|
114
|
+
labelLeftRef.current.style.opacity = "0";
|
|
115
|
+
}
|
|
116
|
+
// Set the width to trigger the transition
|
|
117
|
+
buttonRef.current.style.width = `${newWidth}px`;
|
|
118
|
+
// After button width transition completes, show the label
|
|
119
|
+
if (newWidth > WIDTH[size]) {
|
|
120
|
+
timeoutRef.current = setTimeout(()=>{
|
|
121
|
+
if (labelRightRef.current && labelRight) {
|
|
122
|
+
labelRightRef.current.style.opacity = "1";
|
|
123
|
+
}
|
|
124
|
+
if (labelLeftRef.current && labelLeft) {
|
|
125
|
+
labelLeftRef.current.style.opacity = "1";
|
|
126
|
+
}
|
|
127
|
+
timeoutRef.current = null;
|
|
128
|
+
}, ANIMATION_DURATION * 0.8); // Wait for most of the width transition to complete
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// If transitioning to icon-only state, hide labels immediately
|
|
132
|
+
if (newWidth === WIDTH[size]) {
|
|
133
|
+
if (labelRightRef.current) {
|
|
134
|
+
labelRightRef.current.style.opacity = "0";
|
|
135
|
+
}
|
|
136
|
+
if (labelLeftRef.current) {
|
|
137
|
+
labelLeftRef.current.style.opacity = "0";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/* v8 ignore stop */ }, [
|
|
142
|
+
rectR,
|
|
143
|
+
labelRight,
|
|
144
|
+
labelRightRef,
|
|
145
|
+
rectL,
|
|
146
|
+
labelLeft,
|
|
147
|
+
labelLeftRef,
|
|
148
|
+
size,
|
|
149
|
+
animated
|
|
150
|
+
]);
|
|
151
|
+
// Clean up timeout on unmount
|
|
152
|
+
/* v8 ignore start */ useEffect(()=>{
|
|
153
|
+
return ()=>{
|
|
154
|
+
if (timeoutRef.current) {
|
|
155
|
+
clearTimeout(timeoutRef.current);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}, []);
|
|
159
|
+
/* v8 ignore stop */ const badgeInfo = getBadgeClasses({
|
|
160
|
+
size,
|
|
161
|
+
badge
|
|
162
|
+
});
|
|
163
|
+
return /*#__PURE__*/ jsxs(BaseButton_private, {
|
|
164
|
+
ref: mergedRef,
|
|
165
|
+
className: buttonClass,
|
|
166
|
+
disabled: disabled,
|
|
167
|
+
type: type,
|
|
168
|
+
"aria-label": ariaLabel || label,
|
|
169
|
+
...otherProps,
|
|
170
|
+
children: [
|
|
171
|
+
/*#__PURE__*/ jsxs("div", {
|
|
172
|
+
className: contentWrapperClass,
|
|
173
|
+
children: [
|
|
174
|
+
/*#__PURE__*/ jsx(ButtonLabel, {
|
|
175
|
+
label: labelLeft,
|
|
176
|
+
labelRef: labelLeftRef,
|
|
177
|
+
labelClass: labelClass,
|
|
178
|
+
labelInnerClass: "pr-2",
|
|
179
|
+
initiallyHidden: animated
|
|
180
|
+
}),
|
|
181
|
+
/*#__PURE__*/ jsx("span", {
|
|
182
|
+
ref: iconRef,
|
|
183
|
+
className: iconClass,
|
|
184
|
+
children: children
|
|
185
|
+
}),
|
|
186
|
+
/*#__PURE__*/ jsx(ButtonLabel, {
|
|
187
|
+
label: labelRight,
|
|
188
|
+
labelRef: labelRightRef,
|
|
189
|
+
labelClass: labelClass,
|
|
190
|
+
labelInnerClass: "pl-2",
|
|
191
|
+
initiallyHidden: animated
|
|
192
|
+
})
|
|
193
|
+
]
|
|
194
|
+
}),
|
|
195
|
+
badgeInfo && /*#__PURE__*/ jsx("span", {
|
|
196
|
+
className: badgeInfo.className,
|
|
197
|
+
"aria-hidden": "true",
|
|
198
|
+
children: badgeInfo.displayValue
|
|
199
|
+
})
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const ButtonLabel = ({ labelRef, labelClass, label, labelInnerClass, initiallyHidden = false })=>{
|
|
204
|
+
if (!label && !initiallyHidden) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return /*#__PURE__*/ jsx("span", {
|
|
208
|
+
ref: labelRef,
|
|
209
|
+
className: labelClass,
|
|
210
|
+
style: initiallyHidden ? {
|
|
211
|
+
opacity: 0
|
|
212
|
+
} : undefined,
|
|
213
|
+
children: label && /*#__PURE__*/ jsx("span", {
|
|
214
|
+
className: labelInnerClass,
|
|
215
|
+
children: label
|
|
216
|
+
})
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export { ButtonIcon };
|
package/dist/{285.js → 795.js}
RENAMED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-button v12.0
|
|
2
|
+
@versini/ui-button v12.1.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { jsx } from "
|
|
7
|
-
import "react";
|
|
8
|
-
|
|
6
|
+
import { jsx } from "./188.js";
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
|
|
@@ -42,3 +40,4 @@ import "react";
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
export { BaseButton_private };
|
|
43
|
+
export { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ButtonTypes } from "@versini/ui-types";
|
|
2
|
-
export declare function Button({ children, disabled, mode, focusMode, fullWidth, className, size, raw, noBorder, variant, truncate, radius, ref, ...otherProps }: ButtonTypes.Props): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function Button({ children, disabled, mode, focusMode, fullWidth, className, size, raw, noBorder, variant, truncate, radius, splitBackground, ref, ...otherProps }: ButtonTypes.Props): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-button v12.0
|
|
2
|
+
@versini/ui-button v12.1.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { jsx } from "
|
|
7
|
-
import {
|
|
8
|
-
import { BaseButton_private } from "../../285.js";
|
|
6
|
+
import { getButtonClasses, TYPE_BUTTON, jsx } from "../../188.js";
|
|
7
|
+
import { BaseButton_private } from "../../795.js";
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
function Button({ children, disabled = false, mode = "system", focusMode = "system", fullWidth = false, className, size = "medium", raw = false, noBorder = false, variant = "primary", truncate = false, radius = "large", ref, ...otherProps }) {
|
|
12
|
+
function Button({ children, disabled = false, mode = "system", focusMode = "system", fullWidth = false, className, size = "medium", raw = false, noBorder = false, variant = "primary", truncate = false, radius = "large", splitBackground = false, ref, ...otherProps }) {
|
|
15
13
|
const buttonClass = getButtonClasses({
|
|
16
14
|
type: TYPE_BUTTON,
|
|
17
15
|
mode,
|
|
@@ -24,7 +22,8 @@ function Button({ children, disabled = false, mode = "system", focusMode = "syst
|
|
|
24
22
|
noBorder,
|
|
25
23
|
variant,
|
|
26
24
|
truncate,
|
|
27
|
-
radius
|
|
25
|
+
radius,
|
|
26
|
+
splitBackground
|
|
28
27
|
});
|
|
29
28
|
return /*#__PURE__*/ jsx(BaseButton_private, {
|
|
30
29
|
ref: ref,
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-button v12.0
|
|
2
|
+
@versini/ui-button v12.1.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { jsx } from "react/jsx-runtime";
|
|
7
6
|
import { IconCopied, IconCopy } from "@versini/ui-icons";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import { jsx } from "../../188.js";
|
|
8
|
+
import { useState, useEffect } from "../../795.js";
|
|
9
|
+
import { ButtonIcon } from "../../370.js";
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ButtonIconTypes } from "@versini/ui-types";
|
|
2
|
-
export declare function ButtonIcon({ children, disabled, mode, focusMode, fullWidth, className, type, raw, noBorder, "aria-label": ariaLabel, label, size, labelRight, labelLeft, noBackground, align, radius, variant, iconClassName, animated, badge, ref, ...otherProps }: ButtonIconTypes.Props): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ButtonIcon({ children, disabled, mode, focusMode, fullWidth, className, type, raw, noBorder, "aria-label": ariaLabel, label, size, labelRight, labelLeft, noBackground, align, radius, variant, iconClassName, animated, badge, splitBackground, ref, ...otherProps }: ButtonIconTypes.Props): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,223 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-button v12.0
|
|
2
|
+
@versini/ui-button v12.1.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { useMergeRefs } from "@versini/ui-hooks/use-merge-refs";
|
|
8
|
-
import { useResizeObserver } from "@versini/ui-hooks/use-resize-observer";
|
|
9
|
-
import { useEffect, useLayoutEffect, useRef } from "react";
|
|
10
|
-
import { getButtonClasses, getIconClasses, getButtonIconLabelClasses, TYPE_ICON, getBadgeClasses } from "../../287.js";
|
|
11
|
-
import { BaseButton_private } from "../../285.js";
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const WIDTH = {
|
|
24
|
-
small: 24,
|
|
25
|
-
medium: 32,
|
|
26
|
-
large: 48
|
|
27
|
-
};
|
|
28
|
-
const PADDINGS = {
|
|
29
|
-
small: 8 * 2,
|
|
30
|
-
medium: 12 * 2,
|
|
31
|
-
large: 16 * 2
|
|
32
|
-
};
|
|
33
|
-
const BORDERS = 2; // border x 2
|
|
34
|
-
const ANIMATION_DURATION = 300; // ms - should match the CSS transition duration
|
|
35
|
-
function ButtonIcon({ children, disabled = false, mode = "system", focusMode = "system", fullWidth = false, className, type = "button", raw = false, noBorder = false, "aria-label": ariaLabel, label, size = "medium", labelRight, labelLeft, noBackground = false, align = "center", radius = "large", variant = "secondary", iconClassName, animated = false, badge, ref, ...otherProps }) {
|
|
36
|
-
const buttonClass = getButtonClasses({
|
|
37
|
-
type: TYPE_ICON,
|
|
38
|
-
mode,
|
|
39
|
-
focusMode,
|
|
40
|
-
fullWidth,
|
|
41
|
-
disabled,
|
|
42
|
-
raw,
|
|
43
|
-
className,
|
|
44
|
-
noBorder,
|
|
45
|
-
size,
|
|
46
|
-
labelRight,
|
|
47
|
-
labelLeft,
|
|
48
|
-
noBackground,
|
|
49
|
-
align,
|
|
50
|
-
radius,
|
|
51
|
-
variant,
|
|
52
|
-
animated,
|
|
53
|
-
badge
|
|
54
|
-
});
|
|
55
|
-
const iconClass = getIconClasses({
|
|
56
|
-
mode,
|
|
57
|
-
raw,
|
|
58
|
-
iconClassName,
|
|
59
|
-
variant
|
|
60
|
-
});
|
|
61
|
-
const labelClass = getButtonIconLabelClasses({
|
|
62
|
-
animated
|
|
63
|
-
});
|
|
64
|
-
// Create a combined class for the button content wrapper
|
|
65
|
-
const contentWrapperClass = "flex items-center justify-center relative w-full h-full overflow-hidden";
|
|
66
|
-
const [labelRightRef, rectR] = useResizeObserver();
|
|
67
|
-
const [labelLeftRef, rectL] = useResizeObserver();
|
|
68
|
-
const [iconRef, rectIcon] = useResizeObserver();
|
|
69
|
-
const bufferRef = useRef(0);
|
|
70
|
-
const buttonRef = useRef(null);
|
|
71
|
-
const timeoutRef = useRef(null);
|
|
72
|
-
const mergedRef = useMergeRefs([
|
|
73
|
-
ref,
|
|
74
|
-
buttonRef
|
|
75
|
-
]);
|
|
76
|
-
/**
|
|
77
|
-
* Effect to calculate the buffer to add to the width of the button to
|
|
78
|
-
* account for the icon, paddings and borders.
|
|
79
|
-
*/ useLayoutEffect(()=>{
|
|
80
|
-
/* v8 ignore start */ if (iconRef && iconRef.current && animated) {
|
|
81
|
-
bufferRef.current = rectIcon.width + PADDINGS[size] + (noBorder ? 0 : BORDERS);
|
|
82
|
-
// Set initial button width if it hasn't been set yet
|
|
83
|
-
if (buttonRef.current && !buttonRef.current.style.width) {
|
|
84
|
-
buttonRef.current.style.width = `${WIDTH[size]}px`;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/* v8 ignore stop */ }, [
|
|
88
|
-
rectIcon,
|
|
89
|
-
iconRef,
|
|
90
|
-
size,
|
|
91
|
-
noBorder,
|
|
92
|
-
animated
|
|
93
|
-
]);
|
|
94
|
-
/**
|
|
95
|
-
* Effect to update the width of the button based on the visibility of
|
|
96
|
-
* the right and left labels.
|
|
97
|
-
*/ useLayoutEffect(()=>{
|
|
98
|
-
/* v8 ignore start */ if (buttonRef && buttonRef.current && animated) {
|
|
99
|
-
// Calculate the target width first
|
|
100
|
-
let newWidth = WIDTH[size];
|
|
101
|
-
if (labelRight && labelRightRef && rectR.width > 0) {
|
|
102
|
-
newWidth = rectR.width + bufferRef.current;
|
|
103
|
-
} else if (labelLeft && labelLeftRef && rectL.width > 0) {
|
|
104
|
-
newWidth = rectL.width + bufferRef.current;
|
|
105
|
-
}
|
|
106
|
-
// Clear any existing timeout to prevent race conditions
|
|
107
|
-
if (timeoutRef.current) {
|
|
108
|
-
clearTimeout(timeoutRef.current);
|
|
109
|
-
}
|
|
110
|
-
// Start transition - expand button first
|
|
111
|
-
if (newWidth !== parseInt(buttonRef.current.style.width || "0", 10)) {
|
|
112
|
-
// Make sure labels are hidden during width transition
|
|
113
|
-
if (labelRightRef.current) {
|
|
114
|
-
labelRightRef.current.style.opacity = "0";
|
|
115
|
-
}
|
|
116
|
-
if (labelLeftRef.current) {
|
|
117
|
-
labelLeftRef.current.style.opacity = "0";
|
|
118
|
-
}
|
|
119
|
-
// Set the width to trigger the transition
|
|
120
|
-
buttonRef.current.style.width = `${newWidth}px`;
|
|
121
|
-
// After button width transition completes, show the label
|
|
122
|
-
if (newWidth > WIDTH[size]) {
|
|
123
|
-
timeoutRef.current = setTimeout(()=>{
|
|
124
|
-
if (labelRightRef.current && labelRight) {
|
|
125
|
-
labelRightRef.current.style.opacity = "1";
|
|
126
|
-
}
|
|
127
|
-
if (labelLeftRef.current && labelLeft) {
|
|
128
|
-
labelLeftRef.current.style.opacity = "1";
|
|
129
|
-
}
|
|
130
|
-
timeoutRef.current = null;
|
|
131
|
-
}, ANIMATION_DURATION * 0.8); // Wait for most of the width transition to complete
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
// If transitioning to icon-only state, hide labels immediately
|
|
135
|
-
if (newWidth === WIDTH[size]) {
|
|
136
|
-
if (labelRightRef.current) {
|
|
137
|
-
labelRightRef.current.style.opacity = "0";
|
|
138
|
-
}
|
|
139
|
-
if (labelLeftRef.current) {
|
|
140
|
-
labelLeftRef.current.style.opacity = "0";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
/* v8 ignore stop */ }, [
|
|
145
|
-
rectR,
|
|
146
|
-
labelRight,
|
|
147
|
-
labelRightRef,
|
|
148
|
-
rectL,
|
|
149
|
-
labelLeft,
|
|
150
|
-
labelLeftRef,
|
|
151
|
-
size,
|
|
152
|
-
animated
|
|
153
|
-
]);
|
|
154
|
-
// Clean up timeout on unmount
|
|
155
|
-
/* v8 ignore start */ useEffect(()=>{
|
|
156
|
-
return ()=>{
|
|
157
|
-
if (timeoutRef.current) {
|
|
158
|
-
clearTimeout(timeoutRef.current);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
}, []);
|
|
162
|
-
/* v8 ignore stop */ const badgeInfo = getBadgeClasses({
|
|
163
|
-
size,
|
|
164
|
-
badge
|
|
165
|
-
});
|
|
166
|
-
return /*#__PURE__*/ jsxs(BaseButton_private, {
|
|
167
|
-
ref: mergedRef,
|
|
168
|
-
className: buttonClass,
|
|
169
|
-
disabled: disabled,
|
|
170
|
-
type: type,
|
|
171
|
-
"aria-label": ariaLabel || label,
|
|
172
|
-
...otherProps,
|
|
173
|
-
children: [
|
|
174
|
-
/*#__PURE__*/ jsxs("div", {
|
|
175
|
-
className: contentWrapperClass,
|
|
176
|
-
children: [
|
|
177
|
-
/*#__PURE__*/ jsx(ButtonLabel, {
|
|
178
|
-
label: labelLeft,
|
|
179
|
-
labelRef: labelLeftRef,
|
|
180
|
-
labelClass: labelClass,
|
|
181
|
-
labelInnerClass: "pr-2",
|
|
182
|
-
initiallyHidden: animated
|
|
183
|
-
}),
|
|
184
|
-
/*#__PURE__*/ jsx("span", {
|
|
185
|
-
ref: iconRef,
|
|
186
|
-
className: iconClass,
|
|
187
|
-
children: children
|
|
188
|
-
}),
|
|
189
|
-
/*#__PURE__*/ jsx(ButtonLabel, {
|
|
190
|
-
label: labelRight,
|
|
191
|
-
labelRef: labelRightRef,
|
|
192
|
-
labelClass: labelClass,
|
|
193
|
-
labelInnerClass: "pl-2",
|
|
194
|
-
initiallyHidden: animated
|
|
195
|
-
})
|
|
196
|
-
]
|
|
197
|
-
}),
|
|
198
|
-
badgeInfo && /*#__PURE__*/ jsx("span", {
|
|
199
|
-
className: badgeInfo.className,
|
|
200
|
-
"aria-hidden": "true",
|
|
201
|
-
children: badgeInfo.displayValue
|
|
202
|
-
})
|
|
203
|
-
]
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
const ButtonLabel = ({ labelRef, labelClass, label, labelInnerClass, initiallyHidden = false })=>{
|
|
207
|
-
if (!label && !initiallyHidden) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
return /*#__PURE__*/ jsx("span", {
|
|
211
|
-
ref: labelRef,
|
|
212
|
-
className: labelClass,
|
|
213
|
-
style: initiallyHidden ? {
|
|
214
|
-
opacity: 0
|
|
215
|
-
} : undefined,
|
|
216
|
-
children: label && /*#__PURE__*/ jsx("span", {
|
|
217
|
-
className: labelInnerClass,
|
|
218
|
-
children: label
|
|
219
|
-
})
|
|
220
|
-
});
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
export { ButtonIcon };
|
|
7
|
+
export { ButtonIcon } from "../../370.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ButtonLinkTypes } from "@versini/ui-types";
|
|
2
|
-
export declare function ButtonLink({ children, mode, focusMode, fullWidth, className, size, raw, noBorder, target, truncate, noNewWindowIcon, radius, ref, ...otherProps }: ButtonLinkTypes.Props): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ButtonLink({ children, mode, focusMode, fullWidth, className, size, raw, noBorder, target, truncate, noNewWindowIcon, radius, splitBackground, ref, ...otherProps }: ButtonLinkTypes.Props): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-button v12.0
|
|
2
|
+
@versini/ui-button v12.1.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import clsx from "clsx";
|
|
8
|
-
import { getButtonClasses, TYPE_LINK } from "../../287.js";
|
|
6
|
+
import { jsxs, getButtonClasses, TYPE_LINK, clsx, jsx } from "../../188.js";
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function ButtonLink({ children, mode = "system", focusMode = "system", fullWidth = false, className, size = "small", raw = false, noBorder = false, target, truncate = false, noNewWindowIcon = false, radius = "large", ref, ...otherProps }) {
|
|
11
|
+
function ButtonLink({ children, mode = "system", focusMode = "system", fullWidth = false, className, size = "small", raw = false, noBorder = false, target, truncate = false, noNewWindowIcon = false, radius = "large", splitBackground = false, ref, ...otherProps }) {
|
|
16
12
|
const buttonClass = getButtonClasses({
|
|
17
13
|
type: TYPE_LINK,
|
|
18
14
|
mode,
|
|
@@ -24,7 +20,8 @@ function ButtonLink({ children, mode = "system", focusMode = "system", fullWidth
|
|
|
24
20
|
size,
|
|
25
21
|
noBorder,
|
|
26
22
|
truncate,
|
|
27
|
-
radius
|
|
23
|
+
radius,
|
|
24
|
+
splitBackground
|
|
28
25
|
});
|
|
29
26
|
const newWindow = target === "_blank";
|
|
30
27
|
const extraProps = {
|
|
@@ -13,5 +13,5 @@ export declare const getBadgeClasses: ({ size, badge, }: Pick<ButtonIconTypes.Pr
|
|
|
13
13
|
className: string;
|
|
14
14
|
displayValue: string | null;
|
|
15
15
|
} | null;
|
|
16
|
-
export declare const getButtonClasses: ({ type, className, raw, mode, focusMode, disabled, fullWidth, size, noBorder, labelRight, labelLeft, noBackground, variant, truncate, align, radius, animated, badge, }: GetButtonClassesProps) => string;
|
|
16
|
+
export declare const getButtonClasses: ({ type, className, raw, mode, focusMode, disabled, fullWidth, size, noBorder, labelRight, labelLeft, noBackground, variant, truncate, align, radius, animated, badge, splitBackground, }: GetButtonClassesProps) => string;
|
|
17
17
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versini/ui-button",
|
|
3
|
-
"version": "12.0
|
|
3
|
+
"version": "12.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Arno Versini",
|
|
6
6
|
"publishConfig": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@testing-library/jest-dom": "6.9.1",
|
|
62
|
-
"@versini/ui-types": "8.
|
|
62
|
+
"@versini/ui-types": "8.4.0"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@versini/ui-hooks": "6.1.1",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"sideEffects": [
|
|
71
71
|
"**/*.css"
|
|
72
72
|
],
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "08f46d8b356d5cfbb4e58dca300428ed6bb03789"
|
|
74
74
|
}
|