infinity-ui-elements 1.5.1-beta.0 → 1.5.1-beta.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/components/Avatar/Avatar.d.ts +59 -0
- package/dist/components/Avatar/Avatar.d.ts.map +1 -0
- package/dist/components/Avatar/Avatar.stories.d.ts +119 -0
- package/dist/components/Avatar/Avatar.stories.d.ts.map +1 -0
- package/dist/components/Avatar/index.d.ts +3 -0
- package/dist/components/Avatar/index.d.ts.map +1 -0
- package/dist/components/Badge/Badge.d.ts +1 -1
- package/dist/components/Button/Button.d.ts +1 -1
- package/dist/components/ButtonGroup/ButtonGroup.d.ts +26 -0
- package/dist/components/ButtonGroup/ButtonGroup.d.ts.map +1 -0
- package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts +102 -0
- package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts.map +1 -0
- package/dist/components/ButtonGroup/index.d.ts +3 -0
- package/dist/components/ButtonGroup/index.d.ts.map +1 -0
- package/dist/components/Checkbox/Checkbox.d.ts +1 -1
- package/dist/components/Counter/Counter.d.ts +1 -1
- package/dist/components/Dropdown/Dropdown.d.ts +1 -1
- package/dist/components/Dropdown/Dropdown.stories.d.ts +1 -1
- package/dist/components/Dropdown/DropdownMenu.d.ts +4 -0
- package/dist/components/Dropdown/DropdownMenu.d.ts.map +1 -1
- package/dist/components/FormHeader/FormHeader.d.ts.map +1 -1
- package/dist/components/Link/Link.d.ts +1 -1
- package/dist/components/Modal/Modal.d.ts +78 -0
- package/dist/components/Modal/Modal.d.ts.map +1 -0
- package/dist/components/Modal/Modal.stories.d.ts +20 -0
- package/dist/components/Modal/Modal.stories.d.ts.map +1 -0
- package/dist/components/Modal/index.d.ts +3 -0
- package/dist/components/Modal/index.d.ts.map +1 -0
- package/dist/components/Pagination/Pagination.d.ts +81 -0
- package/dist/components/Pagination/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination/Pagination.stories.d.ts +22 -0
- package/dist/components/Pagination/Pagination.stories.d.ts.map +1 -0
- package/dist/components/Pagination/index.d.ts +3 -0
- package/dist/components/Pagination/index.d.ts.map +1 -0
- package/dist/components/Radio/Radio.d.ts +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.d.ts +4 -0
- package/dist/components/SearchableDropdown/SearchableDropdown.d.ts.map +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.stories.d.ts +10 -9
- package/dist/components/SearchableDropdown/SearchableDropdown.stories.d.ts.map +1 -1
- package/dist/components/Select/Select.d.ts +148 -0
- package/dist/components/Select/Select.d.ts.map +1 -0
- package/dist/components/Select/Select.stories.d.ts +32 -0
- package/dist/components/Select/Select.stories.d.ts.map +1 -0
- package/dist/components/Select/index.d.ts +2 -0
- package/dist/components/Select/index.d.ts.map +1 -0
- package/dist/components/Switch/Switch.d.ts +1 -1
- package/dist/components/TabItem/TabItem.d.ts +1 -1
- package/dist/components/Table/DetailPanel.d.ts +10 -0
- package/dist/components/Table/DetailPanel.d.ts.map +1 -0
- package/dist/components/Table/Table.d.ts +39 -0
- package/dist/components/Table/Table.d.ts.map +1 -0
- package/dist/components/Table/Table.refactored.d.ts +39 -0
- package/dist/components/Table/Table.refactored.d.ts.map +1 -0
- package/dist/components/Table/Table.stories.d.ts +23 -0
- package/dist/components/Table/Table.stories.d.ts.map +1 -0
- package/dist/components/Table/TableBody.d.ts +18 -0
- package/dist/components/Table/TableBody.d.ts.map +1 -0
- package/dist/components/Table/TableCellTypes.d.ts +32 -0
- package/dist/components/Table/TableCellTypes.d.ts.map +1 -0
- package/dist/components/Table/TableDetailPanel.d.ts +25 -0
- package/dist/components/Table/TableDetailPanel.d.ts.map +1 -0
- package/dist/components/Table/TableHeader.d.ts +18 -0
- package/dist/components/Table/TableHeader.d.ts.map +1 -0
- package/dist/components/Table/index.d.ts +6 -0
- package/dist/components/Table/index.d.ts.map +1 -0
- package/dist/components/Table/tableHelpers.d.ts +7 -0
- package/dist/components/Table/tableHelpers.d.ts.map +1 -0
- package/dist/components/Table/tableVariants.d.ts +12 -0
- package/dist/components/Table/tableVariants.d.ts.map +1 -0
- package/dist/components/TextArea/TextArea.d.ts +1 -1
- package/dist/components/TextField/TextField.d.ts +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +893 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +910 -40
- package/dist/index.js.map +1 -1
- package/dist/lib/icons.d.ts +5 -1
- package/dist/lib/icons.d.ts.map +1 -1
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/package.json +6 -1
package/dist/index.esm.js
CHANGED
|
@@ -5,7 +5,9 @@ import { clsx } from 'clsx';
|
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
6
6
|
import { Slot } from '@radix-ui/react-slot';
|
|
7
7
|
import { PulseLoader, ClipLoader } from 'react-spinners';
|
|
8
|
-
import { ExternalLink, Loader2, Search } from 'lucide-react';
|
|
8
|
+
import { ExternalLink, Loader2, Search, X, ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
9
|
+
import { createPortal } from 'react-dom';
|
|
10
|
+
import { flexRender } from '@tanstack/react-table';
|
|
9
11
|
|
|
10
12
|
// Define patterns for custom classes that should be preserved
|
|
11
13
|
// This approach is more scalable than hardcoding individual class names
|
|
@@ -46,6 +48,106 @@ function cn(...inputs) {
|
|
|
46
48
|
return clsx(mergedStandard, customClasses);
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
// Helper function to get the text utility class name
|
|
52
|
+
function getTextClassName(variant = "body", size = "medium", weight = "regular", color = "default") {
|
|
53
|
+
// Build the base class name
|
|
54
|
+
let baseClass = `text-${variant}`;
|
|
55
|
+
// Add size
|
|
56
|
+
if (size) {
|
|
57
|
+
baseClass += `-${size}`;
|
|
58
|
+
}
|
|
59
|
+
// Add weight
|
|
60
|
+
if (weight) {
|
|
61
|
+
baseClass += `-${weight}`;
|
|
62
|
+
}
|
|
63
|
+
// Add color class separately
|
|
64
|
+
const colorClass = `text-color-${color}`;
|
|
65
|
+
return `${baseClass} ${colorClass}`;
|
|
66
|
+
}
|
|
67
|
+
const Text = React.forwardRef(({ className, variant = "body", size = "medium", weight = "regular", color = "default", as = "p", children, ...props }, ref) => {
|
|
68
|
+
const Component = as;
|
|
69
|
+
const textClass = getTextClassName(variant, size, weight, color);
|
|
70
|
+
return React.createElement(Component, {
|
|
71
|
+
className: cn(textClass, className),
|
|
72
|
+
ref,
|
|
73
|
+
...props,
|
|
74
|
+
}, children);
|
|
75
|
+
});
|
|
76
|
+
Text.displayName = "Text";
|
|
77
|
+
|
|
78
|
+
const avatarVariants = cva("inline-flex items-center justify-center font-medium text-center select-none", {
|
|
79
|
+
variants: {
|
|
80
|
+
color: {
|
|
81
|
+
a1: "bg-avatar-fill-a1-bg text-avatar-fill-a1-on-bg",
|
|
82
|
+
a2: "bg-avatar-fill-a2-bg text-avatar-fill-a2-on-bg",
|
|
83
|
+
a3: "bg-avatar-fill-a3-bg text-avatar-fill-a3-on-bg",
|
|
84
|
+
a4: "bg-avatar-fill-a4-bg text-avatar-fill-a4-on-bg",
|
|
85
|
+
a5: "bg-avatar-fill-a5-bg text-avatar-fill-a5-on-bg",
|
|
86
|
+
},
|
|
87
|
+
size: {
|
|
88
|
+
small: "h-[24px] w-[24px] text-body-medium-regular rounded-large",
|
|
89
|
+
medium: "h-[32px] w-[32px] text-body-medium-regular rounded-xlarge",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
defaultVariants: {
|
|
93
|
+
color: "a1",
|
|
94
|
+
size: "medium",
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
const statusVariants = cva("absolute flex items-center justify-center rounded-full border-2 border-surface-fill-neutral-intense", {
|
|
98
|
+
variants: {
|
|
99
|
+
size: {
|
|
100
|
+
small: "h-5 w-5 -bottom-0.5 -right-0.5",
|
|
101
|
+
medium: "h-6 w-6 -bottom-1 -right-1",
|
|
102
|
+
},
|
|
103
|
+
statusColor: {
|
|
104
|
+
positive: "bg-action-fill-positive-default",
|
|
105
|
+
negative: "bg-action-fill-negative-default",
|
|
106
|
+
notice: "bg-action-fill-notice-default",
|
|
107
|
+
info: "bg-action-fill-info-default",
|
|
108
|
+
neutral: "bg-action-fill-neutral-default",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
defaultVariants: {
|
|
112
|
+
size: "medium",
|
|
113
|
+
statusColor: "notice",
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
const Avatar = React.forwardRef(({ className, color, size, children, src, alt, showStatus = false, statusColor = "notice", statusIcon, label, trailingComponent, containerClassName, ...props }, ref) => {
|
|
117
|
+
const [imageError, setImageError] = React.useState(false);
|
|
118
|
+
const handleImageError = () => {
|
|
119
|
+
setImageError(true);
|
|
120
|
+
};
|
|
121
|
+
const getStatusIconSize = () => {
|
|
122
|
+
switch (size) {
|
|
123
|
+
case "small":
|
|
124
|
+
return "h-3.5 w-3.5";
|
|
125
|
+
case "medium":
|
|
126
|
+
return "h-4 w-4";
|
|
127
|
+
default:
|
|
128
|
+
return "h-4 w-4";
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const getTextSize = () => {
|
|
132
|
+
switch (size) {
|
|
133
|
+
case "small":
|
|
134
|
+
return "small";
|
|
135
|
+
case "medium":
|
|
136
|
+
return "medium";
|
|
137
|
+
default:
|
|
138
|
+
return "medium";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const avatarElement = (jsxs("div", { className: "relative inline-block", children: [jsx("div", { className: cn(avatarVariants({ color, size }), className), ...props, children: src && !imageError ? (jsx("img", { src: src, alt: alt || "Avatar", className: cn("h-full w-full object-cover", size === "small" ? "rounded-large" : "rounded-xlarge"), onError: handleImageError })) : (children) }), showStatus && (jsx("div", { className: cn(statusVariants({ size, statusColor })), children: statusIcon && (jsx("span", { className: cn("text-action-ink-on-primary-normal", getStatusIconSize()), children: statusIcon })) }))] }));
|
|
142
|
+
// If no label or trailing component, return just the avatar
|
|
143
|
+
if (!label && !trailingComponent) {
|
|
144
|
+
return jsx("div", { ref: ref, children: avatarElement });
|
|
145
|
+
}
|
|
146
|
+
// Otherwise, return avatar with label and/or trailing component
|
|
147
|
+
return (jsxs("div", { ref: ref, className: cn("inline-flex items-center gap-3", containerClassName), children: [avatarElement, label && (jsx(Text, { variant: "body", size: getTextSize(), weight: "medium", color: "default", as: "span", children: label })), trailingComponent && (jsx("span", { className: "ml-auto", children: trailingComponent }))] }));
|
|
148
|
+
});
|
|
149
|
+
Avatar.displayName = "Avatar";
|
|
150
|
+
|
|
49
151
|
const badgeVariants = cva("inline-flex items-center whitespace-nowrap transition-colors", {
|
|
50
152
|
variants: {
|
|
51
153
|
variant: {
|
|
@@ -462,32 +564,145 @@ const Button = React.forwardRef(({ className, variant = "primary", color = "prim
|
|
|
462
564
|
});
|
|
463
565
|
Button.displayName = "Button";
|
|
464
566
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
567
|
+
const buttonGroupVariants = cva("inline-flex", {
|
|
568
|
+
variants: {
|
|
569
|
+
variant: {
|
|
570
|
+
attached: "",
|
|
571
|
+
separated: "",
|
|
572
|
+
},
|
|
573
|
+
orientation: {
|
|
574
|
+
horizontal: "flex-row",
|
|
575
|
+
vertical: "flex-col",
|
|
576
|
+
},
|
|
577
|
+
size: {
|
|
578
|
+
xsmall: "",
|
|
579
|
+
small: "",
|
|
580
|
+
medium: "",
|
|
581
|
+
large: "",
|
|
582
|
+
},
|
|
583
|
+
isFullWidth: {
|
|
584
|
+
true: "w-full",
|
|
585
|
+
false: "w-fit",
|
|
586
|
+
},
|
|
587
|
+
isDisabled: {
|
|
588
|
+
true: "pointer-events-none opacity-50",
|
|
589
|
+
false: "",
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
compoundVariants: [
|
|
593
|
+
{
|
|
594
|
+
variant: "separated",
|
|
595
|
+
orientation: "horizontal",
|
|
596
|
+
class: "gap-2",
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
variant: "separated",
|
|
600
|
+
orientation: "vertical",
|
|
601
|
+
class: "gap-2",
|
|
602
|
+
},
|
|
603
|
+
],
|
|
604
|
+
defaultVariants: {
|
|
605
|
+
variant: "attached",
|
|
606
|
+
orientation: "horizontal",
|
|
607
|
+
size: "medium",
|
|
608
|
+
isFullWidth: false,
|
|
609
|
+
isDisabled: false,
|
|
610
|
+
},
|
|
489
611
|
});
|
|
490
|
-
|
|
612
|
+
const ButtonGroup = React.forwardRef(({ className, variant = "attached", orientation = "horizontal", size = "medium", isDisabled = false, isFullWidth = false, value, onChange, children, ...props }, ref) => {
|
|
613
|
+
const childrenArray = React.Children.toArray(children);
|
|
614
|
+
const isControlled = value !== undefined && onChange !== undefined;
|
|
615
|
+
return (jsx("div", { ref: ref, className: cn(buttonGroupVariants({
|
|
616
|
+
variant,
|
|
617
|
+
orientation,
|
|
618
|
+
size,
|
|
619
|
+
isDisabled,
|
|
620
|
+
isFullWidth,
|
|
621
|
+
}), className), role: "group", ...props, children: childrenArray.map((child, index) => {
|
|
622
|
+
if (!React.isValidElement(child)) {
|
|
623
|
+
return child;
|
|
624
|
+
}
|
|
625
|
+
const isFirst = index === 0;
|
|
626
|
+
const isLast = index === childrenArray.length - 1;
|
|
627
|
+
const isMiddle = !isFirst && !isLast;
|
|
628
|
+
// Get value from child props for controlled mode
|
|
629
|
+
const childValue = child.props.value;
|
|
630
|
+
const isSelected = isControlled && childValue === value;
|
|
631
|
+
// Build classes to apply border radius removal and borders
|
|
632
|
+
let groupClasses = "";
|
|
633
|
+
// Only apply connected styling for "attached" variant
|
|
634
|
+
if (variant === "attached") {
|
|
635
|
+
if (orientation === "horizontal") {
|
|
636
|
+
if (isFirst) {
|
|
637
|
+
groupClasses = "rounded-r-none border-r-0";
|
|
638
|
+
}
|
|
639
|
+
else if (isLast) {
|
|
640
|
+
groupClasses = "rounded-l-none";
|
|
641
|
+
}
|
|
642
|
+
else if (isMiddle) {
|
|
643
|
+
groupClasses = "rounded-none border-r-0";
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
// vertical
|
|
648
|
+
if (isFirst) {
|
|
649
|
+
groupClasses = "rounded-b-none border-b-0";
|
|
650
|
+
}
|
|
651
|
+
else if (isLast) {
|
|
652
|
+
groupClasses = "rounded-t-none";
|
|
653
|
+
}
|
|
654
|
+
else if (isMiddle) {
|
|
655
|
+
groupClasses = "rounded-none border-b-0";
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// Determine the variant to use
|
|
660
|
+
const childVariant = child.props.variant;
|
|
661
|
+
const hasExplicitVariant = childVariant !== undefined;
|
|
662
|
+
// For controlled mode with explicit variant, maintain the variant
|
|
663
|
+
// and add styling classes for selected state
|
|
664
|
+
let finalVariant = childVariant;
|
|
665
|
+
let selectedStateClasses = "";
|
|
666
|
+
if (isControlled) {
|
|
667
|
+
if (hasExplicitVariant) {
|
|
668
|
+
// Keep the child's variant and add selected state styling
|
|
669
|
+
if (isSelected && childVariant === "tertiary") {
|
|
670
|
+
selectedStateClasses = "bg-action-fill-primary-faded";
|
|
671
|
+
}
|
|
672
|
+
else if (isSelected && childVariant === "secondary") {
|
|
673
|
+
selectedStateClasses =
|
|
674
|
+
"bg-action-fill-primary-faded border-action-outline-primary-faded";
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
// No explicit variant: use primary for selected, secondary for unselected
|
|
679
|
+
finalVariant = isSelected ? "primary" : "secondary";
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
// Clone child and add our classes and handlers
|
|
683
|
+
return React.cloneElement(child, {
|
|
684
|
+
...child.props,
|
|
685
|
+
className: cn(child.props.className, groupClasses, isFullWidth && "flex-1",
|
|
686
|
+
// For attached variant, ensure proper layering
|
|
687
|
+
variant === "attached" && "relative", variant === "attached" && !isSelected && "hover:z-10 focus:z-10", variant === "attached" && isSelected && "z-20",
|
|
688
|
+
// Apply selected state classes for explicit variants
|
|
689
|
+
selectedStateClasses),
|
|
690
|
+
disabled: isDisabled || child.props.disabled,
|
|
691
|
+
size: size || child.props.size,
|
|
692
|
+
// If controlled and has value, handle click
|
|
693
|
+
onClick: isControlled && childValue !== undefined
|
|
694
|
+
? (e) => {
|
|
695
|
+
child.props.onClick?.(e);
|
|
696
|
+
if (!isDisabled && !child.props.disabled) {
|
|
697
|
+
onChange(childValue);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
: child.props.onClick,
|
|
701
|
+
variant: finalVariant,
|
|
702
|
+
});
|
|
703
|
+
}) }));
|
|
704
|
+
});
|
|
705
|
+
ButtonGroup.displayName = "ButtonGroup";
|
|
491
706
|
|
|
492
707
|
const FormFooter = React.forwardRef(({ helperText, trailingText, validationState = "default", size = "medium", isDisabled = false, className, helperTextClassName, trailingTextClassName, }, ref) => {
|
|
493
708
|
// Size-based configurations
|
|
@@ -562,6 +777,9 @@ const iconRegistry = {
|
|
|
562
777
|
// Alias: check points to the same icon as tick
|
|
563
778
|
check: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
564
779
|
<path d="M10.364 15.1924L19.5564 6L20.9706 7.41421L10.364 18.0208L4 11.6569L5.41422 10.2427L10.364 15.1924Z" fill="#081416"/>
|
|
780
|
+
</svg>`,
|
|
781
|
+
add: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
782
|
+
<path d="M12.9 11.0999L21 11.0997V12.8997L12.9 12.8999V21H11.1V12.8999L3.00004 12.9001L3 11.1001L11.1 11.0999L11.0999 3.00001L12.8999 3L12.9 11.0999Z" fill="#081416"/>
|
|
565
783
|
</svg>`,
|
|
566
784
|
};
|
|
567
785
|
const Icon = ({ name, size = 24, className = "", style = {}, ...props }) => {
|
|
@@ -579,17 +797,17 @@ const Icon = ({ name, size = 24, className = "", style = {}, ...props }) => {
|
|
|
579
797
|
console.error(`Invalid SVG content for icon "${String(name)}"`);
|
|
580
798
|
return null;
|
|
581
799
|
}
|
|
582
|
-
//
|
|
800
|
+
// Get the viewBox for proper scaling
|
|
583
801
|
const viewBox = svgElement.getAttribute("viewBox") || "0 0 24 24";
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
//
|
|
587
|
-
|
|
802
|
+
// Get all SVG content as string and replace hardcoded colors with currentColor
|
|
803
|
+
let innerSVG = svgElement.innerHTML;
|
|
804
|
+
// Replace common hardcoded colors with currentColor
|
|
805
|
+
// This allows the icon to inherit text color from parent
|
|
806
|
+
innerSVG = innerSVG
|
|
588
807
|
.replace(/fill="[^"]*"/g, 'fill="currentColor"')
|
|
589
808
|
.replace(/stroke="[^"]*"/g, 'stroke="currentColor"');
|
|
590
|
-
return (jsx("svg", { width: size, height: size, viewBox: viewBox,
|
|
809
|
+
return (jsx("svg", { width: size, height: size, viewBox: viewBox, className: className, style: style, xmlns: "http://www.w3.org/2000/svg", ...props, dangerouslySetInnerHTML: { __html: innerSVG } }));
|
|
591
810
|
};
|
|
592
|
-
Icon.displayName = "Icon";
|
|
593
811
|
/**
|
|
594
812
|
* Get all available icon names from the registry
|
|
595
813
|
* @returns Array of registered icon names
|
|
@@ -1203,7 +1421,7 @@ const Link = React.forwardRef(({ className, type = "anchor", color = "primary",
|
|
|
1203
1421
|
});
|
|
1204
1422
|
Link.displayName = "Link";
|
|
1205
1423
|
|
|
1206
|
-
const DropdownMenu = React.forwardRef(({ items = [], sectionHeading, isLoading = false, isEmpty = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText = "Primary", secondaryButtonText = "Secondary", onPrimaryClick, onSecondaryClick, showChevron = false, emptyIcon, disableFooter = false, onClose, focusedIndex = -1, className, width = "auto", }, ref) => {
|
|
1424
|
+
const DropdownMenu = React.forwardRef(({ items = [], sectionHeading, isLoading = false, isEmpty = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText = "Primary", secondaryButtonText = "Secondary", onPrimaryClick, onSecondaryClick, showChevron = false, emptyIcon, disableFooter = false, footerLayout = "horizontal", onClose, focusedIndex = -1, className, width = "auto", }, ref) => {
|
|
1207
1425
|
const renderContent = () => {
|
|
1208
1426
|
if (isLoading) {
|
|
1209
1427
|
return (jsx("div", { className: "flex flex-col items-center justify-center py-12 px-6", children: jsx(Loader2, { className: "w-12 h-12 text-action-ink-primary-normal mb-4 animate-spin" }) }));
|
|
@@ -1217,10 +1435,12 @@ const DropdownMenu = React.forwardRef(({ items = [], sectionHeading, isLoading =
|
|
|
1217
1435
|
}, containerClassName: cn(index === focusedIndex && "bg-action-fill-primary-faded") }, item.id))) })] }));
|
|
1218
1436
|
};
|
|
1219
1437
|
const widthClass = width === "full" ? "w-full" : width === "auto" ? "w-auto" : "";
|
|
1220
|
-
return (jsxs("div", { ref: ref, className: cn("bg-
|
|
1438
|
+
return (jsxs("div", { ref: ref, className: cn("bg-white rounded-large overflow-hidden", widthClass, className), style: {
|
|
1221
1439
|
boxShadow: "0 1px 2px rgba(25, 25, 30, 0.1), 0 2px 6px rgba(25, 25, 30, 0.06)",
|
|
1222
1440
|
...(width !== "full" && width !== "auto" ? { width } : {}),
|
|
1223
|
-
}, children: [renderContent(), !disableFooter && (jsxs("div", { className: "flex flex-col", children: [jsx(Divider, { thickness: "thin", variant: "muted" }), jsxs("div", { className: "flex
|
|
1441
|
+
}, children: [renderContent(), !disableFooter && (jsxs("div", { className: "flex flex-col", children: [jsx(Divider, { thickness: "thin", variant: "muted" }), jsxs("div", { className: cn("flex gap-3 p-4", footerLayout === "vertical"
|
|
1442
|
+
? "flex-col"
|
|
1443
|
+
: "items-center flex-row"), children: [jsx(Button, { variant: "secondary", color: "primary", size: "medium", isFullWidth: true, onClick: onSecondaryClick, children: secondaryButtonText }), jsx(Button, { variant: "primary", color: "primary", size: "medium", isFullWidth: true, onClick: onPrimaryClick, children: primaryButtonText })] })] }))] }));
|
|
1224
1444
|
});
|
|
1225
1445
|
DropdownMenu.displayName = "DropdownMenu";
|
|
1226
1446
|
|
|
@@ -1537,10 +1757,370 @@ const FormHeader = React.forwardRef(({ label, size = "medium", isOptional = fals
|
|
|
1537
1757
|
},
|
|
1538
1758
|
};
|
|
1539
1759
|
const config = sizeConfig[size];
|
|
1540
|
-
return (jsxs("div", { ref: ref, className: cn("flex items-center justify-between px-1", config.gap, className), children: [jsxs("div", { className: cn("flex items-center", config.gap), children: [jsxs("label", { htmlFor: htmlFor, className: cn("flex items-center", labelClassName), children: [jsx(Text, { as: "span", variant: "body", size: config.textSize, weight: "semibold", color: "subtle", children: label }), isRequired && (jsx(Text, { as: "span", variant: "body", size: config.textSize, weight: "semibold",
|
|
1760
|
+
return (jsxs("div", { ref: ref, className: cn("flex items-center justify-between px-1", config.gap, className), children: [jsxs("div", { className: cn("flex items-center", config.gap), children: [jsxs("label", { htmlFor: htmlFor, className: cn("flex items-center", labelClassName), children: [jsx(Text, { as: "span", variant: "body", size: config.textSize, weight: "semibold", color: "subtle", children: label }), isRequired && (jsx(Text, { as: "span", variant: "body", size: config.textSize, weight: "semibold", color: "negative", className: "ml-0.5", children: "*" })), isOptional && (jsx(Text, { as: "span", variant: "body", size: config.textSize, weight: "regular", className: "text-surface-ink-neutral-muted italic ml-1", children: "(optional)" }))] }), infoDescription && (jsx(Tooltip, { description: infoDescription, heading: infoHeading, children: jsxs("svg", { width: config.iconSize, height: config.iconSize, viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: "text-surface-ink-neutral-muted", children: [jsx("circle", { cx: "7", cy: "7", r: "6", stroke: "currentColor", strokeWidth: "1" }), jsx("path", { d: "M7 6V10M7 4.5V4", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round" })] }) }))] }), linkText && (jsx("a", { href: linkHref, onClick: onLinkClick, className: cn("text-surface-ink-primary-normal hover:text-surface-ink-primary-hover transition-colors cursor-pointer font-display font-semibold leading-tight shrink-0", size === "small" && "text-xs", size === "medium" && "text-xs", size === "large" && "text-sm", linkClassName), children: linkText }))] }));
|
|
1541
1761
|
});
|
|
1542
1762
|
FormHeader.displayName = "FormHeader";
|
|
1543
1763
|
|
|
1764
|
+
const Modal = React.forwardRef(({ isOpen, onClose, title, description, footer, children, size = "medium", showCloseButton = true, closeOnOverlayClick = true, closeOnEscape = true, className, contentClassName, headerClassName, bodyClassName, footerClassName, overlayClassName, ariaLabel, ariaDescribedBy, }, ref) => {
|
|
1765
|
+
const modalRef = React.useRef(null);
|
|
1766
|
+
const contentRef = ref || modalRef;
|
|
1767
|
+
// Size configurations
|
|
1768
|
+
const sizeConfig = {
|
|
1769
|
+
small: "max-w-sm",
|
|
1770
|
+
medium: "max-w-md",
|
|
1771
|
+
large: "max-w-lg",
|
|
1772
|
+
xlarge: "max-w-2xl",
|
|
1773
|
+
};
|
|
1774
|
+
// Handle escape key
|
|
1775
|
+
React.useEffect(() => {
|
|
1776
|
+
if (!isOpen || !closeOnEscape)
|
|
1777
|
+
return;
|
|
1778
|
+
const handleEscape = (e) => {
|
|
1779
|
+
if (e.key === "Escape") {
|
|
1780
|
+
onClose();
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
document.addEventListener("keydown", handleEscape);
|
|
1784
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
1785
|
+
}, [isOpen, closeOnEscape, onClose]);
|
|
1786
|
+
// Prevent body scroll when modal is open
|
|
1787
|
+
React.useEffect(() => {
|
|
1788
|
+
if (isOpen) {
|
|
1789
|
+
document.body.style.overflow = "hidden";
|
|
1790
|
+
}
|
|
1791
|
+
else {
|
|
1792
|
+
document.body.style.overflow = "";
|
|
1793
|
+
}
|
|
1794
|
+
return () => {
|
|
1795
|
+
document.body.style.overflow = "";
|
|
1796
|
+
};
|
|
1797
|
+
}, [isOpen]);
|
|
1798
|
+
// Handle overlay click
|
|
1799
|
+
const handleOverlayClick = (e) => {
|
|
1800
|
+
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
1801
|
+
onClose();
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
// Don't render if not open
|
|
1805
|
+
if (!isOpen)
|
|
1806
|
+
return null;
|
|
1807
|
+
const hasHeader = title || description;
|
|
1808
|
+
return (jsxs("div", { className: cn("fixed inset-0 z-50 flex items-center justify-center p-4", className), role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title, "aria-describedby": ariaDescribedBy, children: [jsx("div", { className: cn("absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity", overlayClassName), onClick: handleOverlayClick, "aria-hidden": "true" }), jsxs("div", { ref: contentRef, className: cn("relative w-full bg-white rounded-large shadow-xl transition-all", "flex flex-col max-h-[90vh]", sizeConfig[size], contentClassName), children: [hasHeader && (jsxs("div", { className: cn("flex items-start justify-between gap-4 px-6 pt-6", !description && "pb-4", description && "pb-2", headerClassName), children: [jsxs("div", { className: "flex-1", children: [title && (jsx(Text, { as: "h2", variant: "body", size: "large", weight: "semibold", color: "default", children: title })), description && (jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", color: "subtle", className: "mt-1", children: description }))] }), showCloseButton && (jsx("button", { type: "button", onClick: onClose, className: cn("shrink-0 rounded-medium p-1.5 transition-colors", "text-surface-ink-neutral-muted hover:text-surface-ink-neutral-default", "hover:bg-surface-fill-neutral-faded focus:outline-none focus:ring-2", "focus:ring-action-outline-primary-default focus:ring-offset-2"), "aria-label": "Close modal", children: jsx(X, { className: "h-5 w-5" }) }))] })), !hasHeader && showCloseButton && (jsx("div", { className: "absolute top-4 right-4 z-10", children: jsx("button", { type: "button", onClick: onClose, className: cn("shrink-0 rounded-medium p-1.5 transition-colors", "text-surface-ink-neutral-muted hover:text-surface-ink-neutral-default", "hover:bg-surface-fill-neutral-faded focus:outline-none focus:ring-2", "focus:ring-action-outline-primary-default focus:ring-offset-2"), "aria-label": "Close modal", children: jsx(X, { className: "h-5 w-5" }) }) })), jsx("div", { className: cn("flex-1 overflow-y-auto px-6", hasHeader ? "py-4" : "pt-6 pb-4", !footer && "pb-6", bodyClassName), children: children }), footer && (jsxs("div", { className: "flex flex-col", children: [jsx(Divider, { thickness: "thin", variant: "muted" }), jsx("div", { className: cn("flex items-center justify-end gap-3 px-6 py-4", footerClassName), children: footer })] }))] })] }));
|
|
1809
|
+
});
|
|
1810
|
+
Modal.displayName = "Modal";
|
|
1811
|
+
|
|
1812
|
+
const selectVariants = cva("relative flex items-center gap-2 border rounded-medium transition-all font-display font-size-100 leading-100", {
|
|
1813
|
+
variants: {
|
|
1814
|
+
size: {
|
|
1815
|
+
small: "h-[28px] px-3 text-xs gap-2",
|
|
1816
|
+
medium: "h-[36px] px-4 text-sm gap-2",
|
|
1817
|
+
large: "h-[44px] px-5 text-base gap-3",
|
|
1818
|
+
},
|
|
1819
|
+
validationState: {
|
|
1820
|
+
none: `
|
|
1821
|
+
border-action-outline-neutral-faded
|
|
1822
|
+
hover:border-action-outline-primary-hover
|
|
1823
|
+
focus-within:border-action-outline-primary-hover
|
|
1824
|
+
focus-within:ring-2
|
|
1825
|
+
ring-action-outline-primary-faded-hover`,
|
|
1826
|
+
positive: `
|
|
1827
|
+
border-action-outline-positive-default
|
|
1828
|
+
focus-within:border-action-outline-positive-hover
|
|
1829
|
+
focus-within:ring-2
|
|
1830
|
+
ring-action-outline-positive-faded-hover`,
|
|
1831
|
+
negative: `border-action-outline-negative-default
|
|
1832
|
+
focus-within:border-action-outline-negative-hover
|
|
1833
|
+
focus-within:ring-2
|
|
1834
|
+
ring-action-outline-negative-faded-hover`,
|
|
1835
|
+
},
|
|
1836
|
+
isDisabled: {
|
|
1837
|
+
true: `
|
|
1838
|
+
border-[var(--border-width-thinner)]
|
|
1839
|
+
hover:border-action-outline-neutral-disabled
|
|
1840
|
+
border-action-outline-neutral-disabled
|
|
1841
|
+
bg-surface-fill-neutral-intense cursor-not-allowed opacity-60`,
|
|
1842
|
+
false: "bg-surface-fill-neutral-intense",
|
|
1843
|
+
},
|
|
1844
|
+
},
|
|
1845
|
+
defaultVariants: {
|
|
1846
|
+
size: "medium",
|
|
1847
|
+
validationState: "none",
|
|
1848
|
+
isDisabled: false,
|
|
1849
|
+
},
|
|
1850
|
+
});
|
|
1851
|
+
const Select = React.forwardRef(({ className, options = [], value: controlledValue, defaultValue, onChange, placeholder = "Select an option", label, helperText, errorText, successText, validationState = "none", isDisabled = false, isRequired = false, isOptional = false, isLoading = false, size = "medium", prefix, suffix, showClearButton = false, onClear, containerClassName, labelClassName, triggerClassName, menuClassName, menuWidth = "full", sectionHeading, emptyTitle = "No options available", emptyDescription = "There are no options to select from.", emptyIcon, infoHeading, infoDescription, linkText, linkHref, onLinkClick, ...props }, ref) => {
|
|
1852
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
1853
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
1854
|
+
const selectRef = React.useRef(null);
|
|
1855
|
+
const value = controlledValue !== undefined ? controlledValue : uncontrolledValue;
|
|
1856
|
+
// Find the selected option
|
|
1857
|
+
const selectedOption = options.find((opt) => opt.value === value);
|
|
1858
|
+
const hasValue = value !== undefined && value !== "";
|
|
1859
|
+
// Determine which helper text to show
|
|
1860
|
+
const displayHelperText = errorText || successText || helperText;
|
|
1861
|
+
const currentValidationState = errorText
|
|
1862
|
+
? "negative"
|
|
1863
|
+
: successText
|
|
1864
|
+
? "positive"
|
|
1865
|
+
: validationState;
|
|
1866
|
+
const handleOpenChange = (newOpen) => {
|
|
1867
|
+
if (!isDisabled && !isLoading) {
|
|
1868
|
+
setIsOpen(newOpen);
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
const toggleOpen = () => {
|
|
1872
|
+
handleOpenChange(!isOpen);
|
|
1873
|
+
};
|
|
1874
|
+
const handleSelect = (option) => {
|
|
1875
|
+
if (controlledValue === undefined) {
|
|
1876
|
+
setUncontrolledValue(option.value);
|
|
1877
|
+
}
|
|
1878
|
+
onChange?.(option.value, option);
|
|
1879
|
+
setIsOpen(false);
|
|
1880
|
+
};
|
|
1881
|
+
const handleClear = (e) => {
|
|
1882
|
+
e.stopPropagation();
|
|
1883
|
+
if (onClear) {
|
|
1884
|
+
onClear();
|
|
1885
|
+
}
|
|
1886
|
+
else {
|
|
1887
|
+
if (controlledValue === undefined) {
|
|
1888
|
+
setUncontrolledValue(undefined);
|
|
1889
|
+
}
|
|
1890
|
+
onChange?.("", {});
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
// Close dropdown when clicking outside
|
|
1894
|
+
React.useEffect(() => {
|
|
1895
|
+
const handleClickOutside = (event) => {
|
|
1896
|
+
if (selectRef.current &&
|
|
1897
|
+
!selectRef.current.contains(event.target)) {
|
|
1898
|
+
handleOpenChange(false);
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
if (isOpen) {
|
|
1902
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1903
|
+
return () => {
|
|
1904
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
}, [isOpen]);
|
|
1908
|
+
// Close on escape key
|
|
1909
|
+
React.useEffect(() => {
|
|
1910
|
+
const handleEscape = (event) => {
|
|
1911
|
+
if (event.key === "Escape") {
|
|
1912
|
+
handleOpenChange(false);
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
if (isOpen) {
|
|
1916
|
+
document.addEventListener("keydown", handleEscape);
|
|
1917
|
+
return () => {
|
|
1918
|
+
document.removeEventListener("keydown", handleEscape);
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
}, [isOpen]);
|
|
1922
|
+
// Handle keyboard navigation
|
|
1923
|
+
React.useEffect(() => {
|
|
1924
|
+
const handleKeyDown = (event) => {
|
|
1925
|
+
if (isDisabled || isLoading)
|
|
1926
|
+
return;
|
|
1927
|
+
if (!isOpen && (event.key === "Enter" || event.key === " ")) {
|
|
1928
|
+
event.preventDefault();
|
|
1929
|
+
setIsOpen(true);
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
if (isOpen) {
|
|
1933
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
1934
|
+
event.preventDefault();
|
|
1935
|
+
const currentIndex = options.findIndex((opt) => opt.value === value);
|
|
1936
|
+
const nextIndex = event.key === "ArrowDown"
|
|
1937
|
+
? Math.min(currentIndex + 1, options.length - 1)
|
|
1938
|
+
: Math.max(currentIndex - 1, 0);
|
|
1939
|
+
if (options[nextIndex] && !options[nextIndex].isDisabled) {
|
|
1940
|
+
handleSelect(options[nextIndex]);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
if (selectRef.current) {
|
|
1946
|
+
selectRef.current.addEventListener("keydown", handleKeyDown);
|
|
1947
|
+
return () => {
|
|
1948
|
+
selectRef.current?.removeEventListener("keydown", handleKeyDown);
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
}, [isOpen, value, options, isDisabled, isLoading]);
|
|
1952
|
+
// Transform options to dropdown menu items
|
|
1953
|
+
const menuItems = options.map((option) => ({
|
|
1954
|
+
...option,
|
|
1955
|
+
onClick: () => handleSelect(option),
|
|
1956
|
+
}));
|
|
1957
|
+
const widthStyle = menuWidth === "full" ? "100%" : menuWidth === "auto" ? "auto" : menuWidth;
|
|
1958
|
+
const sizeConfig = {
|
|
1959
|
+
small: {
|
|
1960
|
+
gap: "gap-2",
|
|
1961
|
+
},
|
|
1962
|
+
medium: {
|
|
1963
|
+
gap: "gap-2",
|
|
1964
|
+
},
|
|
1965
|
+
large: {
|
|
1966
|
+
gap: "gap-3",
|
|
1967
|
+
},
|
|
1968
|
+
};
|
|
1969
|
+
return (jsxs("div", { className: cn("w-full flex flex-col", sizeConfig[size].gap, containerClassName), children: [label && (jsx(FormHeader, { label: label, size: size, isRequired: isRequired, isOptional: isOptional, infoHeading: infoHeading, infoDescription: infoDescription, linkText: linkText, linkHref: linkHref, onLinkClick: onLinkClick, htmlFor: props.id, className: "mb-2", labelClassName: labelClassName })), jsxs("div", { ref: selectRef, className: cn(selectVariants({
|
|
1970
|
+
size,
|
|
1971
|
+
validationState: currentValidationState,
|
|
1972
|
+
isDisabled,
|
|
1973
|
+
}), "relative w-full cursor-pointer", className), onClick: !isDisabled && !isLoading ? toggleOpen : undefined, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-disabled": isDisabled, ...props, children: [prefix && (jsx("span", { className: cn("shrink-0 flex items-center", isDisabled
|
|
1974
|
+
? "text-surface-ink-neutral-disabled"
|
|
1975
|
+
: currentValidationState === "positive"
|
|
1976
|
+
? "text-feedback-ink-positive-intense"
|
|
1977
|
+
: currentValidationState === "negative"
|
|
1978
|
+
? "text-feedback-ink-negative-subtle"
|
|
1979
|
+
: "text-surface-ink-neutral-muted"), children: prefix })), jsx("span", { className: cn("flex-1 text-left truncate", !selectedOption && "text-surface-ink-neutral-muted", isDisabled && "text-surface-ink-neutral-disabled"), children: isLoading
|
|
1980
|
+
? "Loading..."
|
|
1981
|
+
: selectedOption?.label || selectedOption?.title || placeholder }), showClearButton && hasValue && !isDisabled && !isLoading && (jsx("button", { type: "button", onClick: handleClear, className: "shrink-0 flex items-center justify-center text-surface-ink-neutral-muted hover:text-surface-ink-neutral-normal transition-colors", tabIndex: -1, children: jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: jsx("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) })), suffix && !showClearButton && (jsx("span", { className: cn("shrink-0 flex items-center", isDisabled
|
|
1982
|
+
? "text-surface-ink-neutral-disabled"
|
|
1983
|
+
: currentValidationState === "positive"
|
|
1984
|
+
? "text-feedback-ink-positive-intense"
|
|
1985
|
+
: currentValidationState === "negative"
|
|
1986
|
+
? "text-feedback-ink-negative-subtle"
|
|
1987
|
+
: "text-surface-ink-neutral-muted"), children: suffix })), jsx(ChevronDown, { className: cn("shrink-0 w-4 h-4 transition-transform", isDisabled
|
|
1988
|
+
? "text-surface-ink-neutral-disabled"
|
|
1989
|
+
: currentValidationState === "positive"
|
|
1990
|
+
? "text-feedback-ink-positive-intense"
|
|
1991
|
+
: currentValidationState === "negative"
|
|
1992
|
+
? "text-feedback-ink-negative-subtle"
|
|
1993
|
+
: "text-surface-ink-neutral-muted", isOpen && "transform rotate-180") }), isOpen && !isDisabled && !isLoading && (jsx("div", { className: "absolute z-50 left-0 right-0 top-full mt-1", children: jsx(DropdownMenu, { ref: ref, items: menuItems, sectionHeading: sectionHeading, isEmpty: options.length === 0, emptyTitle: emptyTitle, emptyDescription: emptyDescription, emptyIcon: emptyIcon, disableFooter: true, onClose: () => handleOpenChange(false), className: menuClassName, width: widthStyle }) }))] }), jsx(FormFooter, { helperText: displayHelperText, validationState: currentValidationState === "none"
|
|
1994
|
+
? "default"
|
|
1995
|
+
: currentValidationState, size: size, isDisabled: isDisabled, className: "mt-1" })] }));
|
|
1996
|
+
});
|
|
1997
|
+
Select.displayName = "Select";
|
|
1998
|
+
|
|
1999
|
+
const paginationVariants = cva("flex items-center gap-4 font-display text-body-medium-medium", {
|
|
2000
|
+
variants: {
|
|
2001
|
+
size: {
|
|
2002
|
+
small: "text-xs gap-2",
|
|
2003
|
+
medium: "text-sm gap-4",
|
|
2004
|
+
large: "text-base gap-5",
|
|
2005
|
+
},
|
|
2006
|
+
},
|
|
2007
|
+
defaultVariants: {
|
|
2008
|
+
size: "medium",
|
|
2009
|
+
},
|
|
2010
|
+
});
|
|
2011
|
+
const Pagination = React.forwardRef(({ className, currentPage: controlledCurrentPage, totalPages, rowsPerPage: controlledRowsPerPage, rowsPerPageOptions = [5, 10, 15, 20, 25, 50, 100], showRowsPerPage = true, onPageChange, onRowsPerPageChange, size = "medium", isDisabled = false, rowsPerPageLabel = "Row per page", ofLabel = "of", prevLabel = "Prev", nextLabel = "Next", showPrevNext = true, showPageJumper = true, showPageNumber = true, pageStatus, maxPageButtons = 5, ...props }, ref) => {
|
|
2012
|
+
const [uncontrolledCurrentPage, setUncontrolledCurrentPage] = React.useState(1);
|
|
2013
|
+
const [uncontrolledRowsPerPage, setUncontrolledRowsPerPage] = React.useState(rowsPerPageOptions[0] || 10);
|
|
2014
|
+
const currentPage = controlledCurrentPage !== undefined
|
|
2015
|
+
? controlledCurrentPage
|
|
2016
|
+
: uncontrolledCurrentPage;
|
|
2017
|
+
const rowsPerPage = controlledRowsPerPage !== undefined
|
|
2018
|
+
? controlledRowsPerPage
|
|
2019
|
+
: uncontrolledRowsPerPage;
|
|
2020
|
+
// Generate rows per page options
|
|
2021
|
+
const rowsPerPageSelectOptions = rowsPerPageOptions.map((value) => {
|
|
2022
|
+
const valueStr = String(value);
|
|
2023
|
+
// Pad with leading zero if needed (manual implementation for compatibility)
|
|
2024
|
+
const label = valueStr.length < 2 ? "0" + valueStr : valueStr;
|
|
2025
|
+
return {
|
|
2026
|
+
id: `rows-${valueStr}`,
|
|
2027
|
+
value: valueStr,
|
|
2028
|
+
label: label,
|
|
2029
|
+
title: valueStr,
|
|
2030
|
+
};
|
|
2031
|
+
});
|
|
2032
|
+
// Generate page options
|
|
2033
|
+
const pageOptions = Array.from({ length: totalPages }, (_, i) => {
|
|
2034
|
+
const pageNum = i + 1;
|
|
2035
|
+
const pageStr = String(pageNum);
|
|
2036
|
+
return {
|
|
2037
|
+
id: `page-${pageStr}`,
|
|
2038
|
+
value: pageStr,
|
|
2039
|
+
label: pageStr,
|
|
2040
|
+
title: `Page ${pageNum}`,
|
|
2041
|
+
};
|
|
2042
|
+
});
|
|
2043
|
+
const handlePageChange = (newPage) => {
|
|
2044
|
+
if (newPage < 1 || newPage > totalPages)
|
|
2045
|
+
return;
|
|
2046
|
+
if (controlledCurrentPage === undefined) {
|
|
2047
|
+
setUncontrolledCurrentPage(newPage);
|
|
2048
|
+
}
|
|
2049
|
+
onPageChange?.(newPage);
|
|
2050
|
+
};
|
|
2051
|
+
const handleRowsPerPageChange = (value) => {
|
|
2052
|
+
const newRowsPerPage = typeof value === "string" ? parseInt(value) : value;
|
|
2053
|
+
if (controlledRowsPerPage === undefined) {
|
|
2054
|
+
setUncontrolledRowsPerPage(newRowsPerPage);
|
|
2055
|
+
}
|
|
2056
|
+
onRowsPerPageChange?.(newRowsPerPage);
|
|
2057
|
+
// Reset to first page when rows per page changes
|
|
2058
|
+
handlePageChange(1);
|
|
2059
|
+
};
|
|
2060
|
+
const handlePrevPage = () => {
|
|
2061
|
+
handlePageChange(currentPage - 1);
|
|
2062
|
+
};
|
|
2063
|
+
const handleNextPage = () => {
|
|
2064
|
+
handlePageChange(currentPage + 1);
|
|
2065
|
+
};
|
|
2066
|
+
const isPrevDisabled = currentPage <= 1 || isDisabled;
|
|
2067
|
+
const isNextDisabled = currentPage >= totalPages || isDisabled;
|
|
2068
|
+
const selectSize = size === "small" ? "small" : size === "large" ? "large" : "medium";
|
|
2069
|
+
// Map pagination size to button size
|
|
2070
|
+
const buttonSize = size === "small" ? "xsmall" : size === "large" ? "large" : "small";
|
|
2071
|
+
// Generate page numbers to display
|
|
2072
|
+
const getPageNumbers = () => {
|
|
2073
|
+
if (totalPages <= maxPageButtons) {
|
|
2074
|
+
// Show all pages if total is less than max
|
|
2075
|
+
return Array.from({ length: totalPages }, (_, i) => i + 1);
|
|
2076
|
+
}
|
|
2077
|
+
// Determine page status if not provided
|
|
2078
|
+
const effectivePageStatus = pageStatus ||
|
|
2079
|
+
(currentPage <= 2
|
|
2080
|
+
? "first"
|
|
2081
|
+
: currentPage >= totalPages - 1
|
|
2082
|
+
? "last"
|
|
2083
|
+
: "middle");
|
|
2084
|
+
if (effectivePageStatus === "first") {
|
|
2085
|
+
// Show first pages: 1, 2, 3, 4, 5
|
|
2086
|
+
return Array.from({ length: Math.min(maxPageButtons, totalPages) }, (_, i) => i + 1);
|
|
2087
|
+
}
|
|
2088
|
+
else if (effectivePageStatus === "last") {
|
|
2089
|
+
// Show last pages
|
|
2090
|
+
const start = totalPages - maxPageButtons + 1;
|
|
2091
|
+
return Array.from({ length: maxPageButtons }, (_, i) => start + i);
|
|
2092
|
+
}
|
|
2093
|
+
else if (effectivePageStatus === "middle") {
|
|
2094
|
+
// Show pages around current page
|
|
2095
|
+
const halfMax = Math.floor(maxPageButtons / 2);
|
|
2096
|
+
let start = currentPage - halfMax;
|
|
2097
|
+
let end = currentPage + halfMax;
|
|
2098
|
+
// Adjust if at boundaries
|
|
2099
|
+
if (start < 1) {
|
|
2100
|
+
end += 1 - start;
|
|
2101
|
+
start = 1;
|
|
2102
|
+
}
|
|
2103
|
+
if (end > totalPages) {
|
|
2104
|
+
start -= end - totalPages;
|
|
2105
|
+
end = totalPages;
|
|
2106
|
+
}
|
|
2107
|
+
start = Math.max(1, start);
|
|
2108
|
+
end = Math.min(totalPages, end);
|
|
2109
|
+
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
|
2110
|
+
}
|
|
2111
|
+
// pageStatus === "none" - don't show any page numbers
|
|
2112
|
+
return [];
|
|
2113
|
+
};
|
|
2114
|
+
const pageNumbers = getPageNumbers();
|
|
2115
|
+
return (jsxs("div", { ref: ref, className: cn(paginationVariants({ size }), className), ...props, children: [showRowsPerPage && (jsxs("div", { className: "flex items-center gap-3", children: [jsx("span", { className: "text-surface-ink-neutral-muted whitespace-nowrap", children: rowsPerPageLabel }), jsx("div", { className: "w-[80px]", children: jsx(Select, { value: rowsPerPage.toString(), options: rowsPerPageSelectOptions, onChange: handleRowsPerPageChange, size: selectSize, isDisabled: isDisabled, menuWidth: "auto" }) })] })), jsxs("div", { className: "flex items-center gap-3 ml-auto", children: [showPrevNext && (jsx(Button, { variant: "tertiary", color: "neutral", size: buttonSize, onClick: handlePrevPage, isDisabled: isPrevDisabled, leadingIcon: jsx(ChevronLeft, { className: "w-4 h-4" }), "aria-label": "Previous page", children: prevLabel })), showPageJumper ? (
|
|
2116
|
+
// Show page dropdown selector
|
|
2117
|
+
jsxs("div", { className: "flex items-center gap-3", children: [jsx("div", { className: "w-[80px]", children: jsx(Select, { value: currentPage.toString(), options: pageOptions, onChange: (value) => handlePageChange(typeof value === "string" ? parseInt(value) : value), size: selectSize, isDisabled: isDisabled, menuWidth: "auto" }) }), jsxs("span", { className: "text-surface-ink-neutral-muted whitespace-nowrap", children: [ofLabel, " ", totalPages] })] })) : (
|
|
2118
|
+
// Show numbered page buttons
|
|
2119
|
+
showPageNumber &&
|
|
2120
|
+
pageNumbers.length > 0 && (jsx(ButtonGroup, { variant: "separated", size: buttonSize, isDisabled: isDisabled, value: currentPage, onChange: (value) => handlePageChange(value), children: pageNumbers.map((pageNum) => (jsx(Button, { value: pageNum, variant: "tertiary", color: "primary", "aria-label": `Page ${pageNum}`, "aria-current": pageNum === currentPage ? "page" : undefined, children: pageNum }, pageNum))) }))), showPrevNext && (jsx(Button, { variant: "tertiary", color: "neutral", size: buttonSize, onClick: handleNextPage, isDisabled: isNextDisabled, trailingIcon: jsx(ChevronRight, { className: "w-4 h-4" }), "aria-label": "Next page", children: nextLabel }))] })] }));
|
|
2121
|
+
});
|
|
2122
|
+
Pagination.displayName = "Pagination";
|
|
2123
|
+
|
|
1544
2124
|
const radioVariants = cva("relative inline-flex items-center justify-center shrink-0 border transition-all cursor-pointer rounded-full", {
|
|
1545
2125
|
variants: {
|
|
1546
2126
|
size: {
|
|
@@ -1815,13 +2395,30 @@ const defaultFilter = (item, query) => {
|
|
|
1815
2395
|
return (item.title.toLowerCase().includes(searchQuery) ||
|
|
1816
2396
|
(item.description?.toLowerCase().includes(searchQuery) ?? false));
|
|
1817
2397
|
};
|
|
1818
|
-
const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHeading, isLoading = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText = "Primary", secondaryButtonText = "Secondary", onPrimaryClick, onSecondaryClick, dropdownWidth = "full", showChevron = false, emptyIcon, disableFooter = false, onSearchChange, onItemSelect, filterFunction = defaultFilter, searchValue: controlledSearchValue, defaultSearchValue = "", dropdownClassName, minSearchLength = 0, showOnFocus = true, containerClassName, ...textFieldProps }, ref) => {
|
|
2398
|
+
const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHeading, isLoading = false, emptyTitle = "No Search Results Found", emptyDescription = "Add description of what the user can search for here.", emptyLinkText = "Link to support site", onEmptyLinkClick, primaryButtonText = "Primary", secondaryButtonText = "Secondary", onPrimaryClick, onSecondaryClick, dropdownWidth = "full", showChevron = false, emptyIcon, disableFooter = false, footerLayout = "horizontal", onSearchChange, onItemSelect, filterFunction = defaultFilter, searchValue: controlledSearchValue, defaultSearchValue = "", dropdownClassName, minSearchLength = 0, showOnFocus = true, containerClassName, ...textFieldProps }, ref) => {
|
|
1819
2399
|
const [uncontrolledSearchValue, setUncontrolledSearchValue] = React.useState(defaultSearchValue);
|
|
1820
2400
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
1821
2401
|
const [focusedIndex, setFocusedIndex] = React.useState(-1);
|
|
1822
2402
|
const dropdownRef = React.useRef(null);
|
|
1823
2403
|
const inputRef = React.useRef(null);
|
|
2404
|
+
const menuRef = React.useRef(null);
|
|
2405
|
+
const [position, setPosition] = React.useState({
|
|
2406
|
+
top: 0,
|
|
2407
|
+
left: 0,
|
|
2408
|
+
width: 0,
|
|
2409
|
+
});
|
|
1824
2410
|
React.useImperativeHandle(ref, () => inputRef.current);
|
|
2411
|
+
// Update position when dropdown opens or window resizes
|
|
2412
|
+
React.useEffect(() => {
|
|
2413
|
+
if (isOpen && dropdownRef.current) {
|
|
2414
|
+
const rect = dropdownRef.current.getBoundingClientRect();
|
|
2415
|
+
setPosition({
|
|
2416
|
+
top: rect.bottom + window.scrollY,
|
|
2417
|
+
left: rect.left + window.scrollX,
|
|
2418
|
+
width: rect.width,
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
}, [isOpen]);
|
|
1825
2422
|
const searchValue = controlledSearchValue !== undefined
|
|
1826
2423
|
? controlledSearchValue
|
|
1827
2424
|
: uncontrolledSearchValue;
|
|
@@ -1847,7 +2444,7 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
1847
2444
|
const handleItemSelect = (item) => {
|
|
1848
2445
|
onItemSelect?.(item);
|
|
1849
2446
|
if (controlledSearchValue === undefined) {
|
|
1850
|
-
setUncontrolledSearchValue(item.
|
|
2447
|
+
setUncontrolledSearchValue(item.title);
|
|
1851
2448
|
}
|
|
1852
2449
|
setIsOpen(false);
|
|
1853
2450
|
inputRef.current?.focus();
|
|
@@ -1862,7 +2459,9 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
1862
2459
|
React.useEffect(() => {
|
|
1863
2460
|
const handleClickOutside = (event) => {
|
|
1864
2461
|
if (dropdownRef.current &&
|
|
1865
|
-
!dropdownRef.current.contains(event.target)
|
|
2462
|
+
!dropdownRef.current.contains(event.target) &&
|
|
2463
|
+
menuRef.current &&
|
|
2464
|
+
!menuRef.current.contains(event.target)) {
|
|
1866
2465
|
setIsOpen(false);
|
|
1867
2466
|
}
|
|
1868
2467
|
};
|
|
@@ -1910,7 +2509,16 @@ const SearchableDropdown = React.forwardRef(({ className, items = [], sectionHea
|
|
|
1910
2509
|
onClick: () => handleItemSelect(item),
|
|
1911
2510
|
}));
|
|
1912
2511
|
const showDropdown = isOpen && searchValue.length >= minSearchLength;
|
|
1913
|
-
|
|
2512
|
+
const dropdownMenu = showDropdown && (jsx("div", { ref: menuRef, style: {
|
|
2513
|
+
position: "absolute",
|
|
2514
|
+
top: `${position.top + 8}px`,
|
|
2515
|
+
left: `${position.left}px`,
|
|
2516
|
+
width: dropdownWidth === "full" ? `${position.width}px` : "auto",
|
|
2517
|
+
zIndex: 9999,
|
|
2518
|
+
}, children: jsx(DropdownMenu, { items: itemsWithHandlers, sectionHeading: sectionHeading, isLoading: isLoading, isEmpty: filteredItems.length === 0, emptyTitle: emptyTitle, emptyDescription: emptyDescription, emptyLinkText: emptyLinkText, onEmptyLinkClick: onEmptyLinkClick, primaryButtonText: primaryButtonText, secondaryButtonText: secondaryButtonText, onPrimaryClick: onPrimaryClick, onSecondaryClick: onSecondaryClick, showChevron: showChevron, emptyIcon: emptyIcon, disableFooter: disableFooter, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: dropdownWidth === "full" ? "full" : "auto" }) }));
|
|
2519
|
+
return (jsxs(Fragment, { children: [jsx("div", { ref: dropdownRef, className: cn("relative", containerClassName), children: jsx(TextField, { ref: inputRef, value: searchValue, onChange: handleSearchChange, onFocus: handleFocus, onKeyDown: handleKeyDown, containerClassName: "mb-0", ...textFieldProps }) }), typeof document !== "undefined" &&
|
|
2520
|
+
dropdownMenu &&
|
|
2521
|
+
createPortal(dropdownMenu, document.body)] }));
|
|
1914
2522
|
});
|
|
1915
2523
|
SearchableDropdown.displayName = "SearchableDropdown";
|
|
1916
2524
|
|
|
@@ -2054,6 +2662,249 @@ const Switch = React.forwardRef(({ label, size = "medium", isDisabled = false, c
|
|
|
2054
2662
|
});
|
|
2055
2663
|
Switch.displayName = "Switch";
|
|
2056
2664
|
|
|
2665
|
+
const tableVariants = cva("w-full border-collapse", {
|
|
2666
|
+
variants: {
|
|
2667
|
+
variant: {
|
|
2668
|
+
default: "border-separate border-spacing-0",
|
|
2669
|
+
bordered: "border-separate border-spacing-0",
|
|
2670
|
+
striped: "border-separate border-spacing-0",
|
|
2671
|
+
},
|
|
2672
|
+
size: {
|
|
2673
|
+
small: "text-body-small-medium",
|
|
2674
|
+
medium: "text-body-medium-medium",
|
|
2675
|
+
large: "text-body-large-medium",
|
|
2676
|
+
},
|
|
2677
|
+
},
|
|
2678
|
+
defaultVariants: {
|
|
2679
|
+
variant: "default",
|
|
2680
|
+
size: "medium",
|
|
2681
|
+
},
|
|
2682
|
+
});
|
|
2683
|
+
const tableHeaderVariants = cva("text-body-medium-regular text-left text-surface-ink-neutral-normal border-b border-surface-outline-neutral-muted", {
|
|
2684
|
+
variants: {
|
|
2685
|
+
size: {
|
|
2686
|
+
small: "px-3 py-2 h-[32px]",
|
|
2687
|
+
medium: "px-4 py-3 h-[40px]",
|
|
2688
|
+
large: "px-6 py-4 h-[48px]",
|
|
2689
|
+
},
|
|
2690
|
+
},
|
|
2691
|
+
defaultVariants: {
|
|
2692
|
+
size: "medium",
|
|
2693
|
+
},
|
|
2694
|
+
});
|
|
2695
|
+
const tableCellVariants = cva("text-body-medium-regular border-b border-surface-outline-neutral-muted text-surface-ink-neutral-normal transition-colors duration-150", {
|
|
2696
|
+
variants: {
|
|
2697
|
+
size: {
|
|
2698
|
+
small: "px-3 py-2 h-[40px]",
|
|
2699
|
+
medium: "px-4 py-3 h-[72px]",
|
|
2700
|
+
large: "px-6 py-4 h-[56px]",
|
|
2701
|
+
},
|
|
2702
|
+
state: {
|
|
2703
|
+
default: "",
|
|
2704
|
+
focus: "",
|
|
2705
|
+
// focus:
|
|
2706
|
+
// "outline outline-2 outline-action-outline-primary-default outline-offset-[-2px]",
|
|
2707
|
+
},
|
|
2708
|
+
},
|
|
2709
|
+
defaultVariants: {
|
|
2710
|
+
size: "medium",
|
|
2711
|
+
state: "default",
|
|
2712
|
+
},
|
|
2713
|
+
});
|
|
2714
|
+
|
|
2715
|
+
function TableHeader({ headerGroups, enableRowSelection, enableSelectAll, showHeaderBackground, stickyHeader, size, headerClassName, isDetailPanelOpen, visibleHeadersCount, onToggleAllRows, isAllRowsSelected, isSomeRowsSelected, }) {
|
|
2716
|
+
return (jsx("thead", { className: cn(showHeaderBackground && "bg-surface-fill-neutral-moderate", stickyHeader && "sticky top-0 z-10"), children: headerGroups.map((headerGroup) => (jsxs("tr", { children: [enableRowSelection && enableSelectAll && (jsx("th", { className: cn(tableHeaderVariants({ size }), showHeaderBackground && "bg-surface-fill-neutral-moderate", "w-10 rounded-tl-xlarge rounded-bl-xlarge", headerClassName), children: jsx(Checkbox, { checked: isAllRowsSelected, isIndeterminate: isSomeRowsSelected, onChange: onToggleAllRows, "aria-label": "Select all rows" }) })), headerGroup.headers.map((header, index) => {
|
|
2717
|
+
const shouldHideColumn = isDetailPanelOpen && index >= visibleHeadersCount;
|
|
2718
|
+
const isLastVisibleColumn = isDetailPanelOpen
|
|
2719
|
+
? index === visibleHeadersCount - 1
|
|
2720
|
+
: index === headerGroup.headers.length - 1;
|
|
2721
|
+
return (jsx("th", { className: cn(tableHeaderVariants({ size }), showHeaderBackground &&
|
|
2722
|
+
"bg-surface-fill-neutral-moderate border-none", !enableRowSelection &&
|
|
2723
|
+
index === 0 &&
|
|
2724
|
+
"rounded-tl-xlarge rounded-bl-xlarge", isLastVisibleColumn && "rounded-tr-xlarge rounded-br-xlarge", header.column.columnDef.meta?.headerClassName, headerClassName, "transition-all duration-300 ease-in-out", shouldHideColumn
|
|
2725
|
+
? "opacity-0 translate-x-8 pointer-events-none"
|
|
2726
|
+
: "opacity-100 translate-x-0"), style: {
|
|
2727
|
+
width: header.getSize(),
|
|
2728
|
+
minWidth: header.column.columnDef.minSize,
|
|
2729
|
+
maxWidth: header.column.columnDef.maxSize,
|
|
2730
|
+
}, children: header.isPlaceholder ? null : (jsxs("div", { className: cn("flex items-center gap-2", header.column.getCanSort() && "cursor-pointer select-none"), onClick: header.column.getToggleSortingHandler(), children: [flexRender(header.column.columnDef.header, header.getContext()), header.column.getCanSort() && (jsx("span", { className: "text-surface-ink-neutral-muted", children: {
|
|
2731
|
+
asc: "↑",
|
|
2732
|
+
desc: "↓",
|
|
2733
|
+
}[header.column.getIsSorted()] ?? "↕" }))] })) }, header.id));
|
|
2734
|
+
})] }, headerGroup.id))) }));
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
function TableBody({ rows, enableRowSelection, size, variant, showRowHover, cellClassName, isDetailPanelOpen, visibleHeadersCount, effectiveSelectedRowId, onRowClick, getRowClassName, handleRowClick, }) {
|
|
2738
|
+
const [focusedCell, setFocusedCell] = React.useState(null);
|
|
2739
|
+
const [hoveredRow, setHoveredRow] = React.useState(null);
|
|
2740
|
+
return (jsx("tbody", { className: "bg-surface-fill-neutral-intense", children: rows.map((row) => {
|
|
2741
|
+
const isRowSelected = row.id === effectiveSelectedRowId;
|
|
2742
|
+
const isRowHovered = hoveredRow === row.id;
|
|
2743
|
+
const handleClick = () => handleRowClick(row.original, row.id);
|
|
2744
|
+
return (jsxs("tr", { className: cn(variant === "striped" &&
|
|
2745
|
+
row.index % 2 === 1 &&
|
|
2746
|
+
"bg-surface-fill-neutral-moderate", onRowClick && "cursor-pointer", isRowSelected && "bg-action-fill-primary-faded", isRowHovered &&
|
|
2747
|
+
showRowHover &&
|
|
2748
|
+
"bg-surface-fill-neutral-moderate", getRowClassName(row.original)), onClick: handleClick, onMouseEnter: () => setHoveredRow(row.id), onMouseLeave: () => setHoveredRow(null), children: [enableRowSelection && (jsx("td", { className: cn(tableCellVariants({ size }), "w-10", cellClassName), children: jsx(Checkbox, { checked: row.getIsSelected(), isIndeterminate: row.getIsSomeSelected(), onChange: row.getToggleSelectedHandler(), onClick: (e) => e.stopPropagation(), "aria-label": `Select row ${row.id}` }) })), row.getVisibleCells().map((cell, cellIndex) => {
|
|
2749
|
+
const shouldHideColumn = isDetailPanelOpen && cellIndex >= visibleHeadersCount;
|
|
2750
|
+
const isCellFocused = focusedCell?.rowId === row.id &&
|
|
2751
|
+
focusedCell?.cellId === cell.id;
|
|
2752
|
+
const cellState = isCellFocused ? "focus" : "default";
|
|
2753
|
+
return (jsx("td", { className: cn(tableCellVariants({ size, state: cellState }), cell.column.columnDef.meta?.cellClassName, cellClassName, "transition-all duration-300 ease-in-out", shouldHideColumn
|
|
2754
|
+
? "opacity-0 translate-x-8 pointer-events-none"
|
|
2755
|
+
: "opacity-100 translate-x-0"), style: {
|
|
2756
|
+
width: cell.column.getSize(),
|
|
2757
|
+
minWidth: cell.column.columnDef.minSize,
|
|
2758
|
+
maxWidth: cell.column.columnDef.maxSize,
|
|
2759
|
+
}, tabIndex: 0, onFocus: () => setFocusedCell({ rowId: row.id, cellId: cell.id }), onBlur: () => setFocusedCell(null), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
2760
|
+
})] }, row.id));
|
|
2761
|
+
}) }));
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
function DetailPanel({ isOpen, content, data, onClose, }) {
|
|
2765
|
+
return (jsx("div", { className: cn("absolute top-0 right-0 h-full z-20 transition-all duration-300 ease-in-out", isOpen
|
|
2766
|
+
? "translate-x-0 opacity-100"
|
|
2767
|
+
: "translate-x-full opacity-0 pointer-events-none"), style: { width: "332px", paddingLeft: "12px" }, children: jsx("div", { className: "w-full h-full bg-white border border-surface-outline-neutral-muted rounded-tr-xlarge rounded-br-xlarge overflow-hidden", children: jsx("div", { className: "w-full h-full overflow-auto", children: data && (jsxs("div", { className: "relative h-full", children: [jsx("button", { onClick: (e) => {
|
|
2768
|
+
e.stopPropagation();
|
|
2769
|
+
onClose();
|
|
2770
|
+
}, className: "absolute top-4 right-4 z-10 p-2 rounded-medium hover:bg-surface-fill-neutral-faded transition-colors", "aria-label": "Close detail panel", children: jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", className: "text-surface-ink-neutral-muted", children: jsx("path", { d: "M12 4L4 12M4 4l8 8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) }), content(data)] })) }) }) }));
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
function renderDefaultLoadingState({ colSpan }) {
|
|
2774
|
+
return (jsx("tr", { children: jsx("td", { colSpan: colSpan, className: "text-center py-12 text-surface-ink-neutral-muted", children: jsxs("div", { className: "flex items-center justify-center gap-2", children: [jsx("div", { className: "animate-spin rounded-full h-6 w-6 border-b-2 border-action-fill-primary-default" }), jsx("span", { children: "Loading..." })] }) }) }));
|
|
2775
|
+
}
|
|
2776
|
+
function renderDefaultEmptyState({ colSpan }) {
|
|
2777
|
+
return (jsx("tr", { children: jsx("td", { colSpan: colSpan, className: "text-center py-12 text-surface-ink-neutral-muted", children: "No data available" }) }));
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// ==================== Component ====================
|
|
2781
|
+
function TableComponent({ className, wrapperClassName, containerClassName, variant, size = "medium", table, enableRowSelection = false, enableSelectAll = false, isLoading = false, loadingComponent, emptyComponent, enableHorizontalScroll = false, stickyHeader = false, maxHeight, showRowHover = true, onRowClick, rowClassName, headerClassName, cellClassName, showHeaderBackground = true, detailPanel, hideColumnsOnDetailOpen = 3, selectedRowId, onRowSelectionChange, ...props }, ref) {
|
|
2782
|
+
// ==================== State ====================
|
|
2783
|
+
const [internalSelectedRowId, setInternalSelectedRowId] = React.useState(null);
|
|
2784
|
+
const selectedRowIdRef = React.useRef(null);
|
|
2785
|
+
const effectiveSelectedRowId = selectedRowId !== undefined ? selectedRowId : internalSelectedRowId;
|
|
2786
|
+
const isDetailPanelOpen = Boolean(effectiveSelectedRowId);
|
|
2787
|
+
// ==================== Effects ====================
|
|
2788
|
+
// Keep ref in sync
|
|
2789
|
+
React.useEffect(() => {
|
|
2790
|
+
selectedRowIdRef.current = effectiveSelectedRowId;
|
|
2791
|
+
}, [effectiveSelectedRowId]);
|
|
2792
|
+
// Clear selection if selected row is not in current data
|
|
2793
|
+
React.useEffect(() => {
|
|
2794
|
+
if (effectiveSelectedRowId) {
|
|
2795
|
+
const rowExists = table
|
|
2796
|
+
.getRowModel()
|
|
2797
|
+
.rows.some((r) => r.id === effectiveSelectedRowId);
|
|
2798
|
+
if (!rowExists) {
|
|
2799
|
+
if (selectedRowId === undefined) {
|
|
2800
|
+
setInternalSelectedRowId(null);
|
|
2801
|
+
}
|
|
2802
|
+
if (onRowSelectionChange) {
|
|
2803
|
+
onRowSelectionChange(null);
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
}, [
|
|
2808
|
+
table.getRowModel().rows,
|
|
2809
|
+
effectiveSelectedRowId,
|
|
2810
|
+
selectedRowId,
|
|
2811
|
+
onRowSelectionChange,
|
|
2812
|
+
]);
|
|
2813
|
+
// ==================== Computed Values ====================
|
|
2814
|
+
const hasData = table.getRowModel().rows?.length > 0;
|
|
2815
|
+
const headerGroups = table.getHeaderGroups();
|
|
2816
|
+
const headers = headerGroups[0]?.headers || [];
|
|
2817
|
+
const visibleHeadersCount = React.useMemo(() => {
|
|
2818
|
+
return isDetailPanelOpen
|
|
2819
|
+
? headers.length - hideColumnsOnDetailOpen
|
|
2820
|
+
: headers.length;
|
|
2821
|
+
}, [isDetailPanelOpen, headers.length, hideColumnsOnDetailOpen]);
|
|
2822
|
+
// ==================== Callbacks ====================
|
|
2823
|
+
const getRowClassName = React.useCallback((row) => {
|
|
2824
|
+
if (typeof rowClassName === "function") {
|
|
2825
|
+
return rowClassName(row);
|
|
2826
|
+
}
|
|
2827
|
+
return rowClassName || "";
|
|
2828
|
+
}, [rowClassName]);
|
|
2829
|
+
const handleRowClickInternal = React.useCallback((row, rowId) => {
|
|
2830
|
+
const currentSelectedId = selectedRowIdRef.current;
|
|
2831
|
+
const newSelectedId = currentSelectedId === rowId ? null : rowId;
|
|
2832
|
+
if (selectedRowId === undefined) {
|
|
2833
|
+
setInternalSelectedRowId(newSelectedId);
|
|
2834
|
+
}
|
|
2835
|
+
if (onRowSelectionChange) {
|
|
2836
|
+
onRowSelectionChange(newSelectedId);
|
|
2837
|
+
}
|
|
2838
|
+
if (onRowClick) {
|
|
2839
|
+
onRowClick(row);
|
|
2840
|
+
}
|
|
2841
|
+
}, [selectedRowId, onRowSelectionChange, onRowClick]);
|
|
2842
|
+
const getSelectedRowData = () => {
|
|
2843
|
+
if (!effectiveSelectedRowId)
|
|
2844
|
+
return null;
|
|
2845
|
+
const row = table
|
|
2846
|
+
.getRowModel()
|
|
2847
|
+
.rows.find((r) => r.id === effectiveSelectedRowId);
|
|
2848
|
+
return row ? row.original : null;
|
|
2849
|
+
};
|
|
2850
|
+
const handleDetailPanelClose = () => {
|
|
2851
|
+
if (selectedRowId === undefined) {
|
|
2852
|
+
setInternalSelectedRowId(null);
|
|
2853
|
+
}
|
|
2854
|
+
if (onRowSelectionChange) {
|
|
2855
|
+
onRowSelectionChange(null);
|
|
2856
|
+
}
|
|
2857
|
+
};
|
|
2858
|
+
// ==================== Render Helpers ====================
|
|
2859
|
+
const renderLoadingState = () => {
|
|
2860
|
+
if (loadingComponent)
|
|
2861
|
+
return loadingComponent;
|
|
2862
|
+
return renderDefaultLoadingState({ colSpan: table.getAllColumns().length });
|
|
2863
|
+
};
|
|
2864
|
+
const renderEmptyState = () => {
|
|
2865
|
+
if (emptyComponent)
|
|
2866
|
+
return emptyComponent;
|
|
2867
|
+
return renderDefaultEmptyState({ colSpan: table.getAllColumns().length });
|
|
2868
|
+
};
|
|
2869
|
+
// ==================== Render ====================
|
|
2870
|
+
return (jsx("div", { ref: ref, className: cn("w-full", wrapperClassName), ...props, children: jsxs("div", { className: cn("relative overflow-x-hidden", maxHeight && "overflow-y-auto", containerClassName), style: maxHeight ? { maxHeight } : undefined, children: [jsxs("table", { className: cn(tableVariants({ variant, size }), className), children: [jsx(TableHeader, { headerGroups: headerGroups, enableRowSelection: enableRowSelection, enableSelectAll: enableSelectAll, showHeaderBackground: showHeaderBackground, stickyHeader: stickyHeader, size: size || "medium", headerClassName: headerClassName, isDetailPanelOpen: isDetailPanelOpen, visibleHeadersCount: visibleHeadersCount, onToggleAllRows: (e) => table.getToggleAllRowsSelectedHandler()(e), isAllRowsSelected: table.getIsAllRowsSelected(), isSomeRowsSelected: table.getIsSomeRowsSelected() }), isLoading ? (jsx("tbody", { children: renderLoadingState() })) : !hasData ? (jsx("tbody", { children: renderEmptyState() })) : (jsx(TableBody, { rows: table.getRowModel().rows, enableRowSelection: enableRowSelection, size: size || "medium", variant: variant || "default", showRowHover: showRowHover, cellClassName: cellClassName, isDetailPanelOpen: isDetailPanelOpen, visibleHeadersCount: visibleHeadersCount, effectiveSelectedRowId: effectiveSelectedRowId, onRowClick: onRowClick, getRowClassName: getRowClassName, handleRowClick: handleRowClickInternal }))] }), detailPanel && (jsx(DetailPanel, { isOpen: isDetailPanelOpen, content: detailPanel, data: getSelectedRowData(), onClose: handleDetailPanelClose }))] }) }));
|
|
2871
|
+
}
|
|
2872
|
+
// ==================== Export ====================
|
|
2873
|
+
const Table = React.forwardRef(TableComponent);
|
|
2874
|
+
Table.displayName = "Table";
|
|
2875
|
+
|
|
2876
|
+
const TableDetailPanel = React.forwardRef(({ isOpen, onClose, children, className, width = "400px" }, ref) => {
|
|
2877
|
+
return (jsx("div", { ref: ref, className: cn("absolute top-0 right-0 h-full bg-white border border-surface-outline-neutral-muted transition-transform duration-300 ease-in-out overflow-auto", isOpen ? "translate-x-0" : "translate-x-full", className), style: { width }, children: children }));
|
|
2878
|
+
});
|
|
2879
|
+
TableDetailPanel.displayName = "TableDetailPanel";
|
|
2880
|
+
|
|
2881
|
+
function NumberCell({ value, currency, subtitle, className, }) {
|
|
2882
|
+
return (jsxs("div", { className: cn("flex flex-col", className), children: [jsxs("div", { className: "text-body-medium-medium text-surface-ink-neutral-normal", children: [currency, " ", value.toLocaleString("en-IN", {
|
|
2883
|
+
minimumFractionDigits: 2,
|
|
2884
|
+
maximumFractionDigits: 2,
|
|
2885
|
+
})] }), subtitle && (jsx("div", { className: "text-body-small-regular text-surface-ink-neutral-muted", children: subtitle }))] }));
|
|
2886
|
+
}
|
|
2887
|
+
function IconCell({ icon, background = "neutral", className, }) {
|
|
2888
|
+
const backgrounds = {
|
|
2889
|
+
neutral: "bg-surface-fill-neutral-faded",
|
|
2890
|
+
primary: "bg-action-fill-primary-faded",
|
|
2891
|
+
success: "bg-action-fill-positive-faded",
|
|
2892
|
+
warning: "bg-action-fill-warning-faded",
|
|
2893
|
+
danger: "bg-action-fill-negative-faded",
|
|
2894
|
+
};
|
|
2895
|
+
return (jsx("div", { className: cn("inline-flex items-center justify-center w-10 h-10 rounded-medium", backgrounds[background], className), children: icon }));
|
|
2896
|
+
}
|
|
2897
|
+
// ==================== Spacer Cell ====================
|
|
2898
|
+
function SpacerCell() {
|
|
2899
|
+
return jsx("div", { className: "w-full h-full" });
|
|
2900
|
+
}
|
|
2901
|
+
function SlotCell({ onDragStart, onDragEnd, className }) {
|
|
2902
|
+
return (jsx("div", { draggable: true, onDragStart: onDragStart, onDragEnd: onDragEnd, className: cn("flex items-center justify-center cursor-grab active:cursor-grabbing", className), children: jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", className: "text-surface-ink-neutral-muted", children: [jsx("path", { d: "M6 4C6 3.44772 5.55228 3 5 3C4.44772 3 4 3.44772 4 4C4 4.55228 4.44772 5 5 5C5.55228 5 6 4.55228 6 4Z", fill: "currentColor" }), jsx("path", { d: "M6 8C6 7.44772 5.55228 7 5 7C4.44772 7 4 7.44772 4 8C4 8.55228 4.44772 9 5 9C5.55228 9 6 8.55228 6 8Z", fill: "currentColor" }), jsx("path", { d: "M6 12C6 11.4477 5.55228 11 5 11C4.44772 11 4 11.4477 4 12C4 12.5523 4.44772 13 5 13C5.55228 13 6 12.5523 6 12Z", fill: "currentColor" }), jsx("path", { d: "M12 4C12 3.44772 11.5523 3 11 3C10.4477 3 10 3.44772 10 4C10 4.55228 10.4477 5 11 5C11.5523 5 12 4.55228 12 4Z", fill: "currentColor" }), jsx("path", { d: "M12 8C12 7.44772 11.5523 7 11 7C10.4477 7 10 7.44772 10 8C10 8.55228 10.4477 9 11 9C11.5523 9 12 8.55228 12 8Z", fill: "currentColor" }), jsx("path", { d: "M12 12C12 11.4477 11.5523 11 11 11C10.4477 11 10 11.4477 10 12C10 12.5523 10.4477 13 11 13C11.5523 13 12 12.5523 12 12Z", fill: "currentColor" })] }) }));
|
|
2903
|
+
}
|
|
2904
|
+
function AvatarCell({ name, initials, avatar, subtitle, color = "a1", className, }) {
|
|
2905
|
+
return (jsxs("div", { className: cn("flex items-center gap-3", className), children: [avatar ? (jsx("img", { src: avatar, alt: name, className: "w-10 h-10 rounded-full object-cover" })) : (jsx("div", { className: cn("w-10 h-10 rounded-full flex items-center justify-center text-body-medium-medium", `bg-${color}`), children: initials || name.charAt(0).toUpperCase() })), jsxs("div", { className: "flex flex-col", children: [jsx("div", { className: "text-body-medium-medium text-surface-ink-neutral-normal", children: name }), subtitle && (jsx("div", { className: "text-body-small-regular text-surface-ink-neutral-muted", children: subtitle }))] })] }));
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2057
2908
|
const tabItemVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 relative cursor-pointer", {
|
|
2058
2909
|
variants: {
|
|
2059
2910
|
variant: {
|
|
@@ -2256,5 +3107,5 @@ const TextArea = React.forwardRef(({ label, helperText, errorText, successText,
|
|
|
2256
3107
|
});
|
|
2257
3108
|
TextArea.displayName = "TextArea";
|
|
2258
3109
|
|
|
2259
|
-
export { Badge, Button, Checkbox, Counter, Divider, Dropdown, DropdownMenu, FormFooter, FormHeader, Icon, Link, ListItem, Radio, SearchableDropdown, Switch, TabItem, Tabs, Text, TextArea, TextField, Tooltip, badgeVariants, buttonVariants, checkboxVariants, cn, counterVariants, dropdownVariants, getAvailableIcons, hasIcon, iconRegistry, linkVariants, listItemVariants, radioVariants, switchVariants, textAreaVariants, textFieldVariants, tooltipVariants };
|
|
3110
|
+
export { Avatar, AvatarCell, Badge, Button, ButtonGroup, Checkbox, Counter, Divider, Dropdown, DropdownMenu, FormFooter, FormHeader, Icon, IconCell, Link, ListItem, Modal, NumberCell, Pagination, Radio, SearchableDropdown, Select, SlotCell, SpacerCell, Switch, TabItem, Table, TableDetailPanel, Tabs, Text, TextArea, TextField, Tooltip, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, checkboxVariants, cn, counterVariants, dropdownVariants, getAvailableIcons, hasIcon, iconRegistry, linkVariants, listItemVariants, paginationVariants, radioVariants, selectVariants, switchVariants, tableCellVariants, tableHeaderVariants, tableVariants, textAreaVariants, textFieldVariants, tooltipVariants };
|
|
2260
3111
|
//# sourceMappingURL=index.esm.js.map
|