@edux-design/buttons 0.1.3 → 0.1.4
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/CHANGELOG.md +6 -0
- package/dist/index.d.mts +230 -0
- package/dist/index.d.ts +230 -0
- package/dist/index.js +803 -0
- package/dist/index.mjs +777 -0
- package/package.json +7 -1
- package/src/demo/Button.stories.jsx +0 -103
- package/src/demo/ButtonCounter.stories.jsx +0 -101
- package/src/demo/ButtonGroup.stories.jsx +0 -67
- package/src/elements/Button.jsx +0 -127
- package/src/elements/Button.test.jsx +0 -120
- package/src/elements/ButtonCounter.jsx +0 -341
- package/src/elements/ButtonCounter.test.jsx +0 -100
- package/src/elements/ButtonGroup.jsx +0 -389
- package/src/elements/ButtonGroup.test.jsx +0 -69
- package/src/elements/ButtonGroupItem.jsx +0 -8
- package/src/elements/IconButton.jsx +0 -104
- package/src/elements/MenuButton.jsx +0 -44
- package/src/index.js +0 -6
- package/src/util/MenuButtonContext.jsx +0 -3
- package/tsconfig.json +0 -12
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
// src/elements/IconButton.jsx
|
|
2
|
+
import React, { forwardRef } from "react";
|
|
3
|
+
import { cx } from "@edux-design/utils";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
var IconButton = forwardRef(
|
|
6
|
+
({
|
|
7
|
+
icon,
|
|
8
|
+
size = "medium",
|
|
9
|
+
isSecondary,
|
|
10
|
+
isDanger,
|
|
11
|
+
isBare,
|
|
12
|
+
disabled,
|
|
13
|
+
className,
|
|
14
|
+
...props
|
|
15
|
+
}, ref) => {
|
|
16
|
+
let variant = "primary";
|
|
17
|
+
if (isBare) variant = "bare";
|
|
18
|
+
if (isSecondary) variant = "secondary";
|
|
19
|
+
if (isDanger) variant = "danger";
|
|
20
|
+
if (disabled) variant = "disabled";
|
|
21
|
+
if (process.env.NODE_ENV !== "production") {
|
|
22
|
+
const count = [isSecondary, isDanger, isBare].filter(Boolean).length;
|
|
23
|
+
if (count > 1) {
|
|
24
|
+
console.warn(
|
|
25
|
+
"[IconButton]: Multiple variant props passed. Using the highest priority only."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const defaults = "flex justify-center items-center focus:shadow-focus focus-visible:shadow-focus outline-none transition-all duration-300 ease-in-out rounded-full cursor-pointer";
|
|
30
|
+
const sizes = {
|
|
31
|
+
small: "h-[36px] w-[36px] text-sm",
|
|
32
|
+
medium: "h-[44px] w-[44px] text-base",
|
|
33
|
+
large: "h-[56px] w-[56px] text-lg"
|
|
34
|
+
};
|
|
35
|
+
const variants = {
|
|
36
|
+
primary: "border-2 border-border-primary-emphasis bg-bg-primary-base text-white hover:bg-border-focus hover:text-fg-focus hover:border-border-emphasis hover:shadow-md active:bg-border-focus active:bg-bg-focus-overlay active:text-fg-focus",
|
|
37
|
+
secondary: "border-2 border-border-primary-base bg-bg-base text-fg-primary-emphasis hover:shadow-md hover:bg-bg-primary-subtle active:bg-bg-primary-subtle-90",
|
|
38
|
+
bare: "bg-bg-base text-fg-primary-emphasis hover:bg-bg-minimal active:bg-bg-minimal-5",
|
|
39
|
+
danger: "bg-bg-danger-base border-2 border-border-danger-emphasis text-white hover:bg-bg-danger-base-5 active:bg-bg-danger-base-10",
|
|
40
|
+
disabled: "border-2 border-border-subtle bg-bg-inactive text-fg-inactive hover:no-underline cursor-not-allowed"
|
|
41
|
+
};
|
|
42
|
+
const STYLES = cx(defaults, sizes[size], variants[variant], className);
|
|
43
|
+
return /* @__PURE__ */ jsx(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
role: "button",
|
|
47
|
+
type: "button",
|
|
48
|
+
"aria-disabled": disabled,
|
|
49
|
+
disabled,
|
|
50
|
+
className: STYLES,
|
|
51
|
+
ref,
|
|
52
|
+
...props,
|
|
53
|
+
children: icon
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// src/elements/Button.jsx
|
|
60
|
+
import React2, { forwardRef as forwardRef2 } from "react";
|
|
61
|
+
import { cx as cx2 } from "@edux-design/utils";
|
|
62
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
63
|
+
var Button = forwardRef2(
|
|
64
|
+
({
|
|
65
|
+
children,
|
|
66
|
+
startIcon,
|
|
67
|
+
endIcon,
|
|
68
|
+
size = "medium",
|
|
69
|
+
isSecondary,
|
|
70
|
+
isDanger,
|
|
71
|
+
isBare,
|
|
72
|
+
isStretched,
|
|
73
|
+
disabled,
|
|
74
|
+
className,
|
|
75
|
+
...props
|
|
76
|
+
}, ref) => {
|
|
77
|
+
let variant = "primary";
|
|
78
|
+
if (disabled) {
|
|
79
|
+
variant = "disabled";
|
|
80
|
+
} else if (isDanger && isBare) {
|
|
81
|
+
variant = "dangerBare";
|
|
82
|
+
} else if (isDanger) {
|
|
83
|
+
variant = "danger";
|
|
84
|
+
} else if (isSecondary) {
|
|
85
|
+
variant = "secondary";
|
|
86
|
+
} else if (isBare) {
|
|
87
|
+
variant = "bare";
|
|
88
|
+
}
|
|
89
|
+
if (process.env.NODE_ENV !== "production") {
|
|
90
|
+
const count = [isSecondary, isDanger, isBare].filter(Boolean).length;
|
|
91
|
+
const hasAllowedCombo = isDanger && isBare && count === 2;
|
|
92
|
+
if (count > 1 && !hasAllowedCombo) {
|
|
93
|
+
console.warn(
|
|
94
|
+
"[Button]: Multiple variant props passed. Using the highest priority only."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const defaults = "flex justify-center items-center gap-8 focus:shadow-focus focus-visible:shadow-focus outline-none transition-all duration-300 ease-in-out cursor-pointer";
|
|
99
|
+
const fonts = "text-bold tracking-normal hover:underline hover:underline-offset-(--underline-offset-btn) active:underline active:underline-offset-(--underline-offset-btn)";
|
|
100
|
+
const sizes = {
|
|
101
|
+
small: "h-control-sm py-8 px-16 rounded-lg text-sm",
|
|
102
|
+
medium: "h-control-md py-8 px-16 rounded-lg text-base",
|
|
103
|
+
large: "h-control-lg py-8 px-16 rounded-lg text-lg"
|
|
104
|
+
};
|
|
105
|
+
const width = isStretched ? "w-full" : "w-fit-content";
|
|
106
|
+
const variants = {
|
|
107
|
+
primary: "border-2 border-border-primary-emphasis bg-bg-primary-base text-white hover:bg-border-focus hover:text-fg-focus hover:border-border-emphasis hover:shadow-md active:bg-border-focus active:bg-bg-focus-overlay active:text-fg-focus",
|
|
108
|
+
secondary: "border-2 border-border-primary-base bg-bg-base text-fg-primary-emphasis hover:shadow-md hover:bg-bg-primary-subtle active:bg-bg-primary-subtle-90",
|
|
109
|
+
bare: "bg-bg-base text-fg-primary-emphasis hover:bg-bg-minimal active:bg-bg-minimal-5",
|
|
110
|
+
dangerBare: "bg-bg-base text-fg-danger-emphasis hover:bg-bg-danger-subtle active:bg-bg-danger-subtle",
|
|
111
|
+
danger: "bg-bg-danger-base border-2 border-border-danger-emphasis text-white hover:bg-bg-danger-base-5 active:bg-bg-danger-base-10",
|
|
112
|
+
disabled: "border-2 border-border-subtle bg-bg-inactive text-fg-inactive hover:no-underline"
|
|
113
|
+
};
|
|
114
|
+
const STYLES = cx2(defaults, sizes[size], width, fonts, variants[variant]);
|
|
115
|
+
return /* @__PURE__ */ jsxs(
|
|
116
|
+
"button",
|
|
117
|
+
{
|
|
118
|
+
role: "button",
|
|
119
|
+
type: "button",
|
|
120
|
+
"aria-disabled": disabled,
|
|
121
|
+
disabled,
|
|
122
|
+
className: STYLES,
|
|
123
|
+
ref,
|
|
124
|
+
...props,
|
|
125
|
+
children: [
|
|
126
|
+
startIcon,
|
|
127
|
+
children ? /* @__PURE__ */ jsx2("span", { children }) : null,
|
|
128
|
+
endIcon
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// src/elements/MenuButton.jsx
|
|
136
|
+
import React4 from "react";
|
|
137
|
+
|
|
138
|
+
// src/util/MenuButtonContext.jsx
|
|
139
|
+
import React3, { createContext, useContext } from "react";
|
|
140
|
+
var MenuButtonContext = createContext();
|
|
141
|
+
|
|
142
|
+
// src/elements/MenuButton.jsx
|
|
143
|
+
import { cx as cx3 } from "@edux-design/utils";
|
|
144
|
+
|
|
145
|
+
// ../icons/dist/index.mjs
|
|
146
|
+
import { jsxs as jsxs2, jsx as jsx3 } from "react/jsx-runtime";
|
|
147
|
+
var SvgChevron = (props) => /* @__PURE__ */ jsx3(
|
|
148
|
+
"svg",
|
|
149
|
+
{
|
|
150
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
151
|
+
width: "1em",
|
|
152
|
+
height: "1em",
|
|
153
|
+
fill: "none",
|
|
154
|
+
viewBox: "0 0 24 24",
|
|
155
|
+
...props,
|
|
156
|
+
children: /* @__PURE__ */ jsx3(
|
|
157
|
+
"path",
|
|
158
|
+
{
|
|
159
|
+
fill: "currentColor",
|
|
160
|
+
fillRule: "evenodd",
|
|
161
|
+
d: "M13.586 12 9.293 7.707a1 1 0 0 1 1.414-1.414l5 5a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414-1.414z",
|
|
162
|
+
clipRule: "evenodd"
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
var Chevron_default = SvgChevron;
|
|
168
|
+
|
|
169
|
+
// src/elements/MenuButton.jsx
|
|
170
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
171
|
+
function MenuButton({ children, size }) {
|
|
172
|
+
const SPLIT_BUTTON_STYLES = cx3(
|
|
173
|
+
"w-[36px] border-l-[1px] border-l-black flex flex-row justify-center items-center",
|
|
174
|
+
{
|
|
175
|
+
"h-[32px]": size === "small",
|
|
176
|
+
"h-[40px] py-2": size === "medium" || size === void 0
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
return /* @__PURE__ */ jsxs3(MenuButtonContext.Provider, { value: true, children: [
|
|
180
|
+
/* @__PURE__ */ jsx4("div", { className: SPLIT_BUTTON_STYLES, children }),
|
|
181
|
+
/* @__PURE__ */ jsx4(Chevron_default, {})
|
|
182
|
+
] });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/elements/ButtonGroup.jsx
|
|
186
|
+
import React5, {
|
|
187
|
+
forwardRef as forwardRef3,
|
|
188
|
+
useCallback,
|
|
189
|
+
useEffect,
|
|
190
|
+
useLayoutEffect,
|
|
191
|
+
useMemo,
|
|
192
|
+
useRef,
|
|
193
|
+
useState
|
|
194
|
+
} from "react";
|
|
195
|
+
import { cx as cx4, filterChildrenByDisplayName } from "@edux-design/utils";
|
|
196
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
197
|
+
var SIZE_STYLES = {
|
|
198
|
+
small: {
|
|
199
|
+
button: "text-xs px-12 py-6 rounded-md "
|
|
200
|
+
},
|
|
201
|
+
medium: {
|
|
202
|
+
button: "text-sm px-12 py-6 rounded-md "
|
|
203
|
+
},
|
|
204
|
+
large: {
|
|
205
|
+
button: "text-base px-12 py-6 rounded-md"
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
var isBrowser = typeof window !== "undefined";
|
|
209
|
+
var useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
|
|
210
|
+
var ButtonGroup = forwardRef3(function ButtonGroup2({
|
|
211
|
+
value,
|
|
212
|
+
defaultValue,
|
|
213
|
+
onChange,
|
|
214
|
+
size = "medium",
|
|
215
|
+
isStretched = false,
|
|
216
|
+
className,
|
|
217
|
+
children,
|
|
218
|
+
...rest
|
|
219
|
+
}, forwardedRef) {
|
|
220
|
+
const normalizedOptions = useMemo(() => {
|
|
221
|
+
const childItems = filterChildrenByDisplayName(children, "ButtonGroupItem");
|
|
222
|
+
return childItems.map((child, index) => {
|
|
223
|
+
const props = child.props || {};
|
|
224
|
+
const hasValue = props.value !== void 0;
|
|
225
|
+
const safeValue = hasValue && props.value !== null ? props.value : index;
|
|
226
|
+
return {
|
|
227
|
+
...props,
|
|
228
|
+
label: props.label ?? props.children ?? `Option ${index + 1}`,
|
|
229
|
+
value: safeValue
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
}, [children]);
|
|
233
|
+
const sizeStyles = SIZE_STYLES[size] ?? SIZE_STYLES.medium;
|
|
234
|
+
const initialValue = value ?? defaultValue ?? (normalizedOptions.length ? normalizedOptions[0].value : void 0);
|
|
235
|
+
const [internalValue, setInternalValue] = useState(initialValue);
|
|
236
|
+
const isControlled = value !== void 0;
|
|
237
|
+
const selectedValue = isControlled ? value : internalValue;
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (!isControlled && defaultValue !== void 0) {
|
|
240
|
+
setInternalValue(defaultValue);
|
|
241
|
+
}
|
|
242
|
+
}, [defaultValue, isControlled]);
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
if (isControlled) return;
|
|
245
|
+
if (normalizedOptions.length && !normalizedOptions.some((option) => option.value === internalValue)) {
|
|
246
|
+
setInternalValue(normalizedOptions[0].value);
|
|
247
|
+
}
|
|
248
|
+
}, [internalValue, isControlled, normalizedOptions]);
|
|
249
|
+
const buttonRefs = useRef(/* @__PURE__ */ new Map());
|
|
250
|
+
const containerRef = useRef(null);
|
|
251
|
+
const indicatorRef = useRef({ width: 0, height: 0, x: 0, y: 0 });
|
|
252
|
+
const [indicator, setIndicator] = useState({
|
|
253
|
+
width: 0,
|
|
254
|
+
height: 0,
|
|
255
|
+
x: 0,
|
|
256
|
+
y: 0
|
|
257
|
+
});
|
|
258
|
+
const setContainerRef = useCallback(
|
|
259
|
+
(node) => {
|
|
260
|
+
containerRef.current = node;
|
|
261
|
+
if (typeof forwardedRef === "function") {
|
|
262
|
+
forwardedRef(node);
|
|
263
|
+
} else if (forwardedRef) {
|
|
264
|
+
forwardedRef.current = node;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
[forwardedRef]
|
|
268
|
+
);
|
|
269
|
+
const registerButtonRef = useCallback((optionValue) => {
|
|
270
|
+
return (node) => {
|
|
271
|
+
if (!buttonRefs.current) return;
|
|
272
|
+
if (node) {
|
|
273
|
+
buttonRefs.current.set(optionValue, node);
|
|
274
|
+
} else {
|
|
275
|
+
buttonRefs.current.delete(optionValue);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}, []);
|
|
279
|
+
const updateIndicator = useCallback(() => {
|
|
280
|
+
const container = containerRef.current;
|
|
281
|
+
const target = buttonRefs.current.get(selectedValue);
|
|
282
|
+
if (!container || !target) {
|
|
283
|
+
if (indicatorRef.current.width !== 0) {
|
|
284
|
+
indicatorRef.current = { width: 0, height: 0, x: 0, y: 0 };
|
|
285
|
+
setIndicator(indicatorRef.current);
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const containerRect = container.getBoundingClientRect();
|
|
290
|
+
const targetRect = target.getBoundingClientRect();
|
|
291
|
+
const next = {
|
|
292
|
+
width: targetRect.width,
|
|
293
|
+
height: targetRect.height,
|
|
294
|
+
x: targetRect.left - containerRect.left,
|
|
295
|
+
y: targetRect.top - containerRect.top
|
|
296
|
+
};
|
|
297
|
+
const prev = indicatorRef.current;
|
|
298
|
+
if (prev.width !== next.width || prev.height !== next.height || prev.x !== next.x || prev.y !== next.y) {
|
|
299
|
+
indicatorRef.current = next;
|
|
300
|
+
setIndicator(next);
|
|
301
|
+
}
|
|
302
|
+
}, [selectedValue]);
|
|
303
|
+
useIsomorphicLayoutEffect(() => {
|
|
304
|
+
updateIndicator();
|
|
305
|
+
}, [updateIndicator, normalizedOptions.length, size]);
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
if (!isBrowser) return;
|
|
308
|
+
const handleResize = () => updateIndicator();
|
|
309
|
+
window.addEventListener("resize", handleResize);
|
|
310
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
311
|
+
}, [updateIndicator]);
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
if (!isBrowser) return;
|
|
314
|
+
const node = containerRef.current;
|
|
315
|
+
if (!node || typeof ResizeObserver === "undefined") return;
|
|
316
|
+
const observer = new ResizeObserver(() => {
|
|
317
|
+
updateIndicator();
|
|
318
|
+
});
|
|
319
|
+
observer.observe(node);
|
|
320
|
+
return () => observer.disconnect();
|
|
321
|
+
}, [updateIndicator]);
|
|
322
|
+
const focusOption = useCallback((option) => {
|
|
323
|
+
const node = buttonRefs.current.get(option.value);
|
|
324
|
+
if (node) {
|
|
325
|
+
node.focus();
|
|
326
|
+
}
|
|
327
|
+
}, []);
|
|
328
|
+
const handleSelect = useCallback(
|
|
329
|
+
(option) => {
|
|
330
|
+
if (option.disabled || option.value === selectedValue) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!isControlled) {
|
|
334
|
+
setInternalValue(option.value);
|
|
335
|
+
}
|
|
336
|
+
if (typeof onChange === "function") {
|
|
337
|
+
onChange(option.value, option);
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
[isControlled, onChange, selectedValue]
|
|
341
|
+
);
|
|
342
|
+
const getNextEnabledIndex = useCallback(
|
|
343
|
+
(startIndex, direction) => {
|
|
344
|
+
if (!normalizedOptions.length) return -1;
|
|
345
|
+
let nextIndex = startIndex;
|
|
346
|
+
for (let i = 0; i < normalizedOptions.length; i += 1) {
|
|
347
|
+
nextIndex = (nextIndex + direction + normalizedOptions.length) % normalizedOptions.length;
|
|
348
|
+
if (!normalizedOptions[nextIndex].disabled) {
|
|
349
|
+
return nextIndex;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return startIndex;
|
|
353
|
+
},
|
|
354
|
+
[normalizedOptions]
|
|
355
|
+
);
|
|
356
|
+
const findFirstEnabledIndex = useCallback(() => {
|
|
357
|
+
return normalizedOptions.findIndex((option) => !option.disabled);
|
|
358
|
+
}, [normalizedOptions]);
|
|
359
|
+
const findLastEnabledIndex = useCallback(() => {
|
|
360
|
+
for (let i = normalizedOptions.length - 1; i >= 0; i -= 1) {
|
|
361
|
+
if (!normalizedOptions[i].disabled) {
|
|
362
|
+
return i;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return -1;
|
|
366
|
+
}, [normalizedOptions]);
|
|
367
|
+
const indicatorVisible = indicator.width > 0 && indicator.height > 0;
|
|
368
|
+
const rootClasses = cx4(
|
|
369
|
+
"inline-flex h-[44px] rounded-lg border-2 border-border-subtle bg-bg-base/90 p-2 shadow-inner",
|
|
370
|
+
isStretched ? "w-full" : "w-auto",
|
|
371
|
+
className
|
|
372
|
+
);
|
|
373
|
+
const trackClasses = cx4(
|
|
374
|
+
"relative isolate flex w-full items-center gap-8",
|
|
375
|
+
!isStretched && "w-auto"
|
|
376
|
+
);
|
|
377
|
+
const buttonBaseClasses = cx4(
|
|
378
|
+
"relative z-10 h-[36px] inline-flex items-center justify-center gap-2 whitespace-nowrap font-semibold tracking-tight transition-colors duration-200 ease-out focus-visible:outline-none focus-visible:shadow-focus motion-reduce:duration-0",
|
|
379
|
+
sizeStyles.button,
|
|
380
|
+
isStretched ? "flex-1" : "w-auto"
|
|
381
|
+
);
|
|
382
|
+
return /* @__PURE__ */ jsx5(
|
|
383
|
+
"div",
|
|
384
|
+
{
|
|
385
|
+
...rest,
|
|
386
|
+
ref: setContainerRef,
|
|
387
|
+
role: "radiogroup",
|
|
388
|
+
className: rootClasses,
|
|
389
|
+
children: /* @__PURE__ */ jsxs4("div", { className: trackClasses, children: [
|
|
390
|
+
/* @__PURE__ */ jsx5(
|
|
391
|
+
"span",
|
|
392
|
+
{
|
|
393
|
+
"aria-hidden": "true",
|
|
394
|
+
className: cx4(
|
|
395
|
+
"pointer-events-none h-[44px] absolute rounded-md border-2 border-border-primary-emphasis bg-bg-base shadow-[0_8px_20px_rgb(22_92_131/0.18)] transition-[transform,width,height] duration-300 ease-out motion-reduce:duration-0",
|
|
396
|
+
!indicatorVisible && "opacity-0"
|
|
397
|
+
),
|
|
398
|
+
style: {
|
|
399
|
+
width: indicator.width || void 0,
|
|
400
|
+
height: indicator.height || void 0,
|
|
401
|
+
transform: `translate(${indicator.x - 4}px`
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
),
|
|
405
|
+
normalizedOptions.map((option, index) => {
|
|
406
|
+
const isActive = option.value === selectedValue;
|
|
407
|
+
const handleKeyDown = (event) => {
|
|
408
|
+
if (option.disabled) return;
|
|
409
|
+
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
|
|
410
|
+
event.preventDefault();
|
|
411
|
+
const nextIndex = getNextEnabledIndex(index, 1);
|
|
412
|
+
const nextOption = normalizedOptions[nextIndex];
|
|
413
|
+
if (nextOption && nextOption !== option) {
|
|
414
|
+
focusOption(nextOption);
|
|
415
|
+
handleSelect(nextOption);
|
|
416
|
+
}
|
|
417
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
|
|
418
|
+
event.preventDefault();
|
|
419
|
+
const prevIndex = getNextEnabledIndex(index, -1);
|
|
420
|
+
const prevOption = normalizedOptions[prevIndex];
|
|
421
|
+
if (prevOption && prevOption !== option) {
|
|
422
|
+
focusOption(prevOption);
|
|
423
|
+
handleSelect(prevOption);
|
|
424
|
+
}
|
|
425
|
+
} else if (event.key === "Home") {
|
|
426
|
+
event.preventDefault();
|
|
427
|
+
const firstIndex = findFirstEnabledIndex();
|
|
428
|
+
const firstOption = firstIndex >= 0 ? normalizedOptions[firstIndex] : void 0;
|
|
429
|
+
if (firstOption && firstOption !== option) {
|
|
430
|
+
focusOption(firstOption);
|
|
431
|
+
handleSelect(firstOption);
|
|
432
|
+
}
|
|
433
|
+
} else if (event.key === "End") {
|
|
434
|
+
event.preventDefault();
|
|
435
|
+
const lastIndex = findLastEnabledIndex();
|
|
436
|
+
const lastOption = lastIndex >= 0 ? normalizedOptions[lastIndex] : void 0;
|
|
437
|
+
if (lastOption && lastOption !== option) {
|
|
438
|
+
focusOption(lastOption);
|
|
439
|
+
handleSelect(lastOption);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const buttonClasses = cx4(
|
|
444
|
+
buttonBaseClasses,
|
|
445
|
+
option.disabled ? "cursor-not-allowed text-fg-inactive opacity-60" : "cursor-pointer text-fg-subtle hover:text-fg-base",
|
|
446
|
+
isActive && "text-fg-primary-emphasis",
|
|
447
|
+
!isActive && "hover:bg-bg-minimal-5 active:bg-bg-inactive"
|
|
448
|
+
);
|
|
449
|
+
const hasStartAdornment = option.startAdornment !== void 0 && option.startAdornment !== null;
|
|
450
|
+
const endAdornment = option.endAdornment ?? option.endIcon;
|
|
451
|
+
const hasEndAdornment = endAdornment !== void 0 && endAdornment !== null;
|
|
452
|
+
return /* @__PURE__ */ jsxs4(
|
|
453
|
+
"button",
|
|
454
|
+
{
|
|
455
|
+
ref: registerButtonRef(option.value),
|
|
456
|
+
type: "button",
|
|
457
|
+
role: "radio",
|
|
458
|
+
"aria-checked": isActive,
|
|
459
|
+
disabled: option.disabled,
|
|
460
|
+
className: buttonClasses,
|
|
461
|
+
onClick: () => handleSelect(option),
|
|
462
|
+
onKeyDown: handleKeyDown,
|
|
463
|
+
"data-state": isActive ? "active" : "inactive",
|
|
464
|
+
children: [
|
|
465
|
+
hasStartAdornment && /* @__PURE__ */ jsx5(
|
|
466
|
+
"span",
|
|
467
|
+
{
|
|
468
|
+
"aria-hidden": "true",
|
|
469
|
+
className: "flex items-center text-current",
|
|
470
|
+
children: option.startAdornment
|
|
471
|
+
}
|
|
472
|
+
),
|
|
473
|
+
/* @__PURE__ */ jsx5("span", { className: "flex items-center gap-1 leading-none text-current", children: option.label ?? `Option ${index + 1}` }),
|
|
474
|
+
hasEndAdornment && /* @__PURE__ */ jsx5(
|
|
475
|
+
"span",
|
|
476
|
+
{
|
|
477
|
+
"aria-hidden": "true",
|
|
478
|
+
className: "flex items-center text-current",
|
|
479
|
+
children: endAdornment
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
]
|
|
483
|
+
},
|
|
484
|
+
String(option.value)
|
|
485
|
+
);
|
|
486
|
+
})
|
|
487
|
+
] })
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// src/elements/ButtonGroupItem.jsx
|
|
493
|
+
import React6 from "react";
|
|
494
|
+
function ButtonGroupItem(_props) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
ButtonGroupItem.displayName = "ButtonGroupItem";
|
|
498
|
+
|
|
499
|
+
// src/elements/ButtonCounter.jsx
|
|
500
|
+
import React7, {
|
|
501
|
+
forwardRef as forwardRef4,
|
|
502
|
+
useCallback as useCallback2,
|
|
503
|
+
useEffect as useEffect2,
|
|
504
|
+
useMemo as useMemo2,
|
|
505
|
+
useRef as useRef2,
|
|
506
|
+
useState as useState2
|
|
507
|
+
} from "react";
|
|
508
|
+
import { cx as cx5 } from "@edux-design/utils";
|
|
509
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
510
|
+
var ButtonCounter = forwardRef4(function ButtonCounter2({
|
|
511
|
+
value,
|
|
512
|
+
defaultValue = 1,
|
|
513
|
+
onChange,
|
|
514
|
+
min = Number.NEGATIVE_INFINITY,
|
|
515
|
+
max = Number.POSITIVE_INFINITY,
|
|
516
|
+
step = 1,
|
|
517
|
+
children,
|
|
518
|
+
size = "medium",
|
|
519
|
+
isSecondary,
|
|
520
|
+
isDanger,
|
|
521
|
+
isBare,
|
|
522
|
+
isStretched,
|
|
523
|
+
disabled,
|
|
524
|
+
isEditable = false,
|
|
525
|
+
inputProps,
|
|
526
|
+
decrementIcon,
|
|
527
|
+
incrementIcon,
|
|
528
|
+
startIcon,
|
|
529
|
+
endIcon,
|
|
530
|
+
decrementButtonProps,
|
|
531
|
+
incrementButtonProps,
|
|
532
|
+
className,
|
|
533
|
+
...rest
|
|
534
|
+
}, ref) {
|
|
535
|
+
const isControlled = value !== void 0;
|
|
536
|
+
const [internalValue, setInternalValue] = useState2(defaultValue);
|
|
537
|
+
const currentValue = isControlled ? value : internalValue;
|
|
538
|
+
const clampedValue = useMemo2(() => {
|
|
539
|
+
const safe = Number.isFinite(currentValue) ? currentValue : defaultValue;
|
|
540
|
+
return Math.min(max, Math.max(min, safe));
|
|
541
|
+
}, [currentValue, defaultValue, max, min]);
|
|
542
|
+
const commitValue = useCallback2(
|
|
543
|
+
(next) => {
|
|
544
|
+
const clamped = Math.min(max, Math.max(min, next));
|
|
545
|
+
if (!isControlled) setInternalValue(clamped);
|
|
546
|
+
onChange == null ? void 0 : onChange(clamped);
|
|
547
|
+
},
|
|
548
|
+
[isControlled, max, min, onChange]
|
|
549
|
+
);
|
|
550
|
+
const decrementDisabled = !!disabled || !Number.isFinite(clampedValue) || clampedValue - step < min;
|
|
551
|
+
const incrementDisabled = !!disabled || !Number.isFinite(clampedValue) || clampedValue + step > max;
|
|
552
|
+
let variant = "primary";
|
|
553
|
+
if (disabled) {
|
|
554
|
+
variant = "disabled";
|
|
555
|
+
} else if (isDanger && isBare) {
|
|
556
|
+
variant = "dangerBare";
|
|
557
|
+
} else if (isDanger) {
|
|
558
|
+
variant = "danger";
|
|
559
|
+
} else if (isSecondary) {
|
|
560
|
+
variant = "secondary";
|
|
561
|
+
} else if (isBare) {
|
|
562
|
+
variant = "bare";
|
|
563
|
+
}
|
|
564
|
+
if (process.env.NODE_ENV !== "production") {
|
|
565
|
+
const count = [isSecondary, isDanger, isBare].filter(Boolean).length;
|
|
566
|
+
const hasAllowedCombo = isDanger && isBare && count === 2;
|
|
567
|
+
if (count > 1 && !hasAllowedCombo) {
|
|
568
|
+
console.warn(
|
|
569
|
+
"[ButtonCounter]: Multiple variant props passed. Using the highest priority only."
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
if (step <= 0) {
|
|
573
|
+
console.warn("[ButtonCounter]: step should be > 0.");
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const CONTROL_SIZES = {
|
|
577
|
+
small: {
|
|
578
|
+
button: "h-[36px] w-[36px] text-sm",
|
|
579
|
+
display: "h-[36px] min-w-[48px] px-12 text-sm"
|
|
580
|
+
},
|
|
581
|
+
medium: {
|
|
582
|
+
button: "h-[44px] w-[44px] text-base",
|
|
583
|
+
display: "h-[44px] min-w-[56px] px-12 text-base"
|
|
584
|
+
},
|
|
585
|
+
large: {
|
|
586
|
+
button: "h-[56px] w-[56px] text-lg",
|
|
587
|
+
display: "h-[56px] min-w-[72px] px-16 text-lg"
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
const defaultsButton = "flex justify-center items-center focus:shadow-focus focus-visible:shadow-focus outline-none transition-all duration-300 ease-in-out cursor-pointer";
|
|
591
|
+
const defaultsDisplay = "flex items-center justify-center text-bold tracking-normal";
|
|
592
|
+
const width = isStretched ? "w-full" : "w-fit-content";
|
|
593
|
+
const variants = {
|
|
594
|
+
primary: {
|
|
595
|
+
button: "border-2 border-border-primary-emphasis bg-bg-primary-base text-white hover:bg-border-focus hover:text-fg-focus hover:border-border-emphasis hover:shadow-md active:bg-border-focus active:bg-bg-focus-overlay active:text-fg-focus",
|
|
596
|
+
display: "border-y-2 border-x-2 border-border-primary-emphasis bg-bg-primary-base text-white"
|
|
597
|
+
},
|
|
598
|
+
secondary: {
|
|
599
|
+
button: "border-2 border-border-primary-base bg-bg-base text-fg-primary-emphasis hover:shadow-md hover:bg-bg-primary-subtle active:bg-bg-primary-subtle-90",
|
|
600
|
+
display: "border-y-2 border-x-2 border-border-primary-base bg-bg-base text-fg-primary-emphasis"
|
|
601
|
+
},
|
|
602
|
+
bare: {
|
|
603
|
+
button: "bg-bg-base text-fg-primary-emphasis hover:bg-bg-minimal active:bg-bg-minimal-5",
|
|
604
|
+
display: "bg-bg-base text-fg-primary-emphasis"
|
|
605
|
+
},
|
|
606
|
+
dangerBare: {
|
|
607
|
+
button: "bg-bg-base text-fg-danger-emphasis hover:bg-bg-danger-subtle active:bg-bg-danger-subtle",
|
|
608
|
+
display: "bg-bg-base text-fg-danger-emphasis"
|
|
609
|
+
},
|
|
610
|
+
danger: {
|
|
611
|
+
button: "bg-bg-danger-base border-2 border-border-danger-emphasis text-white hover:bg-bg-danger-base-5 active:bg-bg-danger-base-10",
|
|
612
|
+
display: "border-y-2 border-x-2 border-border-danger-emphasis bg-bg-danger-base text-white"
|
|
613
|
+
},
|
|
614
|
+
disabled: {
|
|
615
|
+
button: "border-2 border-border-subtle bg-bg-inactive text-fg-inactive hover:no-underline cursor-not-allowed",
|
|
616
|
+
display: "border-y-2 border-x-2 border-border-subtle bg-bg-inactive text-fg-inactive"
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
const sizeStyles = CONTROL_SIZES[size] ?? CONTROL_SIZES.medium;
|
|
620
|
+
const variantStyles = variants[variant] ?? variants.primary;
|
|
621
|
+
const leftButtonStyles = cx5(
|
|
622
|
+
defaultsButton,
|
|
623
|
+
sizeStyles.button,
|
|
624
|
+
"rounded-l-lg",
|
|
625
|
+
"relative z-10",
|
|
626
|
+
variantStyles.button,
|
|
627
|
+
variant !== "bare" && variant !== "dangerBare" ? "border-r-0" : null
|
|
628
|
+
);
|
|
629
|
+
const rightButtonStyles = cx5(
|
|
630
|
+
defaultsButton,
|
|
631
|
+
sizeStyles.button,
|
|
632
|
+
"rounded-r-lg",
|
|
633
|
+
variantStyles.button,
|
|
634
|
+
variant !== "bare" && variant !== "dangerBare" ? "border-l-0" : null
|
|
635
|
+
);
|
|
636
|
+
const displayStyles = cx5(
|
|
637
|
+
defaultsDisplay,
|
|
638
|
+
sizeStyles.display,
|
|
639
|
+
"relative z-0",
|
|
640
|
+
variantStyles.display,
|
|
641
|
+
isStretched ? "flex-1" : null
|
|
642
|
+
);
|
|
643
|
+
const effectiveDecrementIcon = decrementIcon ?? startIcon ?? "-";
|
|
644
|
+
const effectiveIncrementIcon = incrementIcon ?? endIcon ?? "+";
|
|
645
|
+
const [draftValue, setDraftValue] = useState2(String(clampedValue));
|
|
646
|
+
const [isEditing, setIsEditing] = useState2(false);
|
|
647
|
+
const inputRef = useRef2(null);
|
|
648
|
+
useEffect2(() => {
|
|
649
|
+
if (isEditing) return;
|
|
650
|
+
setDraftValue(String(clampedValue));
|
|
651
|
+
}, [clampedValue, isEditing]);
|
|
652
|
+
const commitDraft = useCallback2(() => {
|
|
653
|
+
const trimmed = String(draftValue).trim();
|
|
654
|
+
if (trimmed === "") {
|
|
655
|
+
setDraftValue(String(clampedValue));
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const next = Number(trimmed);
|
|
659
|
+
if (!Number.isFinite(next)) {
|
|
660
|
+
setDraftValue(String(clampedValue));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
commitValue(next);
|
|
664
|
+
}, [clampedValue, commitValue, draftValue]);
|
|
665
|
+
return /* @__PURE__ */ jsxs5(
|
|
666
|
+
"div",
|
|
667
|
+
{
|
|
668
|
+
ref,
|
|
669
|
+
className: cx5("inline-flex items-stretch", width, className),
|
|
670
|
+
...rest,
|
|
671
|
+
children: [
|
|
672
|
+
/* @__PURE__ */ jsx6(
|
|
673
|
+
"button",
|
|
674
|
+
{
|
|
675
|
+
...decrementButtonProps,
|
|
676
|
+
type: "button",
|
|
677
|
+
"aria-label": (decrementButtonProps == null ? void 0 : decrementButtonProps["aria-label"]) ?? "Decrease",
|
|
678
|
+
"aria-disabled": decrementDisabled,
|
|
679
|
+
disabled: decrementDisabled,
|
|
680
|
+
className: cx5(
|
|
681
|
+
leftButtonStyles,
|
|
682
|
+
decrementDisabled && variant !== "disabled" ? variants.disabled.button : null,
|
|
683
|
+
decrementButtonProps == null ? void 0 : decrementButtonProps.className
|
|
684
|
+
),
|
|
685
|
+
onClick: (event) => {
|
|
686
|
+
var _a;
|
|
687
|
+
(_a = decrementButtonProps == null ? void 0 : decrementButtonProps.onClick) == null ? void 0 : _a.call(decrementButtonProps, event);
|
|
688
|
+
if (event.defaultPrevented) return;
|
|
689
|
+
commitValue(clampedValue - step);
|
|
690
|
+
},
|
|
691
|
+
children: effectiveDecrementIcon
|
|
692
|
+
}
|
|
693
|
+
),
|
|
694
|
+
isEditable && children == null ? /* @__PURE__ */ jsx6(
|
|
695
|
+
"input",
|
|
696
|
+
{
|
|
697
|
+
...inputProps,
|
|
698
|
+
ref: inputRef,
|
|
699
|
+
type: "number",
|
|
700
|
+
inputMode: "numeric",
|
|
701
|
+
disabled: !!disabled,
|
|
702
|
+
"aria-label": (inputProps == null ? void 0 : inputProps["aria-label"]) ?? "Value",
|
|
703
|
+
className: cx5(
|
|
704
|
+
displayStyles,
|
|
705
|
+
"w-[44px] text-center outline-none bg-transparent text-fg-base",
|
|
706
|
+
"[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:m-0 [&::-webkit-inner-spin-button]:m-0",
|
|
707
|
+
"focus:shadow-focus focus-visible:shadow-focus focus:z-15 focus-within:z-15",
|
|
708
|
+
inputProps == null ? void 0 : inputProps.className
|
|
709
|
+
),
|
|
710
|
+
value: draftValue,
|
|
711
|
+
onChange: (event) => {
|
|
712
|
+
var _a;
|
|
713
|
+
(_a = inputProps == null ? void 0 : inputProps.onChange) == null ? void 0 : _a.call(inputProps, event);
|
|
714
|
+
if (event.defaultPrevented) return;
|
|
715
|
+
setDraftValue(event.target.value);
|
|
716
|
+
},
|
|
717
|
+
onFocus: (event) => {
|
|
718
|
+
var _a;
|
|
719
|
+
(_a = inputProps == null ? void 0 : inputProps.onFocus) == null ? void 0 : _a.call(inputProps, event);
|
|
720
|
+
if (event.defaultPrevented) return;
|
|
721
|
+
setIsEditing(true);
|
|
722
|
+
},
|
|
723
|
+
onBlur: (event) => {
|
|
724
|
+
var _a;
|
|
725
|
+
(_a = inputProps == null ? void 0 : inputProps.onBlur) == null ? void 0 : _a.call(inputProps, event);
|
|
726
|
+
if (event.defaultPrevented) return;
|
|
727
|
+
setIsEditing(false);
|
|
728
|
+
commitDraft();
|
|
729
|
+
},
|
|
730
|
+
onKeyDown: (event) => {
|
|
731
|
+
var _a;
|
|
732
|
+
(_a = inputProps == null ? void 0 : inputProps.onKeyDown) == null ? void 0 : _a.call(inputProps, event);
|
|
733
|
+
if (event.defaultPrevented) return;
|
|
734
|
+
if (event.key === "Enter") {
|
|
735
|
+
event.currentTarget.blur();
|
|
736
|
+
}
|
|
737
|
+
if (event.key === "Escape") {
|
|
738
|
+
setDraftValue(String(clampedValue));
|
|
739
|
+
event.currentTarget.blur();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
) : /* @__PURE__ */ jsx6("div", { className: cx5(displayStyles, "select-none"), children: children ?? clampedValue }),
|
|
744
|
+
/* @__PURE__ */ jsx6(
|
|
745
|
+
"button",
|
|
746
|
+
{
|
|
747
|
+
...incrementButtonProps,
|
|
748
|
+
type: "button",
|
|
749
|
+
"aria-label": (incrementButtonProps == null ? void 0 : incrementButtonProps["aria-label"]) ?? "Increase",
|
|
750
|
+
"aria-disabled": incrementDisabled,
|
|
751
|
+
disabled: incrementDisabled,
|
|
752
|
+
className: cx5(
|
|
753
|
+
rightButtonStyles,
|
|
754
|
+
incrementDisabled && variant !== "disabled" ? variants.disabled.button : null,
|
|
755
|
+
incrementButtonProps == null ? void 0 : incrementButtonProps.className
|
|
756
|
+
),
|
|
757
|
+
onClick: (event) => {
|
|
758
|
+
var _a;
|
|
759
|
+
(_a = incrementButtonProps == null ? void 0 : incrementButtonProps.onClick) == null ? void 0 : _a.call(incrementButtonProps, event);
|
|
760
|
+
if (event.defaultPrevented) return;
|
|
761
|
+
commitValue(clampedValue + step);
|
|
762
|
+
},
|
|
763
|
+
children: effectiveIncrementIcon
|
|
764
|
+
}
|
|
765
|
+
)
|
|
766
|
+
]
|
|
767
|
+
}
|
|
768
|
+
);
|
|
769
|
+
});
|
|
770
|
+
export {
|
|
771
|
+
Button,
|
|
772
|
+
ButtonCounter,
|
|
773
|
+
ButtonGroup,
|
|
774
|
+
ButtonGroupItem,
|
|
775
|
+
IconButton,
|
|
776
|
+
MenuButton
|
|
777
|
+
};
|