@edux-design/buttons 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js DELETED
@@ -1,536 +0,0 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
-
29
- // src/index.js
30
- var index_exports = {};
31
- __export(index_exports, {
32
- Button: () => Button,
33
- ButtonGroup: () => ButtonGroup,
34
- ButtonGroupItem: () => ButtonGroupItem,
35
- IconButton: () => IconButton,
36
- MenuButton: () => MenuButton
37
- });
38
- module.exports = __toCommonJS(index_exports);
39
-
40
- // src/elements/IconButton.jsx
41
- var import_react = __toESM(require("react"));
42
- var import_utils = require("@edux-design/utils");
43
- var import_jsx_runtime = require("react/jsx-runtime");
44
- var IconButton = (0, import_react.forwardRef)(
45
- ({
46
- icon,
47
- size = "medium",
48
- isSecondary,
49
- isDanger,
50
- isBare,
51
- disabled,
52
- className,
53
- ...props
54
- }, ref) => {
55
- let variant = "primary";
56
- if (isBare) variant = "bare";
57
- if (isSecondary) variant = "secondary";
58
- if (isDanger) variant = "danger";
59
- if (disabled) variant = "disabled";
60
- if (process.env.NODE_ENV !== "production") {
61
- const count = [isSecondary, isDanger, isBare].filter(Boolean).length;
62
- if (count > 1) {
63
- console.warn(
64
- "[IconButton]: Multiple variant props passed. Using the highest priority only."
65
- );
66
- }
67
- }
68
- 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";
69
- const sizes = {
70
- small: "h-[36px] w-[36px] text-sm",
71
- medium: "h-[44px] w-[44px] text-base",
72
- large: "h-[56px] w-[56px] text-lg"
73
- };
74
- const variants = {
75
- primary: "border-2 border-border-primary-emphasis bg-bg-primary-base text-white hover:bg-border-focus hover:text-fg-base hover:border-border-emphasis hover:shadow-md active:bg-border-focus active:bg-bg-focus-overlay active:text-fg-base",
76
- 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",
77
- bare: "bg-bg-base text-fg-primary-emphasis hover:bg-bg-minimal active:bg-bg-minimal-5",
78
- 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",
79
- disabled: "border-2 border-border-subtle bg-bg-inactive text-fg-inactive hover:no-underline cursor-not-allowed"
80
- };
81
- const STYLES = (0, import_utils.cx)(defaults, sizes[size], variants[variant], className);
82
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
83
- "button",
84
- {
85
- role: "button",
86
- type: "button",
87
- "aria-disabled": disabled,
88
- disabled,
89
- className: STYLES,
90
- ref,
91
- ...props,
92
- children: icon
93
- }
94
- );
95
- }
96
- );
97
-
98
- // src/elements/Button.jsx
99
- var import_react2 = __toESM(require("react"));
100
- var import_utils2 = require("@edux-design/utils");
101
- var import_jsx_runtime2 = require("react/jsx-runtime");
102
- var Button = (0, import_react2.forwardRef)(
103
- ({
104
- children,
105
- startIcon,
106
- endIcon,
107
- size = "medium",
108
- isSecondary,
109
- isDanger,
110
- isBare,
111
- isStretched,
112
- disabled,
113
- className,
114
- ...props
115
- }, ref) => {
116
- let variant = "primary";
117
- if (disabled) {
118
- variant = "disabled";
119
- } else if (isDanger && isBare) {
120
- variant = "dangerBare";
121
- } else if (isDanger) {
122
- variant = "danger";
123
- } else if (isSecondary) {
124
- variant = "secondary";
125
- } else if (isBare) {
126
- variant = "bare";
127
- }
128
- if (process.env.NODE_ENV !== "production") {
129
- const count = [isSecondary, isDanger, isBare].filter(Boolean).length;
130
- const hasAllowedCombo = isDanger && isBare && count === 2;
131
- if (count > 1 && !hasAllowedCombo) {
132
- console.warn(
133
- "[Button]: Multiple variant props passed. Using the highest priority only."
134
- );
135
- }
136
- }
137
- 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";
138
- const fonts = "text-bold tracking-normal hover:underline hover:underline-offset-(--underline-offset-btn) active:underline active:underline-offset-(--underline-offset-btn)";
139
- const sizes = {
140
- small: "h-[36px] py-8 px-16 rounded-lg text-sm",
141
- medium: "h-[44px] py-8 px-16 rounded-lg text-base",
142
- large: "h-[56px] py-8 px-16 rounded-lg text-lg"
143
- };
144
- const width = isStretched ? "w-full" : "w-fit-content";
145
- const variants = {
146
- primary: "border-2 border-border-primary-emphasis bg-bg-primary-base text-white hover:bg-border-focus hover:text-fg-base hover:border-border-emphasis hover:shadow-md active:bg-border-focus active:bg-bg-focus-overlay active:text-fg-base",
147
- 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",
148
- bare: "bg-bg-base text-fg-primary-emphasis hover:bg-bg-minimal active:bg-bg-minimal-5",
149
- dangerBare: "bg-bg-base text-fg-danger-emphasis hover:bg-bg-danger-subtle active:bg-bg-danger-subtle",
150
- 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",
151
- disabled: "border-2 border-border-subtle bg-bg-inactive text-fg-inactive hover:no-underline"
152
- };
153
- const STYLES = (0, import_utils2.cx)(defaults, sizes[size], width, fonts, variants[variant]);
154
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
155
- "button",
156
- {
157
- role: "button",
158
- type: "button",
159
- "aria-disabled": disabled,
160
- disabled,
161
- className: STYLES,
162
- ref,
163
- ...props,
164
- children: [
165
- startIcon,
166
- children ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children }) : null,
167
- endIcon
168
- ]
169
- }
170
- );
171
- }
172
- );
173
-
174
- // src/elements/MenuButton.jsx
175
- var import_react4 = __toESM(require("react"));
176
-
177
- // src/util/MenuButtonContext.jsx
178
- var import_react3 = __toESM(require("react"));
179
- var MenuButtonContext = (0, import_react3.createContext)();
180
-
181
- // src/elements/MenuButton.jsx
182
- var import_utils3 = require("@edux-design/utils");
183
-
184
- // ../icons/dist/index.mjs
185
- var import_jsx_runtime3 = require("react/jsx-runtime");
186
- var SvgChevron = (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
187
- "svg",
188
- {
189
- xmlns: "http://www.w3.org/2000/svg",
190
- width: "1em",
191
- height: "1em",
192
- fill: "none",
193
- viewBox: "0 0 24 24",
194
- ...props,
195
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
196
- "path",
197
- {
198
- fill: "currentColor",
199
- fillRule: "evenodd",
200
- 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",
201
- clipRule: "evenodd"
202
- }
203
- )
204
- }
205
- );
206
- var Chevron_default = SvgChevron;
207
-
208
- // src/elements/MenuButton.jsx
209
- var import_jsx_runtime4 = require("react/jsx-runtime");
210
- function MenuButton({ children, size }) {
211
- const SPLIT_BUTTON_STYLES = (0, import_utils3.cx)(
212
- "w-[36px] border-l-[1px] border-l-black flex flex-row justify-center items-center",
213
- {
214
- "h-[32px]": size === "small",
215
- "h-[40px] py-2": size === "medium" || size === void 0
216
- }
217
- );
218
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(MenuButtonContext.Provider, { value: true, children: [
219
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: SPLIT_BUTTON_STYLES, children }),
220
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Chevron_default, {})
221
- ] });
222
- }
223
-
224
- // src/elements/ButtonGroup.jsx
225
- var import_react5 = __toESM(require("react"));
226
- var import_utils4 = require("@edux-design/utils");
227
- var import_jsx_runtime5 = require("react/jsx-runtime");
228
- var SIZE_STYLES = {
229
- small: {
230
- button: "text-xs px-12 py-6 rounded-md "
231
- },
232
- medium: {
233
- button: "text-sm px-12 py-6 rounded-md "
234
- },
235
- large: {
236
- button: "text-base px-12 py-6 rounded-md"
237
- }
238
- };
239
- var isBrowser = typeof window !== "undefined";
240
- var useIsomorphicLayoutEffect = isBrowser ? import_react5.useLayoutEffect : import_react5.useEffect;
241
- var ButtonGroup = (0, import_react5.forwardRef)(function ButtonGroup2({
242
- value,
243
- defaultValue,
244
- onChange,
245
- size = "medium",
246
- isStretched = false,
247
- className,
248
- children,
249
- ...rest
250
- }, forwardedRef) {
251
- const normalizedOptions = (0, import_react5.useMemo)(() => {
252
- const childItems = (0, import_utils4.filterChildrenByDisplayName)(children, "ButtonGroupItem");
253
- return childItems.map((child, index) => {
254
- const props = child.props || {};
255
- const hasValue = props.value !== void 0;
256
- const safeValue = hasValue && props.value !== null ? props.value : index;
257
- return {
258
- ...props,
259
- label: props.label ?? props.children ?? `Option ${index + 1}`,
260
- value: safeValue
261
- };
262
- });
263
- }, [children]);
264
- const sizeStyles = SIZE_STYLES[size] ?? SIZE_STYLES.medium;
265
- const initialValue = value ?? defaultValue ?? (normalizedOptions.length ? normalizedOptions[0].value : void 0);
266
- const [internalValue, setInternalValue] = (0, import_react5.useState)(initialValue);
267
- const isControlled = value !== void 0;
268
- const selectedValue = isControlled ? value : internalValue;
269
- (0, import_react5.useEffect)(() => {
270
- if (!isControlled && defaultValue !== void 0) {
271
- setInternalValue(defaultValue);
272
- }
273
- }, [defaultValue, isControlled]);
274
- (0, import_react5.useEffect)(() => {
275
- if (isControlled) return;
276
- if (normalizedOptions.length && !normalizedOptions.some((option) => option.value === internalValue)) {
277
- setInternalValue(normalizedOptions[0].value);
278
- }
279
- }, [internalValue, isControlled, normalizedOptions]);
280
- const buttonRefs = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
281
- const containerRef = (0, import_react5.useRef)(null);
282
- const indicatorRef = (0, import_react5.useRef)({ width: 0, height: 0, x: 0, y: 0 });
283
- const [indicator, setIndicator] = (0, import_react5.useState)({
284
- width: 0,
285
- height: 0,
286
- x: 0,
287
- y: 0
288
- });
289
- const setContainerRef = (0, import_react5.useCallback)(
290
- (node) => {
291
- containerRef.current = node;
292
- if (typeof forwardedRef === "function") {
293
- forwardedRef(node);
294
- } else if (forwardedRef) {
295
- forwardedRef.current = node;
296
- }
297
- },
298
- [forwardedRef]
299
- );
300
- const registerButtonRef = (0, import_react5.useCallback)((optionValue) => {
301
- return (node) => {
302
- if (!buttonRefs.current) return;
303
- if (node) {
304
- buttonRefs.current.set(optionValue, node);
305
- } else {
306
- buttonRefs.current.delete(optionValue);
307
- }
308
- };
309
- }, []);
310
- const updateIndicator = (0, import_react5.useCallback)(() => {
311
- const container = containerRef.current;
312
- const target = buttonRefs.current.get(selectedValue);
313
- if (!container || !target) {
314
- if (indicatorRef.current.width !== 0) {
315
- indicatorRef.current = { width: 0, height: 0, x: 0, y: 0 };
316
- setIndicator(indicatorRef.current);
317
- }
318
- return;
319
- }
320
- const containerRect = container.getBoundingClientRect();
321
- const targetRect = target.getBoundingClientRect();
322
- const next = {
323
- width: targetRect.width,
324
- height: targetRect.height,
325
- x: targetRect.left - containerRect.left,
326
- y: targetRect.top - containerRect.top
327
- };
328
- const prev = indicatorRef.current;
329
- if (prev.width !== next.width || prev.height !== next.height || prev.x !== next.x || prev.y !== next.y) {
330
- indicatorRef.current = next;
331
- setIndicator(next);
332
- }
333
- }, [selectedValue]);
334
- useIsomorphicLayoutEffect(() => {
335
- updateIndicator();
336
- }, [updateIndicator, normalizedOptions.length, size]);
337
- (0, import_react5.useEffect)(() => {
338
- if (!isBrowser) return;
339
- const handleResize = () => updateIndicator();
340
- window.addEventListener("resize", handleResize);
341
- return () => window.removeEventListener("resize", handleResize);
342
- }, [updateIndicator]);
343
- (0, import_react5.useEffect)(() => {
344
- if (!isBrowser) return;
345
- const node = containerRef.current;
346
- if (!node || typeof ResizeObserver === "undefined") return;
347
- const observer = new ResizeObserver(() => {
348
- updateIndicator();
349
- });
350
- observer.observe(node);
351
- return () => observer.disconnect();
352
- }, [updateIndicator]);
353
- const focusOption = (0, import_react5.useCallback)((option) => {
354
- const node = buttonRefs.current.get(option.value);
355
- if (node) {
356
- node.focus();
357
- }
358
- }, []);
359
- const handleSelect = (0, import_react5.useCallback)(
360
- (option) => {
361
- if (option.disabled || option.value === selectedValue) {
362
- return;
363
- }
364
- if (!isControlled) {
365
- setInternalValue(option.value);
366
- }
367
- if (typeof onChange === "function") {
368
- onChange(option.value, option);
369
- }
370
- },
371
- [isControlled, onChange, selectedValue]
372
- );
373
- const getNextEnabledIndex = (0, import_react5.useCallback)(
374
- (startIndex, direction) => {
375
- if (!normalizedOptions.length) return -1;
376
- let nextIndex = startIndex;
377
- for (let i = 0; i < normalizedOptions.length; i += 1) {
378
- nextIndex = (nextIndex + direction + normalizedOptions.length) % normalizedOptions.length;
379
- if (!normalizedOptions[nextIndex].disabled) {
380
- return nextIndex;
381
- }
382
- }
383
- return startIndex;
384
- },
385
- [normalizedOptions]
386
- );
387
- const findFirstEnabledIndex = (0, import_react5.useCallback)(() => {
388
- return normalizedOptions.findIndex((option) => !option.disabled);
389
- }, [normalizedOptions]);
390
- const findLastEnabledIndex = (0, import_react5.useCallback)(() => {
391
- for (let i = normalizedOptions.length - 1; i >= 0; i -= 1) {
392
- if (!normalizedOptions[i].disabled) {
393
- return i;
394
- }
395
- }
396
- return -1;
397
- }, [normalizedOptions]);
398
- const indicatorVisible = indicator.width > 0 && indicator.height > 0;
399
- const rootClasses = (0, import_utils4.cx)(
400
- "inline-flex h-[44px] rounded-lg border-2 border-border-subtle bg-bg-base/90 p-2 shadow-inner",
401
- isStretched ? "w-full" : "w-auto",
402
- className
403
- );
404
- const trackClasses = (0, import_utils4.cx)(
405
- "relative isolate flex w-full items-center gap-8",
406
- !isStretched && "w-auto"
407
- );
408
- const buttonBaseClasses = (0, import_utils4.cx)(
409
- "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",
410
- sizeStyles.button,
411
- isStretched ? "flex-1" : "w-auto"
412
- );
413
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
414
- "div",
415
- {
416
- ...rest,
417
- ref: setContainerRef,
418
- role: "radiogroup",
419
- className: rootClasses,
420
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: trackClasses, children: [
421
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
422
- "span",
423
- {
424
- "aria-hidden": "true",
425
- className: (0, import_utils4.cx)(
426
- "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",
427
- !indicatorVisible && "opacity-0"
428
- ),
429
- style: {
430
- width: indicator.width || void 0,
431
- height: indicator.height || void 0,
432
- transform: `translate(${indicator.x - 4}px`
433
- }
434
- }
435
- ),
436
- normalizedOptions.map((option, index) => {
437
- const isActive = option.value === selectedValue;
438
- const handleKeyDown = (event) => {
439
- if (option.disabled) return;
440
- if (event.key === "ArrowRight" || event.key === "ArrowDown") {
441
- event.preventDefault();
442
- const nextIndex = getNextEnabledIndex(index, 1);
443
- const nextOption = normalizedOptions[nextIndex];
444
- if (nextOption && nextOption !== option) {
445
- focusOption(nextOption);
446
- handleSelect(nextOption);
447
- }
448
- } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
449
- event.preventDefault();
450
- const prevIndex = getNextEnabledIndex(index, -1);
451
- const prevOption = normalizedOptions[prevIndex];
452
- if (prevOption && prevOption !== option) {
453
- focusOption(prevOption);
454
- handleSelect(prevOption);
455
- }
456
- } else if (event.key === "Home") {
457
- event.preventDefault();
458
- const firstIndex = findFirstEnabledIndex();
459
- const firstOption = firstIndex >= 0 ? normalizedOptions[firstIndex] : void 0;
460
- if (firstOption && firstOption !== option) {
461
- focusOption(firstOption);
462
- handleSelect(firstOption);
463
- }
464
- } else if (event.key === "End") {
465
- event.preventDefault();
466
- const lastIndex = findLastEnabledIndex();
467
- const lastOption = lastIndex >= 0 ? normalizedOptions[lastIndex] : void 0;
468
- if (lastOption && lastOption !== option) {
469
- focusOption(lastOption);
470
- handleSelect(lastOption);
471
- }
472
- }
473
- };
474
- const buttonClasses = (0, import_utils4.cx)(
475
- buttonBaseClasses,
476
- option.disabled ? "cursor-not-allowed text-fg-inactive opacity-60" : "cursor-pointer text-fg-subtle hover:text-fg-base",
477
- isActive && "text-fg-primary-emphasis",
478
- !isActive && "hover:bg-bg-minimal-5 active:bg-bg-inactive"
479
- );
480
- const hasStartAdornment = option.startAdornment !== void 0 && option.startAdornment !== null;
481
- const endAdornment = option.endAdornment ?? option.endIcon;
482
- const hasEndAdornment = endAdornment !== void 0 && endAdornment !== null;
483
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
484
- "button",
485
- {
486
- ref: registerButtonRef(option.value),
487
- type: "button",
488
- role: "radio",
489
- "aria-checked": isActive,
490
- disabled: option.disabled,
491
- className: buttonClasses,
492
- onClick: () => handleSelect(option),
493
- onKeyDown: handleKeyDown,
494
- "data-state": isActive ? "active" : "inactive",
495
- children: [
496
- hasStartAdornment && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
497
- "span",
498
- {
499
- "aria-hidden": "true",
500
- className: "flex items-center text-current",
501
- children: option.startAdornment
502
- }
503
- ),
504
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "flex items-center gap-1 leading-none text-current", children: option.label ?? `Option ${index + 1}` }),
505
- hasEndAdornment && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
506
- "span",
507
- {
508
- "aria-hidden": "true",
509
- className: "flex items-center text-current",
510
- children: endAdornment
511
- }
512
- )
513
- ]
514
- },
515
- String(option.value)
516
- );
517
- })
518
- ] })
519
- }
520
- );
521
- });
522
-
523
- // src/elements/ButtonGroupItem.jsx
524
- var import_react6 = __toESM(require("react"));
525
- function ButtonGroupItem(_props) {
526
- return null;
527
- }
528
- ButtonGroupItem.displayName = "ButtonGroupItem";
529
- // Annotate the CommonJS export names for ESM import in node:
530
- 0 && (module.exports = {
531
- Button,
532
- ButtonGroup,
533
- ButtonGroupItem,
534
- IconButton,
535
- MenuButton
536
- });