@dragunio/fe-libs 0.1.1 → 0.1.2

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 CHANGED
@@ -23,8 +23,14 @@ __export(index_exports, {
23
23
  Button: () => Button,
24
24
  ChevronDownIcon: () => ChevronDownIcon,
25
25
  CloseIcon: () => CloseIcon,
26
+ CurrencyInput: () => CurrencyInput,
27
+ DateTimePicker: () => DateTimePicker,
28
+ Drawer: () => Drawer,
29
+ ExpandableSection: () => ExpandableSection,
30
+ NumberInput: () => NumberInput,
26
31
  PlusIcon: () => PlusIcon,
27
32
  SearchIcon: () => SearchIcon,
33
+ Selection: () => Selection,
28
34
  Table: () => Table,
29
35
  TableBody: () => TableBody,
30
36
  TableCaption: () => TableCaption,
@@ -32,6 +38,9 @@ __export(index_exports, {
32
38
  TableHead: () => TableHead,
33
39
  TableHeader: () => TableHeader,
34
40
  TableRow: () => TableRow,
41
+ Toggle: () => Toggle,
42
+ Tooltip: () => Tooltip,
43
+ formatNumber: () => formatNumber,
35
44
  getIconSize: () => getIconSize
36
45
  });
37
46
  module.exports = __toCommonJS(index_exports);
@@ -46,45 +55,60 @@ var hierarchyFromVariant = {
46
55
  danger: "primary"
47
56
  };
48
57
  var getIconSize = (size) => size === "24" ? 16 : 20;
49
- var Button = (0, import_react.forwardRef)(function Button2({
50
- hierarchy,
51
- variant,
52
- size = "32",
53
- color = "normal",
54
- destructive,
55
- iconOnly = false,
56
- icon,
57
- prefix,
58
- suffix,
59
- content,
60
- loading = false,
61
- alignCenter = false,
62
- children,
63
- className,
64
- disabled,
65
- type = "button",
66
- ...props
67
- }, ref) {
68
- const resolvedHierarchy = hierarchy ?? hierarchyFromVariant[variant ?? "primary"];
69
- const resolvedDestructive = destructive || variant === "danger";
70
- const isDisabled = disabled || loading;
71
- const label = loading ? "Loading\u2026" : children ?? content;
72
- const classes = [
73
- "fe-button",
74
- `fe-button--${resolvedHierarchy}`,
75
- `fe-button--${size}`,
76
- `fe-button--${color}`,
77
- iconOnly && "fe-button--icon-only",
78
- resolvedDestructive && "fe-button--destructive",
79
- alignCenter && "fe-button--align-center",
80
- className
81
- ].filter(Boolean).join(" ");
82
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ref, className: classes, type, disabled: isDisabled, "aria-busy": loading || void 0, ...props, children: iconOnly ? icon : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
83
- prefix && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "fe-button__icon", children: prefix }),
84
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "fe-button__label", children: label }),
85
- suffix && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "fe-button__icon", children: suffix })
86
- ] }) });
87
- });
58
+ var Button = (0, import_react.forwardRef)(
59
+ function Button2({
60
+ hierarchy,
61
+ variant,
62
+ size = "32",
63
+ color = "normal",
64
+ destructive,
65
+ iconOnly = false,
66
+ icon,
67
+ prefix,
68
+ suffix,
69
+ content,
70
+ loading = false,
71
+ alignCenter = false,
72
+ children,
73
+ className,
74
+ disabled,
75
+ type = "button",
76
+ ...props
77
+ }, ref) {
78
+ const resolvedHierarchy = hierarchy ?? hierarchyFromVariant[variant ?? "primary"];
79
+ const resolvedDestructive = destructive || variant === "danger";
80
+ const isDisabled = disabled || loading;
81
+ const label = loading ? "Loading\u2026" : children ?? content;
82
+ const hasLabel = label !== null && label !== void 0 && label !== false && label !== "";
83
+ const resolvedIconOnly = iconOnly || icon !== null && icon !== void 0 && !hasLabel;
84
+ const classes = [
85
+ "fe-button",
86
+ `fe-button--${resolvedHierarchy}`,
87
+ `fe-button--${size}`,
88
+ `fe-button--${color}`,
89
+ resolvedIconOnly && "fe-button--icon-only",
90
+ resolvedDestructive && "fe-button--destructive",
91
+ alignCenter && "fe-button--align-center",
92
+ className
93
+ ].filter(Boolean).join(" ");
94
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
95
+ "button",
96
+ {
97
+ ref,
98
+ className: classes,
99
+ type,
100
+ disabled: isDisabled,
101
+ "aria-busy": loading || void 0,
102
+ ...props,
103
+ children: resolvedIconOnly ? icon : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
104
+ prefix && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "fe-button__icon", children: prefix }),
105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "fe-button__label", children: label }),
106
+ suffix && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "fe-button__icon", children: suffix })
107
+ ] })
108
+ }
109
+ );
110
+ }
111
+ );
88
112
  Button.displayName = "Button";
89
113
 
90
114
  // packages/icons/src/index.tsx
@@ -249,13 +273,382 @@ function TableHeader({ children, ...props }) {
249
273
  function TableCaption(props) {
250
274
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("caption", { ...props });
251
275
  }
276
+
277
+ // packages/toggle/src/index.tsx
278
+ var import_react3 = require("react");
279
+ var import_jsx_runtime4 = require("react/jsx-runtime");
280
+ var Toggle = (0, import_react3.forwardRef)(function Toggle2({ color = "#16a34a", label, className, style, id, ...props }, ref) {
281
+ const classes = ["fe-toggle", className].filter(Boolean).join(" ");
282
+ const toggleStyle = {
283
+ "--fe-toggle-color": color,
284
+ ...style
285
+ };
286
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: classes, style: toggleStyle, children: [
287
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { ref, id, className: "fe-toggle__input", type: "checkbox", role: "switch", ...props }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fe-toggle__control", "aria-hidden": "true" }),
289
+ label && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fe-toggle__label", children: label })
290
+ ] });
291
+ });
292
+ Toggle.displayName = "Toggle";
293
+
294
+ // packages/drawer/src/index.tsx
295
+ var import_react4 = require("react");
296
+ var import_react_dom = require("react-dom");
297
+ var import_jsx_runtime5 = require("react/jsx-runtime");
298
+ function Drawer({ open, onClose, title, footer, side = "right", width = 400, fullScreen = false, children, closeOnBackdropClick = true, className, style, ...props }) {
299
+ (0, import_react4.useEffect)(() => {
300
+ if (!open) return;
301
+ const onKeyDown = (event) => {
302
+ if (event.key === "Escape") onClose();
303
+ };
304
+ document.addEventListener("keydown", onKeyDown);
305
+ return () => document.removeEventListener("keydown", onKeyDown);
306
+ }, [onClose, open]);
307
+ if (!open || typeof document === "undefined") return null;
308
+ const panelStyle = !fullScreen && (side === "left" || side === "right") ? { ...style, width: typeof width === "number" ? `${width}px` : width } : style;
309
+ return (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "fe-drawer-backdrop", role: "presentation", onMouseDown: (event) => {
310
+ if (event.target === event.currentTarget && closeOnBackdropClick) onClose();
311
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("aside", { className: ["fe-drawer", `fe-drawer--${side}`, fullScreen && "fe-drawer--full-screen", className].filter(Boolean).join(" "), style: panelStyle, role: "dialog", "aria-modal": "true", "aria-label": typeof title === "string" ? title : "Drawer", ...props, children: [
312
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "fe-drawer__header", children: [
313
+ title && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { children: title }),
314
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", "aria-label": "Close drawer", onClick: onClose, children: "\xD7" })
315
+ ] }),
316
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "fe-drawer__body", children }),
317
+ footer && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("footer", { className: "fe-drawer__footer", children: footer })
318
+ ] }) }), document.body);
319
+ }
320
+
321
+ // packages/form-controls/src/index.tsx
322
+ var import_react5 = require("react");
323
+ var import_jsx_runtime6 = require("react/jsx-runtime");
324
+ var Selection = (0, import_react5.forwardRef)(function Selection2({ className, label, helperText, error, options, placeholder = "Select an option", size = "medium", id, value, defaultValue, onChange, multiple = false, searchable = false, tag = false, clearable = false, disabled = false, name, onKeyDown, ...props }, ref) {
325
+ const generatedId = (0, import_react5.useId)();
326
+ const listboxId = id ?? generatedId;
327
+ const isControlled = value !== void 0;
328
+ const [internalValue, setInternalValue] = (0, import_react5.useState)(defaultValue ?? (multiple ? [] : ""));
329
+ const selectedValue = isControlled ? value : internalValue;
330
+ const selectedValues = Array.isArray(selectedValue) ? selectedValue : selectedValue === "" || selectedValue === null || selectedValue === void 0 ? [] : [selectedValue];
331
+ const selectedOptions = options.filter((option) => selectedValues.some((item) => String(item) === String(option.value)));
332
+ const [open, setOpen] = (0, import_react5.useState)(false);
333
+ const [query, setQuery] = (0, import_react5.useState)("");
334
+ const [activeIndex, setActiveIndex] = (0, import_react5.useState)(0);
335
+ const rootRef = (0, import_react5.useRef)(null);
336
+ (0, import_react5.useEffect)(() => {
337
+ const close = (event) => {
338
+ if (!rootRef.current?.contains(event.target)) setOpen(false);
339
+ };
340
+ document.addEventListener("mousedown", close);
341
+ return () => document.removeEventListener("mousedown", close);
342
+ }, []);
343
+ const filteredOptions = options.filter((option) => !query || (option.searchText ?? (typeof option.label === "string" ? option.label : String(option.value))).toLowerCase().includes(query.toLowerCase()));
344
+ const commit = (next) => {
345
+ if (!isControlled) setInternalValue(next);
346
+ const selected = options.filter((option) => (Array.isArray(next) ? next : [next]).some((item) => String(item) === String(option.value)));
347
+ onChange?.(next, multiple ? selected : selected[0]);
348
+ };
349
+ const choose = (option) => {
350
+ if (option.disabled) return;
351
+ const isSelected = selectedValues.some((item) => String(item) === String(option.value));
352
+ if (multiple) commit(isSelected ? selectedValues.filter((item) => String(item) !== String(option.value)) : [...selectedValues, option.value]);
353
+ else {
354
+ commit(option.value);
355
+ setOpen(false);
356
+ setQuery("");
357
+ }
358
+ };
359
+ const clear = () => {
360
+ commit(multiple ? [] : "");
361
+ setQuery("");
362
+ };
363
+ const displayLabel = selectedOptions.length ? selectedOptions.map((option) => option.label) : null;
364
+ const onTriggerKeyDown = (event) => {
365
+ onKeyDown?.(event);
366
+ if (disabled) return;
367
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
368
+ event.preventDefault();
369
+ setOpen(true);
370
+ setActiveIndex((current) => Math.max(0, Math.min(filteredOptions.length - 1, current + (event.key === "ArrowDown" ? 1 : -1))));
371
+ }
372
+ if (event.key === "Enter" || event.key === " ") {
373
+ event.preventDefault();
374
+ if (!open) setOpen(true);
375
+ else if (filteredOptions[activeIndex]) choose(filteredOptions[activeIndex]);
376
+ }
377
+ if (event.key === "Escape") setOpen(false);
378
+ };
379
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref, className: ["fe-selection", `fe-selection--${size}`, open && "fe-selection--open", clearable && selectedOptions.length > 0 && "fe-selection--clearable", error && "fe-selection--error", disabled && "fe-selection--disabled", className].filter(Boolean).join(" "), ...props, children: [
380
+ label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-selection__label", children: label }),
381
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref: rootRef, className: "fe-selection__control", children: [
382
+ name && selectedValues.map((item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "hidden", name: multiple ? `${name}[]` : name, value: item }, String(item))),
383
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("button", { type: "button", className: "fe-selection__trigger", disabled, "aria-invalid": error || void 0, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": listboxId, onClick: () => setOpen((current) => !current), onKeyDown: onTriggerKeyDown, children: [
384
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: ["fe-selection__value", !selectedOptions.length && "is-placeholder", tag && selectedOptions.length > 1 && "is-tags"].filter(Boolean).join(" "), children: displayLabel ? tag && multiple ? selectedOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "fe-selection__tag", children: [
385
+ option.label,
386
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-hidden": "true", children: "\xD7" })
387
+ ] }, String(option.value))) : displayLabel.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
388
+ item,
389
+ index < displayLabel.length - 1 ? ", " : ""
390
+ ] }, index)) : placeholder }),
391
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-selection__actions", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-selection__chevron", "aria-hidden": "true" }) })
392
+ ] }),
393
+ clearable && selectedOptions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "fe-selection__clear", "aria-label": "Clear selection", onClick: clear }),
394
+ open && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { id: listboxId, className: "fe-selection__menu", role: "listbox", "aria-multiselectable": multiple || void 0, children: [
395
+ searchable && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "fe-selection__search", children: [
396
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-hidden": "true", children: "\u2315" }),
397
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { autoFocus: true, value: query, onChange: (event) => {
398
+ setQuery(event.target.value);
399
+ setActiveIndex(0);
400
+ }, placeholder: "Search\u2026", "aria-label": "Search options" })
401
+ ] }),
402
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "fe-selection__options", children: filteredOptions.length ? filteredOptions.map((option, index) => {
403
+ const isSelected = selectedValues.some((item) => String(item) === String(option.value));
404
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("button", { type: "button", className: [isSelected && "is-selected", index === activeIndex && "is-active"].filter(Boolean).join(" "), role: "option", "aria-selected": isSelected, disabled: option.disabled, onMouseMove: () => setActiveIndex(index), onClick: () => choose(option), children: [
405
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: option.label }),
406
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-selection__check", "aria-hidden": "true", children: "\u2713" })
407
+ ] }, String(option.value));
408
+ }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "fe-selection__empty", children: "No matching options" }) })
409
+ ] })
410
+ ] }),
411
+ helperText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-selection__helper", children: helperText })
412
+ ] });
413
+ });
414
+ Selection.displayName = "Selection";
415
+ function formatNumber(value, decimals = 2, thousandSeparator = ",", decimalSeparator = ".") {
416
+ return new Intl.NumberFormat("en-US", {
417
+ minimumFractionDigits: 0,
418
+ maximumFractionDigits: decimals,
419
+ useGrouping: true
420
+ }).format(value).replace(/,/g, thousandSeparator).replace(/\./g, decimalSeparator);
421
+ }
422
+ function escapeRegExp(value) {
423
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
424
+ }
425
+ function groupInteger(value, separator) {
426
+ return value.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
427
+ }
428
+ function normalizeNumberInput(raw, options) {
429
+ const { allowDecimal, decimalSeparator, thousandSeparator, min, max } = options;
430
+ const decimals = Math.max(0, options.decimals);
431
+ const negative = raw.trimStart().startsWith("-") && (min === void 0 || min < 0);
432
+ const decimalExpression = new RegExp(`[^0-9${allowDecimal ? escapeRegExp(decimalSeparator) : ""}]`, "g");
433
+ const cleaned = raw.replace(decimalExpression, "");
434
+ const decimalIndex = allowDecimal ? cleaned.indexOf(decimalSeparator) : -1;
435
+ const hasDecimal = decimalIndex >= 0;
436
+ const rawInteger = hasDecimal ? cleaned.slice(0, decimalIndex) : cleaned;
437
+ const rawFraction = hasDecimal ? cleaned.slice(decimalIndex + decimalSeparator.length).replace(new RegExp(escapeRegExp(decimalSeparator), "g"), "") : "";
438
+ const integer = rawInteger.replace(/^0+(?=\d)/, "") || (rawInteger ? "0" : "");
439
+ const fraction = rawFraction.slice(0, decimals);
440
+ if (!integer && !fraction) return { display: negative ? "-" : "", value: null };
441
+ const numericValue = Number(`${negative ? "-" : ""}${integer || "0"}${hasDecimal ? `.${fraction || "0"}` : ""}`);
442
+ if (!Number.isFinite(numericValue)) return { display: "", value: null };
443
+ const lowerBound = min === void 0 ? numericValue : Math.max(numericValue, min);
444
+ const value = max === void 0 ? lowerBound : Math.min(lowerBound, max);
445
+ if (value !== numericValue) return { display: formatNumber(value, decimals, thousandSeparator, decimalSeparator), value };
446
+ return { display: `${negative ? "-" : ""}${groupInteger(integer || "0", thousandSeparator)}${hasDecimal ? `${decimalSeparator}${fraction}` : ""}`, value };
447
+ }
448
+ var NumberInput = (0, import_react5.forwardRef)(function NumberInput2({ value, defaultValue = null, onValueChange, allowDecimal = true, decimals = 2, min, max, decimalSeparator = ".", thousandSeparator = ",", label, helperText, error, className, onBlur, onFocus, ...props }, ref) {
449
+ const isControlled = value !== void 0;
450
+ const [internalValue, setInternalValue] = (0, import_react5.useState)(defaultValue);
451
+ const [focused, setFocused] = (0, import_react5.useState)(false);
452
+ const actualValue = isControlled ? value ?? null : internalValue;
453
+ const [text, setText] = (0, import_react5.useState)(actualValue === null ? "" : formatNumber(actualValue, decimals, thousandSeparator, decimalSeparator));
454
+ (0, import_react5.useEffect)(() => {
455
+ if (!focused) setText(actualValue === null ? "" : formatNumber(actualValue, decimals, thousandSeparator, decimalSeparator));
456
+ }, [actualValue, decimalSeparator, decimals, focused, thousandSeparator]);
457
+ const updateValue = (nextText) => {
458
+ const next = normalizeNumberInput(nextText, { allowDecimal, decimals, decimalSeparator, thousandSeparator, min, max });
459
+ setText(next.display);
460
+ const nextValue = next.value;
461
+ if (!isControlled) setInternalValue(nextValue);
462
+ onValueChange?.(nextValue);
463
+ };
464
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: ["fe-number-input", error && "fe-number-input--error", className].filter(Boolean).join(" "), children: [
465
+ label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-number-input__label", children: label }),
466
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { ref, inputMode: allowDecimal ? "decimal" : "numeric", value: text, "aria-invalid": error || void 0, onFocus: (event) => {
467
+ setFocused(true);
468
+ onFocus?.(event);
469
+ }, onChange: (event) => updateValue(event.target.value), onBlur: (event) => {
470
+ setFocused(false);
471
+ onBlur?.(event);
472
+ }, ...props }),
473
+ helperText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-number-input__helper", children: helperText })
474
+ ] });
475
+ });
476
+ NumberInput.displayName = "NumberInput";
477
+ function CurrencyInput({ suffix = "VND", currencies, currency, defaultCurrency, onCurrencyChange, className, ...props }) {
478
+ const [internalCurrency, setInternalCurrency] = (0, import_react5.useState)(defaultCurrency ?? currencies?.[0]?.code ?? "");
479
+ const selectedCurrency = currency ?? internalCurrency;
480
+ const current = currencies?.find((item) => item.code === selectedCurrency);
481
+ const currencySuffix = current?.suffix ?? current?.code ?? suffix;
482
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: ["fe-currency-input", className].filter(Boolean).join(" "), children: [
483
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NumberInput, { ...props, className: "fe-currency-input__number" }),
484
+ currencies?.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { className: "fe-currency-input__currency", "aria-label": "Currency", value: selectedCurrency, onChange: (event) => {
485
+ const next = currencies.find((item) => item.code === event.target.value);
486
+ if (!next) return;
487
+ if (currency === void 0) setInternalCurrency(next.code);
488
+ onCurrencyChange?.(next);
489
+ }, children: currencies.map((item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: item.code, children: item.label ?? item.code }, item.code)) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-currency-input__suffix", children: currencySuffix })
490
+ ] });
491
+ }
492
+ var pad = (value) => String(value).padStart(2, "0");
493
+ function formatDate(date, format) {
494
+ return format.replace(/yyyy/g, String(date.getFullYear())).replace(/MM/g, pad(date.getMonth() + 1)).replace(/dd/g, pad(date.getDate())).replace(/HH/g, pad(date.getHours())).replace(/mm/g, pad(date.getMinutes()));
495
+ }
496
+ function startOfDay(date) {
497
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
498
+ }
499
+ function sameDay(first, second) {
500
+ return !!first && !!second && startOfDay(first).getTime() === startOfDay(second).getTime();
501
+ }
502
+ function mergeDateTime(day, time) {
503
+ const [hours, minutes] = time.split(":").map(Number);
504
+ const next = new Date(day);
505
+ next.setHours(hours || 0, minutes || 0, 0, 0);
506
+ return next;
507
+ }
508
+ function DateTimePicker(props) {
509
+ const { label, format = "dd/MM/yyyy HH:mm", disabled, placeholder, locale = "en-US", className, mode = "single" } = props;
510
+ const isRange = mode === "range";
511
+ const controlled = props.value !== void 0;
512
+ const [singleInternal, setSingleInternal] = (0, import_react5.useState)((!isRange ? props.defaultValue : null) ?? null);
513
+ const [rangeInternal, setRangeInternal] = (0, import_react5.useState)((isRange ? props.defaultValue : void 0) ?? [null, null]);
514
+ const singleValue = !isRange ? props.value ?? singleInternal : null;
515
+ const rangeValue = isRange ? props.value ?? rangeInternal : [null, null];
516
+ const [open, setOpen] = (0, import_react5.useState)(false);
517
+ const [month, setMonth] = (0, import_react5.useState)(() => new Date((isRange ? rangeValue[0] : singleValue) ?? /* @__PURE__ */ new Date()));
518
+ const [editingEnd, setEditingEnd] = (0, import_react5.useState)(false);
519
+ const rootRef = (0, import_react5.useRef)(null);
520
+ (0, import_react5.useEffect)(() => {
521
+ const onPointerDown = (event) => {
522
+ if (!rootRef.current?.contains(event.target)) setOpen(false);
523
+ };
524
+ document.addEventListener("mousedown", onPointerDown);
525
+ return () => document.removeEventListener("mousedown", onPointerDown);
526
+ }, []);
527
+ const setSingle = (next) => {
528
+ if (!controlled) setSingleInternal(next);
529
+ props.onChange?.(next);
530
+ };
531
+ const setRange = (next) => {
532
+ if (!controlled) setRangeInternal(next);
533
+ props.onChange?.(next);
534
+ };
535
+ const selectDay = (day) => {
536
+ if (!isRange) {
537
+ setSingle(mergeDateTime(day, singleValue ? `${pad(singleValue.getHours())}:${pad(singleValue.getMinutes())}` : "00:00"));
538
+ setOpen(false);
539
+ return;
540
+ }
541
+ const [start, end] = rangeValue;
542
+ if (!start || end || editingEnd) {
543
+ setRange([mergeDateTime(day, start ? `${pad(start.getHours())}:${pad(start.getMinutes())}` : "00:00"), null]);
544
+ setEditingEnd(false);
545
+ return;
546
+ }
547
+ const nextEnd = mergeDateTime(day, "00:00");
548
+ setRange(nextEnd < start ? [nextEnd, start] : [start, nextEnd]);
549
+ setEditingEnd(false);
550
+ };
551
+ const shownValue = isRange ? [rangeValue[0] && formatDate(rangeValue[0], format), rangeValue[1] && formatDate(rangeValue[1], format)].filter(Boolean).join(" \u2014 ") : singleValue && formatDate(singleValue, format);
552
+ const firstDay = new Date(month.getFullYear(), month.getMonth(), 1);
553
+ const offset = (firstDay.getDay() + 6) % 7;
554
+ const days = Array.from({ length: 42 }, (_, index) => new Date(month.getFullYear(), month.getMonth(), index - offset + 1));
555
+ const selectedForTime = isRange ? editingEnd ? rangeValue[1] : rangeValue[0] : singleValue;
556
+ const setTime = (time) => {
557
+ if (!selectedForTime) return;
558
+ const next = mergeDateTime(selectedForTime, time);
559
+ if (isRange) setRange(editingEnd ? [rangeValue[0], next] : [next, rangeValue[1]]);
560
+ else setSingle(next);
561
+ };
562
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref: rootRef, className: ["fe-datetime-picker", className].filter(Boolean).join(" "), children: [
563
+ label && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fe-datetime-picker__label", children: label }),
564
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("button", { type: "button", className: "fe-datetime-picker__trigger", disabled, "aria-expanded": open, onClick: () => setOpen((current) => !current), children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-hidden": "true", children: "\u25F7" }),
566
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: shownValue || placeholder || (isRange ? "Select date range" : "Select date and time") })
567
+ ] }),
568
+ open && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "fe-datetime-picker__popper", role: "dialog", "aria-label": "Date and time picker", children: [
569
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "fe-datetime-picker__month", children: [
570
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", "aria-label": "Previous month", onClick: () => setMonth((current) => new Date(current.getFullYear(), current.getMonth() - 1, 1)), children: "\u2039" }),
571
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: month.toLocaleDateString(locale, { month: "long", year: "numeric" }) }),
572
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", "aria-label": "Next month", onClick: () => setMonth((current) => new Date(current.getFullYear(), current.getMonth() + 1, 1)), children: "\u203A" })
573
+ ] }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "fe-datetime-picker__weekdays", children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((day) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: day }, day)) }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "fe-datetime-picker__days", children: days.map((day) => {
576
+ const inMonth = day.getMonth() === month.getMonth();
577
+ const selected = isRange ? sameDay(day, rangeValue[0]) || sameDay(day, rangeValue[1]) : sameDay(day, singleValue);
578
+ const inRange = isRange && rangeValue[0] && rangeValue[1] && day > startOfDay(rangeValue[0]) && day < startOfDay(rangeValue[1]);
579
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: [!inMonth && "is-other-month", selected && "is-selected", inRange && "is-in-range"].filter(Boolean).join(" "), onClick: () => selectDay(day), children: day.getDate() }, day.toISOString());
580
+ }) }),
581
+ isRange && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "fe-datetime-picker__range-tabs", children: [
582
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: !editingEnd ? "is-active" : "", onClick: () => setEditingEnd(false), children: "Start" }),
583
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: editingEnd ? "is-active" : "", onClick: () => setEditingEnd(true), children: "End" })
584
+ ] }),
585
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "fe-datetime-picker__time", children: [
586
+ "Time",
587
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "time", value: selectedForTime ? `${pad(selectedForTime.getHours())}:${pad(selectedForTime.getMinutes())}` : "00:00", onChange: (event) => setTime(event.target.value), disabled: !selectedForTime })
588
+ ] })
589
+ ] })
590
+ ] });
591
+ }
592
+
593
+ // packages/tooltip/src/index.tsx
594
+ var import_react6 = require("react");
595
+ var import_jsx_runtime7 = require("react/jsx-runtime");
596
+ function Tooltip({ content, children, placement = "top", disabled, className, ...props }) {
597
+ const [visible, setVisible] = (0, import_react6.useState)(false);
598
+ const id = (0, import_react6.useId)();
599
+ const trigger = (0, import_react6.cloneElement)(children, { "aria-describedby": visible ? id : void 0, onMouseEnter: (event) => {
600
+ children.props.onMouseEnter?.(event);
601
+ setVisible(true);
602
+ }, onMouseLeave: (event) => {
603
+ children.props.onMouseLeave?.(event);
604
+ setVisible(false);
605
+ }, onFocus: (event) => {
606
+ children.props.onFocus?.(event);
607
+ setVisible(true);
608
+ }, onBlur: (event) => {
609
+ children.props.onBlur?.(event);
610
+ setVisible(false);
611
+ } });
612
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: ["fe-tooltip", `fe-tooltip--${placement}`, className].filter(Boolean).join(" "), ...props, children: [
613
+ trigger,
614
+ visible && !disabled && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { id, role: "tooltip", className: "fe-tooltip__content", children: content })
615
+ ] });
616
+ }
617
+
618
+ // packages/expandable-section/src/index.tsx
619
+ var import_react7 = require("react");
620
+ var import_jsx_runtime8 = require("react/jsx-runtime");
621
+ function ExpandableSection({ title, children, expanded, defaultExpanded = false, onExpandedChange, disabled, className, ...props }) {
622
+ const [internalExpanded, setInternalExpanded] = (0, import_react7.useState)(defaultExpanded);
623
+ const isExpanded = expanded ?? internalExpanded;
624
+ const id = (0, import_react7.useId)();
625
+ const toggle = () => {
626
+ if (disabled) return;
627
+ const next = !isExpanded;
628
+ if (expanded === void 0) setInternalExpanded(next);
629
+ onExpandedChange?.(next);
630
+ };
631
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: ["fe-expandable-section", isExpanded && "fe-expandable-section--expanded", disabled && "fe-expandable-section--disabled", className].filter(Boolean).join(" "), ...props, children: [
632
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { className: "fe-expandable-section__trigger", type: "button", "aria-expanded": isExpanded, "aria-controls": id, disabled, onClick: toggle, children: [
633
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: title }),
634
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "fe-expandable-section__chevron", "aria-hidden": "true" })
635
+ ] }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { id, className: "fe-expandable-section__content", "aria-hidden": !isExpanded, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "fe-expandable-section__content-inner", children }) })
637
+ ] });
638
+ }
252
639
  // Annotate the CommonJS export names for ESM import in node:
253
640
  0 && (module.exports = {
254
641
  Button,
255
642
  ChevronDownIcon,
256
643
  CloseIcon,
644
+ CurrencyInput,
645
+ DateTimePicker,
646
+ Drawer,
647
+ ExpandableSection,
648
+ NumberInput,
257
649
  PlusIcon,
258
650
  SearchIcon,
651
+ Selection,
259
652
  Table,
260
653
  TableBody,
261
654
  TableCaption,
@@ -263,5 +656,8 @@ function TableCaption(props) {
263
656
  TableHead,
264
657
  TableHeader,
265
658
  TableRow,
659
+ Toggle,
660
+ Tooltip,
661
+ formatNumber,
266
662
  getIconSize
267
663
  });