@papernote/ui 1.13.0 → 1.14.1
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/FilterBar.d.ts +1 -1
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterPills.d.ts +14 -0
- package/dist/components/FilterPills.d.ts.map +1 -0
- package/dist/components/LetterNav.d.ts +8 -0
- package/dist/components/LetterNav.d.ts.map +1 -0
- package/dist/components/Pagination.d.ts +11 -1
- package/dist/components/Pagination.d.ts.map +1 -1
- package/dist/components/Select.d.ts +3 -3
- package/dist/components/Select.d.ts.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +38 -6
- package/dist/index.esm.js +446 -344
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +446 -342
- package/dist/index.js.map +1 -1
- package/dist/styles.css +4 -0
- package/package.json +1 -1
- package/src/components/FilterBar.tsx +116 -3
- package/src/components/FilterPills.tsx +58 -0
- package/src/components/LetterNav.tsx +67 -0
- package/src/components/Pagination.tsx +49 -1
- package/src/components/Select.tsx +367 -241
- package/src/components/index.ts +6 -0
package/dist/index.js
CHANGED
|
@@ -725,15 +725,15 @@ function usePrefersMobile() {
|
|
|
725
725
|
|
|
726
726
|
// Size classes for trigger button
|
|
727
727
|
const sizeClasses$d = {
|
|
728
|
-
sm:
|
|
729
|
-
md:
|
|
730
|
-
lg:
|
|
728
|
+
sm: "h-8 text-sm py-1",
|
|
729
|
+
md: "h-10 text-base py-2",
|
|
730
|
+
lg: "h-12 text-base py-3 min-h-touch", // 44px touch target
|
|
731
731
|
};
|
|
732
732
|
// Size classes for options
|
|
733
733
|
const optionSizeClasses = {
|
|
734
|
-
sm:
|
|
735
|
-
md:
|
|
736
|
-
lg:
|
|
734
|
+
sm: "py-2 text-sm",
|
|
735
|
+
md: "py-2.5 text-sm",
|
|
736
|
+
lg: "py-3.5 text-base min-h-touch", // 44px touch target for mobile
|
|
737
737
|
};
|
|
738
738
|
/**
|
|
739
739
|
* Select - Dropdown select component with search, groups, virtual scrolling, and mobile support
|
|
@@ -806,9 +806,9 @@ const optionSizeClasses = {
|
|
|
806
806
|
* ```
|
|
807
807
|
*/
|
|
808
808
|
const Select = React.forwardRef((props, ref) => {
|
|
809
|
-
const { options = [], groups = [], value, onChange, placeholder =
|
|
809
|
+
const { options = [], groups = [], value, onChange, placeholder = "Select an option", searchable = false, disabled = false, label, helperText, error, loading = false, clearable = false, creatable = false, onCreateOption, virtualized = false, virtualHeight = "300px", virtualItemHeight = 42, size = "md", mobileMode = "auto", usePortal = true, required = false, } = props;
|
|
810
810
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
811
|
-
const [searchQuery, setSearchQuery] = React.useState(
|
|
811
|
+
const [searchQuery, setSearchQuery] = React.useState("");
|
|
812
812
|
const [scrollTop, setScrollTop] = React.useState(0);
|
|
813
813
|
const [activeDescendant] = React.useState(undefined);
|
|
814
814
|
const [dropdownPosition, setDropdownPosition] = React.useState(null);
|
|
@@ -821,10 +821,10 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
821
821
|
const nativeSelectRef = React.useRef(null);
|
|
822
822
|
// Detect mobile viewport
|
|
823
823
|
const isMobile = useIsMobile();
|
|
824
|
-
const useMobileSheet = mobileMode ===
|
|
825
|
-
const useNativeSelect = mobileMode ===
|
|
824
|
+
const useMobileSheet = mobileMode === "auto" && isMobile;
|
|
825
|
+
const useNativeSelect = mobileMode === "native" && isMobile;
|
|
826
826
|
// Auto-size for mobile
|
|
827
|
-
const effectiveSize = isMobile && size ===
|
|
827
|
+
const effectiveSize = isMobile && size === "md" ? "lg" : size;
|
|
828
828
|
// Generate unique IDs for ARIA
|
|
829
829
|
const labelId = React.useId();
|
|
830
830
|
const listboxId = React.useId();
|
|
@@ -838,11 +838,8 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
838
838
|
close: () => setIsOpen(false),
|
|
839
839
|
}));
|
|
840
840
|
// Flatten all options (from both options and groups)
|
|
841
|
-
const allOptions = [
|
|
842
|
-
|
|
843
|
-
...groups.flatMap(group => group.options)
|
|
844
|
-
];
|
|
845
|
-
const selectedOption = allOptions.find(opt => opt.value === value);
|
|
841
|
+
const allOptions = [...options, ...groups.flatMap((group) => group.options)];
|
|
842
|
+
const selectedOption = allOptions.find((opt) => opt.value === value);
|
|
846
843
|
// Filter options/groups based on search
|
|
847
844
|
const getFilteredData = () => {
|
|
848
845
|
if (!searchable || !searchQuery) {
|
|
@@ -850,41 +847,58 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
850
847
|
}
|
|
851
848
|
const query = searchQuery.toLowerCase();
|
|
852
849
|
// Filter flat options
|
|
853
|
-
const filteredOptions = options.filter(opt => opt.label.toLowerCase().includes(query));
|
|
850
|
+
const filteredOptions = options.filter((opt) => opt.label.toLowerCase().includes(query));
|
|
854
851
|
// Filter grouped options
|
|
855
852
|
const filteredGroups = groups
|
|
856
|
-
.map(group => ({
|
|
853
|
+
.map((group) => ({
|
|
857
854
|
...group,
|
|
858
|
-
options: group.options.filter(opt => opt.label.toLowerCase().includes(query))
|
|
855
|
+
options: group.options.filter((opt) => opt.label.toLowerCase().includes(query)),
|
|
859
856
|
}))
|
|
860
|
-
.filter(group => group.options.length > 0);
|
|
857
|
+
.filter((group) => group.options.length > 0);
|
|
861
858
|
return { options: filteredOptions, groups: filteredGroups };
|
|
862
859
|
};
|
|
863
860
|
const { options: filteredOptions, groups: filteredGroups } = getFilteredData();
|
|
864
861
|
// Virtual scrolling calculations
|
|
865
|
-
const totalItems = filteredOptions.length + filteredGroups.flatMap(g => g.options).length;
|
|
862
|
+
const totalItems = filteredOptions.length + filteredGroups.flatMap((g) => g.options).length;
|
|
866
863
|
const useVirtualScrolling = virtualized && totalItems > 50;
|
|
867
864
|
const visibleRangeStart = useVirtualScrolling
|
|
868
865
|
? Math.floor(scrollTop / virtualItemHeight)
|
|
869
866
|
: 0;
|
|
870
867
|
const visibleRangeEnd = useVirtualScrolling
|
|
871
|
-
? Math.min(visibleRangeStart +
|
|
868
|
+
? Math.min(visibleRangeStart +
|
|
869
|
+
Math.ceil(parseInt(virtualHeight) / virtualItemHeight) +
|
|
870
|
+
5, totalItems)
|
|
872
871
|
: totalItems;
|
|
873
872
|
// Flatten all filtered items for virtualization
|
|
874
873
|
const allFilteredItems = [
|
|
875
|
-
...filteredOptions.map((opt, idx) => ({
|
|
876
|
-
|
|
874
|
+
...filteredOptions.map((opt, idx) => ({
|
|
875
|
+
type: "option",
|
|
876
|
+
option: opt,
|
|
877
|
+
groupIndex: -1,
|
|
878
|
+
optionIndex: idx,
|
|
879
|
+
})),
|
|
880
|
+
...filteredGroups.flatMap((group, groupIdx) => group.options.map((opt, optIdx) => ({
|
|
881
|
+
type: "grouped",
|
|
882
|
+
option: opt,
|
|
883
|
+
groupIndex: groupIdx,
|
|
884
|
+
optionIndex: optIdx,
|
|
885
|
+
groupLabel: group.label,
|
|
886
|
+
}))),
|
|
877
887
|
];
|
|
878
888
|
const visibleItems = useVirtualScrolling
|
|
879
889
|
? allFilteredItems.slice(visibleRangeStart, visibleRangeEnd)
|
|
880
890
|
: allFilteredItems;
|
|
881
|
-
const offsetY = useVirtualScrolling
|
|
882
|
-
|
|
891
|
+
const offsetY = useVirtualScrolling
|
|
892
|
+
? visibleRangeStart * virtualItemHeight
|
|
893
|
+
: 0;
|
|
894
|
+
const totalHeight = useVirtualScrolling
|
|
895
|
+
? totalItems * virtualItemHeight
|
|
896
|
+
: "auto";
|
|
883
897
|
// Check if we should show "Create" option
|
|
884
898
|
const showCreateOption = creatable &&
|
|
885
|
-
searchQuery.trim() !==
|
|
886
|
-
!filteredOptions.some(opt => opt.label.toLowerCase() === searchQuery.toLowerCase()) &&
|
|
887
|
-
!filteredGroups.some(group => group.options.some(opt => opt.label.toLowerCase() === searchQuery.toLowerCase()));
|
|
899
|
+
searchQuery.trim() !== "" &&
|
|
900
|
+
!filteredOptions.some((opt) => opt.label.toLowerCase() === searchQuery.toLowerCase()) &&
|
|
901
|
+
!filteredGroups.some((group) => group.options.some((opt) => opt.label.toLowerCase() === searchQuery.toLowerCase()));
|
|
888
902
|
// Handle creating new option
|
|
889
903
|
const handleCreateOption = () => {
|
|
890
904
|
if (onCreateOption) {
|
|
@@ -894,7 +908,7 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
894
908
|
// If no callback, just select the typed value
|
|
895
909
|
onChange?.(searchQuery.trim());
|
|
896
910
|
}
|
|
897
|
-
setSearchQuery(
|
|
911
|
+
setSearchQuery("");
|
|
898
912
|
setIsOpen(false);
|
|
899
913
|
};
|
|
900
914
|
// Handle click outside (desktop dropdown only)
|
|
@@ -908,14 +922,14 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
908
922
|
const isOutsideDropdown = dropdownRef.current && !dropdownRef.current.contains(target);
|
|
909
923
|
if (isOutsideSelect && isOutsideDropdown) {
|
|
910
924
|
setIsOpen(false);
|
|
911
|
-
setSearchQuery(
|
|
925
|
+
setSearchQuery("");
|
|
912
926
|
}
|
|
913
927
|
};
|
|
914
928
|
if (isOpen) {
|
|
915
|
-
document.addEventListener(
|
|
929
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
916
930
|
}
|
|
917
931
|
return () => {
|
|
918
|
-
document.removeEventListener(
|
|
932
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
919
933
|
};
|
|
920
934
|
}, [isOpen, useMobileSheet]);
|
|
921
935
|
// Focus search input when opened
|
|
@@ -949,8 +963,8 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
949
963
|
const hasSpaceBelow = spaceBelow >= dropdownHeight + gap;
|
|
950
964
|
const hasSpaceAbove = spaceAbove >= dropdownHeight + gap;
|
|
951
965
|
// Prefer bottom placement, flip to top if not enough space below but enough above
|
|
952
|
-
const placement = hasSpaceBelow || !hasSpaceAbove ?
|
|
953
|
-
const top = placement ===
|
|
966
|
+
const placement = hasSpaceBelow || !hasSpaceAbove ? "bottom" : "top";
|
|
967
|
+
const top = placement === "bottom"
|
|
954
968
|
? rect.bottom + gap
|
|
955
969
|
: rect.top - dropdownHeight - gap;
|
|
956
970
|
setDropdownPosition({
|
|
@@ -963,23 +977,23 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
963
977
|
// Initial position calculation
|
|
964
978
|
updatePosition();
|
|
965
979
|
// Listen for scroll events on all scrollable ancestors
|
|
966
|
-
window.addEventListener(
|
|
967
|
-
window.addEventListener(
|
|
980
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
981
|
+
window.addEventListener("resize", updatePosition);
|
|
968
982
|
return () => {
|
|
969
|
-
window.removeEventListener(
|
|
970
|
-
window.removeEventListener(
|
|
983
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
984
|
+
window.removeEventListener("resize", updatePosition);
|
|
971
985
|
};
|
|
972
986
|
}, [isOpen, useMobileSheet, usePortal]);
|
|
973
987
|
// Lock body scroll when mobile sheet is open
|
|
974
988
|
React.useEffect(() => {
|
|
975
989
|
if (useMobileSheet && isOpen) {
|
|
976
|
-
document.body.style.overflow =
|
|
990
|
+
document.body.style.overflow = "hidden";
|
|
977
991
|
}
|
|
978
992
|
else {
|
|
979
|
-
document.body.style.overflow =
|
|
993
|
+
document.body.style.overflow = "";
|
|
980
994
|
}
|
|
981
995
|
return () => {
|
|
982
|
-
document.body.style.overflow =
|
|
996
|
+
document.body.style.overflow = "";
|
|
983
997
|
};
|
|
984
998
|
}, [isOpen, useMobileSheet]);
|
|
985
999
|
// Handle escape key for mobile sheet
|
|
@@ -987,83 +1001,105 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
987
1001
|
if (!useMobileSheet || !isOpen)
|
|
988
1002
|
return;
|
|
989
1003
|
const handleEscape = (e) => {
|
|
990
|
-
if (e.key ===
|
|
1004
|
+
if (e.key === "Escape") {
|
|
991
1005
|
setIsOpen(false);
|
|
992
|
-
setSearchQuery(
|
|
1006
|
+
setSearchQuery("");
|
|
993
1007
|
}
|
|
994
1008
|
};
|
|
995
|
-
document.addEventListener(
|
|
996
|
-
return () => document.removeEventListener(
|
|
1009
|
+
document.addEventListener("keydown", handleEscape);
|
|
1010
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
997
1011
|
}, [isOpen, useMobileSheet]);
|
|
998
1012
|
const handleSelect = (optionValue) => {
|
|
999
1013
|
onChange?.(optionValue);
|
|
1000
1014
|
setIsOpen(false);
|
|
1001
|
-
setSearchQuery(
|
|
1015
|
+
setSearchQuery("");
|
|
1002
1016
|
};
|
|
1003
1017
|
const handleClose = () => {
|
|
1004
1018
|
setIsOpen(false);
|
|
1005
|
-
setSearchQuery(
|
|
1019
|
+
setSearchQuery("");
|
|
1006
1020
|
};
|
|
1007
1021
|
// Render option button (shared between desktop and mobile)
|
|
1008
1022
|
const renderOption = (option, isSelected, mobile = false) => (jsxRuntime.jsxs("button", { type: "button", onClick: () => !option.disabled && handleSelect(option.value), disabled: option.disabled, className: `
|
|
1009
1023
|
w-full flex items-center justify-between px-4 transition-colors
|
|
1010
1024
|
${mobile ? optionSizeClasses.lg : optionSizeClasses[effectiveSize]}
|
|
1011
|
-
${isSelected ?
|
|
1012
|
-
${option.disabled ?
|
|
1013
|
-
`, role: "option", "aria-selected": isSelected, children: [jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [option.icon && jsxRuntime.jsx("span", { children: option.icon }), option.label] }), isSelected && jsxRuntime.jsx(lucideReact.Check, { className: `${mobile ?
|
|
1025
|
+
${isSelected ? "bg-accent-50 text-accent-900" : "text-ink-700"}
|
|
1026
|
+
${option.disabled ? "opacity-40 cursor-not-allowed" : "hover:bg-paper-50 active:bg-paper-100 cursor-pointer"}
|
|
1027
|
+
`, role: "option", "aria-selected": isSelected, children: [jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [option.icon && jsxRuntime.jsx("span", { children: option.icon }), option.label] }), isSelected && (jsxRuntime.jsx(lucideReact.Check, { className: `${mobile ? "h-5 w-5" : "h-4 w-4"} text-accent-600` }))] }, option.value));
|
|
1014
1028
|
// Render options list content (shared between desktop and mobile)
|
|
1015
1029
|
const renderOptionsContent = (mobile = false) => {
|
|
1016
1030
|
if (loading) {
|
|
1017
1031
|
return (jsxRuntime.jsxs("div", { className: "px-4 py-8 flex items-center justify-center", role: "status", "aria-live": "polite", children: [jsxRuntime.jsx(lucideReact.Loader2, { className: "h-5 w-5 animate-spin text-ink-500" }), jsxRuntime.jsx("span", { className: "ml-2 text-sm text-ink-500", children: "Loading..." })] }));
|
|
1018
1032
|
}
|
|
1019
|
-
if (filteredOptions.length === 0 &&
|
|
1033
|
+
if (filteredOptions.length === 0 &&
|
|
1034
|
+
filteredGroups.length === 0 &&
|
|
1035
|
+
!showCreateOption) {
|
|
1020
1036
|
return (jsxRuntime.jsx("div", { className: "px-4 py-3 text-sm text-ink-500 text-center", role: "status", "aria-live": "polite", children: "No options found" }));
|
|
1021
1037
|
}
|
|
1022
1038
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showCreateOption && (jsxRuntime.jsx("button", { type: "button", onClick: handleCreateOption, className: `
|
|
1023
1039
|
w-full flex items-center px-4 text-accent-700 hover:bg-accent-50 transition-colors border-b border-paper-200
|
|
1024
|
-
${mobile ?
|
|
1025
|
-
`, children: jsxRuntime.jsxs("span", { className: "font-medium", children: ["Create \"", searchQuery, "\""] }) })), useVirtualScrolling ? (jsxRuntime.jsx("div", { style: { height: totalHeight, position:
|
|
1040
|
+
${mobile ? "py-3.5 text-base" : "py-2.5 text-sm"}
|
|
1041
|
+
`, children: jsxRuntime.jsxs("span", { className: "font-medium", children: ["Create \"", searchQuery, "\""] }) })), useVirtualScrolling ? (jsxRuntime.jsx("div", { style: { height: totalHeight, position: "relative" }, children: jsxRuntime.jsx("div", { style: { transform: `translateY(${offsetY}px)` }, children: visibleItems.map((item) => {
|
|
1026
1042
|
const option = item.option;
|
|
1027
1043
|
const isSelected = option.value === value;
|
|
1028
1044
|
const key = `${item.type}-${item.groupIndex}-${item.optionIndex}-${option.value}`;
|
|
1029
|
-
return (jsxRuntime.jsxs("button", { type: "button", onClick: () => !option.disabled && handleSelect(option.value), disabled: option.disabled, style: {
|
|
1045
|
+
return (jsxRuntime.jsxs("button", { type: "button", onClick: () => !option.disabled && handleSelect(option.value), disabled: option.disabled, style: {
|
|
1046
|
+
height: mobile ? "56px" : `${virtualItemHeight}px`,
|
|
1047
|
+
}, className: `
|
|
1030
1048
|
w-full flex items-center justify-between px-4 transition-colors
|
|
1031
|
-
${mobile ?
|
|
1032
|
-
${isSelected ?
|
|
1033
|
-
${option.disabled ?
|
|
1034
|
-
`, role: "option", "aria-selected": isSelected, children: [jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [option.icon && jsxRuntime.jsx("span", { children: option.icon }), option.label] }), isSelected && jsxRuntime.jsx(lucideReact.Check, { className: `${mobile ?
|
|
1049
|
+
${mobile ? "text-base" : "text-sm"}
|
|
1050
|
+
${isSelected ? "bg-accent-50 text-accent-900" : "text-ink-700"}
|
|
1051
|
+
${option.disabled ? "opacity-40 cursor-not-allowed" : "hover:bg-paper-50 cursor-pointer"}
|
|
1052
|
+
`, role: "option", "aria-selected": isSelected, children: [jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [option.icon && jsxRuntime.jsx("span", { children: option.icon }), option.label] }), isSelected && (jsxRuntime.jsx(lucideReact.Check, { className: `${mobile ? "h-5 w-5" : "h-4 w-4"} text-accent-600` }))] }, key));
|
|
1035
1053
|
}) }) })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [filteredOptions.map((option) => renderOption(option, option.value === value, mobile)), filteredGroups.map((group) => (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: `
|
|
1036
1054
|
px-4 font-semibold text-ink-500 uppercase tracking-wider bg-paper-50 border-t border-b border-paper-200
|
|
1037
|
-
${mobile ?
|
|
1055
|
+
${mobile ? "py-2.5 text-xs" : "py-2 text-xs"}
|
|
1038
1056
|
`, children: group.label }), group.options.map((option) => renderOption(option, option.value === value, mobile))] }, group.label)))] }))] }));
|
|
1039
1057
|
};
|
|
1040
1058
|
// Native select for mobile (optional)
|
|
1041
1059
|
if (useNativeSelect) {
|
|
1042
|
-
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsxs("label", { id: labelId, className: "label", children: [label, required && jsxRuntime.jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("select", { ref: nativeSelectRef, value: value ||
|
|
1060
|
+
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsxs("label", { id: labelId, className: "label", children: [label, required && jsxRuntime.jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("select", { ref: nativeSelectRef, value: value || "", onChange: (e) => onChange?.(e.target.value), disabled: disabled, className: `
|
|
1043
1061
|
input w-full appearance-none pr-10
|
|
1044
1062
|
${sizeClasses$d[effectiveSize]}
|
|
1045
|
-
${error ?
|
|
1046
|
-
${disabled ?
|
|
1047
|
-
`, "aria-labelledby": label ? labelId : undefined, "aria-invalid": error ?
|
|
1063
|
+
${error ? "border-error-400 focus:border-error-400 focus:ring-error-400" : ""}
|
|
1064
|
+
${disabled ? "opacity-40 cursor-not-allowed" : "cursor-pointer"}
|
|
1065
|
+
`, "aria-labelledby": label ? labelId : undefined, "aria-invalid": error ? "true" : undefined, "aria-describedby": error ? errorId : helperText ? helperTextId : undefined, "aria-required": required, children: [jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }), options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))), groups.map((group) => (jsxRuntime.jsx("optgroup", { label: group.label, children: group.options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))) }, group.label)))] }), jsxRuntime.jsx(lucideReact.ChevronDown, { className: "absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-500 pointer-events-none" })] }), error && (jsxRuntime.jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsxRuntime.jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
|
|
1048
1066
|
}
|
|
1049
1067
|
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsxs("label", { id: labelId, className: "label", children: [label, required && jsxRuntime.jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxRuntime.jsx("div", { ref: selectRef, className: "relative", children: jsxRuntime.jsxs("button", { ref: buttonRef, type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `
|
|
1050
1068
|
input w-full flex items-center justify-between px-3
|
|
1051
1069
|
${sizeClasses$d[effectiveSize]}
|
|
1052
|
-
${error ?
|
|
1053
|
-
${disabled ?
|
|
1054
|
-
`, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? placeholder : undefined, "aria-activedescendant": activeDescendant, "aria-invalid": error ?
|
|
1070
|
+
${error ? "border-error-400 focus:border-error-400 focus:ring-error-400" : ""}
|
|
1071
|
+
${disabled ? "opacity-40 cursor-not-allowed" : "cursor-pointer"}
|
|
1072
|
+
`, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? placeholder : undefined, "aria-activedescendant": activeDescendant, "aria-invalid": error ? "true" : undefined, "aria-describedby": error ? errorId : helperText ? helperTextId : undefined, "aria-disabled": disabled, "aria-required": required, children: [jsxRuntime.jsxs("span", { className: `flex items-center gap-2 ${selectedOption ? "text-ink-800" : "text-ink-400"}`, children: [loading && (jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin text-ink-500" })), !loading && selectedOption?.icon && (jsxRuntime.jsx("span", { children: selectedOption.icon })), selectedOption ? selectedOption.label : placeholder] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [clearable && value && (jsxRuntime.jsx("span", { role: "button", tabIndex: -1, onClick: (e) => {
|
|
1055
1073
|
e.stopPropagation();
|
|
1056
|
-
onChange?.(
|
|
1074
|
+
onChange?.("");
|
|
1057
1075
|
setIsOpen(false);
|
|
1058
|
-
},
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1076
|
+
}, onKeyDown: (e) => {
|
|
1077
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1078
|
+
e.preventDefault();
|
|
1079
|
+
e.stopPropagation();
|
|
1080
|
+
onChange?.("");
|
|
1081
|
+
setIsOpen(false);
|
|
1082
|
+
}
|
|
1083
|
+
}, className: "text-ink-400 hover:text-ink-600 transition-colors p-0.5 cursor-pointer inline-flex", "aria-label": "Clear selection", children: jsxRuntime.jsx(lucideReact.X, { className: `${effectiveSize === "lg" ? "h-5 w-5" : "h-4 w-4"}` }) })), jsxRuntime.jsx(lucideReact.ChevronDown, { className: `${effectiveSize === "lg" ? "h-5 w-5" : "h-4 w-4"} text-ink-500 transition-transform ${isOpen ? "rotate-180" : ""}` })] })] }) }), isOpen &&
|
|
1084
|
+
!useMobileSheet &&
|
|
1085
|
+
(usePortal ? dropdownPosition : true) &&
|
|
1086
|
+
(usePortal ? (reactDom.createPortal(jsxRuntime.jsxs("div", { ref: dropdownRef, className: `fixed z-[9999] bg-white bg-subtle-grain rounded-lg shadow-lg border border-paper-200 max-h-60 overflow-hidden animate-fade-in ${dropdownPosition?.placement === "top"
|
|
1087
|
+
? "origin-bottom"
|
|
1088
|
+
: "origin-top"}`, style: {
|
|
1089
|
+
top: dropdownPosition.top,
|
|
1090
|
+
left: dropdownPosition.left,
|
|
1091
|
+
width: dropdownPosition.width,
|
|
1092
|
+
}, children: [searchable && (jsxRuntime.jsx("div", { className: "p-2 border-b border-paper-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-ink-400" }), jsxRuntime.jsx("input", { ref: searchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", className: "w-full pl-9 pr-3 py-2 text-sm border border-paper-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options", "aria-autocomplete": "list", "aria-controls": listboxId })] }) })), jsxRuntime.jsx("div", { ref: listRef, id: listboxId, className: "overflow-y-auto", style: {
|
|
1093
|
+
maxHeight: useVirtualScrolling ? virtualHeight : "12rem",
|
|
1094
|
+
}, onScroll: (e) => useVirtualScrolling && setScrollTop(e.currentTarget.scrollTop), role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(false) })] }), document.body)) : (
|
|
1095
|
+
// Non-portal dropdown (inline, relative positioning)
|
|
1096
|
+
jsxRuntime.jsxs("div", { ref: dropdownRef, className: "absolute z-50 mt-1 w-full bg-white bg-subtle-grain rounded-lg shadow-lg border border-paper-200 max-h-60 overflow-hidden animate-fade-in", children: [searchable && (jsxRuntime.jsx("div", { className: "p-2 border-b border-paper-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-ink-400" }), jsxRuntime.jsx("input", { ref: searchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", className: "w-full pl-9 pr-3 py-2 text-sm border border-paper-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options", "aria-autocomplete": "list", "aria-controls": listboxId })] }) })), jsxRuntime.jsx("div", { ref: listRef, id: listboxId, className: "overflow-y-auto", style: {
|
|
1097
|
+
maxHeight: useVirtualScrolling ? virtualHeight : "12rem",
|
|
1098
|
+
}, onScroll: (e) => useVirtualScrolling && setScrollTop(e.currentTarget.scrollTop), role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(false) })] }))), isOpen &&
|
|
1099
|
+
useMobileSheet &&
|
|
1100
|
+
reactDom.createPortal(jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex items-end", onClick: (e) => e.target === e.currentTarget && handleClose(), role: "dialog", "aria-modal": "true", "aria-labelledby": label ? `mobile-${labelId}` : undefined, children: [jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/50 animate-fade-in" }), jsxRuntime.jsxs("div", { className: "relative w-full bg-white rounded-t-2xl shadow-2xl animate-slide-up max-h-[85vh] flex flex-col", style: { paddingBottom: "env(safe-area-inset-bottom)" }, children: [jsxRuntime.jsx("div", { className: "py-3 cursor-grab", children: jsxRuntime.jsx("div", { className: "w-12 h-1.5 bg-ink-300 rounded-full mx-auto" }) }), jsxRuntime.jsxs("div", { className: "px-4 pb-3 border-b border-paper-200 flex items-center justify-between", children: [label && (jsxRuntime.jsx("h2", { id: `mobile-${labelId}`, className: "text-lg font-semibold text-ink-900", children: label })), !label && (jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-ink-900", children: placeholder })), jsxRuntime.jsx("button", { onClick: handleClose, className: "text-ink-400 hover:text-ink-600 transition-colors p-2 -mr-2", "aria-label": "Close", children: jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" }) })] }), searchable && (jsxRuntime.jsx("div", { className: "p-3 border-b border-paper-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-400" }), jsxRuntime.jsx("input", { ref: mobileSearchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", inputMode: "search", enterKeyHint: "search", className: "w-full pl-12 pr-4 py-3 text-base border border-paper-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options" })] }) })), jsxRuntime.jsx("div", { id: listboxId, className: "overflow-y-auto flex-1", role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(true) })] })] }), document.body), error && (jsxRuntime.jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsxRuntime.jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
|
|
1065
1101
|
});
|
|
1066
|
-
Select.displayName =
|
|
1102
|
+
Select.displayName = "Select";
|
|
1067
1103
|
|
|
1068
1104
|
const MultiSelect = React.forwardRef(({ options, value = [], onChange, placeholder = 'Select options', searchable = false, disabled = false, label, helperText, error, maxHeight = 240, maxSelections, loading = false, 'aria-label': ariaLabel, }, ref) => {
|
|
1069
1105
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
@@ -2719,7 +2755,18 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
|
|
|
2719
2755
|
// Default clear: set all values to null/empty
|
|
2720
2756
|
const clearedValues = {};
|
|
2721
2757
|
filters.forEach(filter => {
|
|
2722
|
-
|
|
2758
|
+
if (filter.type === 'text' || filter.type === 'search') {
|
|
2759
|
+
clearedValues[filter.key] = '';
|
|
2760
|
+
}
|
|
2761
|
+
else if (filter.type === 'dateRange') {
|
|
2762
|
+
clearedValues[filter.key] = { from: undefined, to: undefined };
|
|
2763
|
+
}
|
|
2764
|
+
else if (filter.type === 'multiSelect') {
|
|
2765
|
+
clearedValues[filter.key] = [];
|
|
2766
|
+
}
|
|
2767
|
+
else {
|
|
2768
|
+
clearedValues[filter.key] = null;
|
|
2769
|
+
}
|
|
2723
2770
|
});
|
|
2724
2771
|
onChange(clearedValues);
|
|
2725
2772
|
}
|
|
@@ -2751,6 +2798,38 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
|
|
|
2751
2798
|
];
|
|
2752
2799
|
return (jsxRuntime.jsx(Select, { options: boolOptions, value: value === null || value === undefined ? '' : String(value), onChange: (newValue) => handleFilterChange(filter.key, newValue === '' ? null : newValue === 'true') }));
|
|
2753
2800
|
}
|
|
2801
|
+
case 'search':
|
|
2802
|
+
return (jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none", children: jsxRuntime.jsx(lucideReact.Search, { className: "h-4 w-4 text-ink-400" }) }), jsxRuntime.jsx("input", { type: "text", placeholder: filter.placeholder || `Search ${filter.label}...`, value: value || '', onChange: (e) => handleFilterChange(filter.key, e.target.value), className: "input pl-9" })] }));
|
|
2803
|
+
case 'dateRange': {
|
|
2804
|
+
const rangeValue = value || {};
|
|
2805
|
+
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx("input", { type: "date", value: rangeValue.from || '', onChange: (e) => handleFilterChange(filter.key, { ...rangeValue, from: e.target.value || undefined }), className: "input text-sm", "aria-label": `${filter.label} from` }), jsxRuntime.jsx("span", { className: "text-ink-400 text-xs", children: "to" }), jsxRuntime.jsx("input", { type: "date", value: rangeValue.to || '', onChange: (e) => handleFilterChange(filter.key, { ...rangeValue, to: e.target.value || undefined }), className: "input text-sm", "aria-label": `${filter.label} to` })] }));
|
|
2806
|
+
}
|
|
2807
|
+
case 'toggle': {
|
|
2808
|
+
const toggleOptions = [
|
|
2809
|
+
{ value: '', label: 'All' },
|
|
2810
|
+
{ value: 'true', label: 'Yes' },
|
|
2811
|
+
{ value: 'false', label: 'No' },
|
|
2812
|
+
];
|
|
2813
|
+
const currentVal = value === null || value === undefined ? '' : String(value);
|
|
2814
|
+
return (jsxRuntime.jsx("div", { className: "flex rounded-lg border border-paper-300 overflow-hidden", role: "group", children: toggleOptions.map((opt) => (jsxRuntime.jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, opt.value === '' ? null : opt.value === 'true'), className: `px-3 py-1.5 text-xs font-medium transition-colors ${currentVal === opt.value
|
|
2815
|
+
? 'bg-accent-500 text-white'
|
|
2816
|
+
: 'bg-white text-ink-600 hover:bg-paper-50'} ${opt.value !== '' ? 'border-l border-paper-300' : ''}`, children: opt.label }, opt.value))) }));
|
|
2817
|
+
}
|
|
2818
|
+
case 'multiSelect': {
|
|
2819
|
+
const selectedValues = Array.isArray(value) ? value : [];
|
|
2820
|
+
const msOptions = filter.options || [];
|
|
2821
|
+
return (jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(Select, { options: [{ value: '', label: `All ${filter.label}` }, ...msOptions.map(o => ({ value: String(o.value), label: o.label }))], value: "", onChange: (newValue) => {
|
|
2822
|
+
if (!newValue) {
|
|
2823
|
+
handleFilterChange(filter.key, []);
|
|
2824
|
+
}
|
|
2825
|
+
else if (!selectedValues.includes(newValue)) {
|
|
2826
|
+
handleFilterChange(filter.key, [...selectedValues, newValue]);
|
|
2827
|
+
}
|
|
2828
|
+
} }), selectedValues.length > 0 && (jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedValues.map((sv) => {
|
|
2829
|
+
const opt = msOptions.find(o => String(o.value) === sv);
|
|
2830
|
+
return (jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-accent-100 text-accent-700 rounded-full", children: [opt?.label || sv, jsxRuntime.jsx("button", { type: "button", onClick: () => handleFilterChange(filter.key, selectedValues.filter(v => v !== sv)), className: "hover:text-accent-900", children: jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" }) })] }, sv));
|
|
2831
|
+
}) }))] }));
|
|
2832
|
+
}
|
|
2754
2833
|
default:
|
|
2755
2834
|
return null;
|
|
2756
2835
|
}
|
|
@@ -2760,6 +2839,237 @@ function FilterBar({ filters, values, onChange, className = '', onClear, showCle
|
|
|
2760
2839
|
return (jsxRuntime.jsx("div", { className: `bg-white bg-subtle-grain border border-paper-200 rounded-lg shadow-sm p-4 ${className}`, children: jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [jsxRuntime.jsx("div", { className: "flex-1 flex flex-wrap gap-4", children: filters.map((filter) => (jsxRuntime.jsxs("div", { className: "flex flex-col space-y-1 min-w-[200px]", children: [jsxRuntime.jsx("label", { className: "label", children: filter.label }), renderFilter(filter)] }, filter.key))) }), showClearButton && (jsxRuntime.jsx("div", { className: "flex items-end", children: jsxRuntime.jsx(Button, { variant: "ghost", size: "md", onClick: handleClear, icon: jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }), iconPosition: "left", children: "Clear" }) }))] }) }));
|
|
2761
2840
|
}
|
|
2762
2841
|
|
|
2842
|
+
const variantClasses$4 = {
|
|
2843
|
+
primary: {
|
|
2844
|
+
default: 'bg-primary-100 text-primary-700 border-primary-200',
|
|
2845
|
+
hover: 'hover:bg-primary-200',
|
|
2846
|
+
close: 'hover:bg-primary-300 text-primary-600',
|
|
2847
|
+
selected: 'bg-primary-200 border-primary-400 ring-2 ring-primary-300',
|
|
2848
|
+
},
|
|
2849
|
+
secondary: {
|
|
2850
|
+
default: 'bg-ink-100 text-ink-700 border-ink-200',
|
|
2851
|
+
hover: 'hover:bg-ink-200',
|
|
2852
|
+
close: 'hover:bg-ink-300 text-ink-600',
|
|
2853
|
+
selected: 'bg-ink-200 border-ink-400 ring-2 ring-ink-300',
|
|
2854
|
+
},
|
|
2855
|
+
success: {
|
|
2856
|
+
default: 'bg-success-100 text-success-700 border-success-200',
|
|
2857
|
+
hover: 'hover:bg-success-200',
|
|
2858
|
+
close: 'hover:bg-success-300 text-success-600',
|
|
2859
|
+
selected: 'bg-success-200 border-success-400 ring-2 ring-success-300',
|
|
2860
|
+
},
|
|
2861
|
+
warning: {
|
|
2862
|
+
default: 'bg-warning-100 text-warning-700 border-warning-200',
|
|
2863
|
+
hover: 'hover:bg-warning-200',
|
|
2864
|
+
close: 'hover:bg-warning-300 text-warning-600',
|
|
2865
|
+
selected: 'bg-warning-200 border-warning-400 ring-2 ring-warning-300',
|
|
2866
|
+
},
|
|
2867
|
+
error: {
|
|
2868
|
+
default: 'bg-error-100 text-error-700 border-error-200',
|
|
2869
|
+
hover: 'hover:bg-error-200',
|
|
2870
|
+
close: 'hover:bg-error-300 text-error-600',
|
|
2871
|
+
selected: 'bg-error-200 border-error-400 ring-2 ring-error-300',
|
|
2872
|
+
},
|
|
2873
|
+
info: {
|
|
2874
|
+
default: 'bg-accent-100 text-accent-700 border-accent-200',
|
|
2875
|
+
hover: 'hover:bg-accent-200',
|
|
2876
|
+
close: 'hover:bg-accent-300 text-accent-600',
|
|
2877
|
+
selected: 'bg-accent-200 border-accent-400 ring-2 ring-accent-300',
|
|
2878
|
+
},
|
|
2879
|
+
};
|
|
2880
|
+
const sizeClasses$b = {
|
|
2881
|
+
sm: {
|
|
2882
|
+
container: 'h-6 px-2 text-xs gap-1',
|
|
2883
|
+
icon: 'h-3 w-3',
|
|
2884
|
+
close: 'h-3 w-3 ml-1',
|
|
2885
|
+
},
|
|
2886
|
+
md: {
|
|
2887
|
+
container: 'h-7 px-2.5 text-sm gap-1.5',
|
|
2888
|
+
icon: 'h-3.5 w-3.5',
|
|
2889
|
+
close: 'h-3.5 w-3.5 ml-1.5',
|
|
2890
|
+
},
|
|
2891
|
+
lg: {
|
|
2892
|
+
container: 'h-8 px-3 text-base gap-2',
|
|
2893
|
+
icon: 'h-4 w-4',
|
|
2894
|
+
close: 'h-4 w-4 ml-2',
|
|
2895
|
+
},
|
|
2896
|
+
};
|
|
2897
|
+
const gapClasses = {
|
|
2898
|
+
xs: 'gap-1',
|
|
2899
|
+
sm: 'gap-1.5',
|
|
2900
|
+
md: 'gap-2',
|
|
2901
|
+
lg: 'gap-3',
|
|
2902
|
+
};
|
|
2903
|
+
/**
|
|
2904
|
+
* Chip - Compact element for displaying values with optional remove functionality
|
|
2905
|
+
*
|
|
2906
|
+
* @example Basic chip
|
|
2907
|
+
* ```tsx
|
|
2908
|
+
* <Chip>Tag Name</Chip>
|
|
2909
|
+
* ```
|
|
2910
|
+
*
|
|
2911
|
+
* @example Removable chip
|
|
2912
|
+
* ```tsx
|
|
2913
|
+
* <Chip onClose={() => removeTag(tag)}>
|
|
2914
|
+
* {tag.name}
|
|
2915
|
+
* </Chip>
|
|
2916
|
+
* ```
|
|
2917
|
+
*
|
|
2918
|
+
* @example With icon and selected state
|
|
2919
|
+
* ```tsx
|
|
2920
|
+
* <Chip
|
|
2921
|
+
* icon={<Star className="h-3 w-3" />}
|
|
2922
|
+
* selected={isSelected}
|
|
2923
|
+
* onClick={() => toggleSelection()}
|
|
2924
|
+
* >
|
|
2925
|
+
* Favorite
|
|
2926
|
+
* </Chip>
|
|
2927
|
+
* ```
|
|
2928
|
+
*/
|
|
2929
|
+
function Chip({ children, variant = 'secondary', size = 'md', onClose, icon, disabled = false, className = '', onClick, selected = false, maxWidth, chipKey, }) {
|
|
2930
|
+
const variantStyle = variantClasses$4[variant];
|
|
2931
|
+
const sizeStyle = sizeClasses$b[size];
|
|
2932
|
+
const isClickable = !disabled && (onClick || onClose);
|
|
2933
|
+
return (jsxRuntime.jsxs("div", { className: `
|
|
2934
|
+
inline-flex items-center rounded-full border font-medium
|
|
2935
|
+
transition-colors
|
|
2936
|
+
${selected ? variantStyle.selected : variantStyle.default}
|
|
2937
|
+
${isClickable && !disabled && !selected ? variantStyle.hover : ''}
|
|
2938
|
+
${sizeStyle.container}
|
|
2939
|
+
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
|
2940
|
+
${onClick && !disabled ? 'cursor-pointer' : ''}
|
|
2941
|
+
${className}
|
|
2942
|
+
`, onClick: onClick && !disabled ? onClick : undefined, role: onClick ? 'button' : undefined, "aria-disabled": disabled, "aria-pressed": onClick ? selected : undefined, "data-chip-key": chipKey, style: { maxWidth: maxWidth || undefined }, children: [icon && (jsxRuntime.jsx("span", { className: `flex-shrink-0 ${sizeStyle.icon}`, children: icon })), jsxRuntime.jsx("span", { className: "truncate", children: children }), onClose && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
2943
|
+
e.stopPropagation();
|
|
2944
|
+
if (!disabled)
|
|
2945
|
+
onClose();
|
|
2946
|
+
}, disabled: disabled, className: `
|
|
2947
|
+
flex-shrink-0 rounded-full transition-colors
|
|
2948
|
+
${variantStyle.close}
|
|
2949
|
+
${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}
|
|
2950
|
+
${sizeStyle.close}
|
|
2951
|
+
`, "aria-label": "Remove", children: jsxRuntime.jsx(lucideReact.X, { className: "w-full h-full" }) }))] }));
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* ChipGroup - Container for multiple chips with layout and selection support
|
|
2955
|
+
*
|
|
2956
|
+
* @example Basic group
|
|
2957
|
+
* ```tsx
|
|
2958
|
+
* <ChipGroup wrap gap="sm">
|
|
2959
|
+
* {tags.map(tag => (
|
|
2960
|
+
* <Chip key={tag.id} onClose={() => removeTag(tag)}>
|
|
2961
|
+
* {tag.name}
|
|
2962
|
+
* </Chip>
|
|
2963
|
+
* ))}
|
|
2964
|
+
* </ChipGroup>
|
|
2965
|
+
* ```
|
|
2966
|
+
*
|
|
2967
|
+
* @example Selectable group (single)
|
|
2968
|
+
* ```tsx
|
|
2969
|
+
* <ChipGroup
|
|
2970
|
+
* selectionMode="single"
|
|
2971
|
+
* selectedKeys={[selectedCategory]}
|
|
2972
|
+
* onSelectionChange={(keys) => setSelectedCategory(keys[0])}
|
|
2973
|
+
* >
|
|
2974
|
+
* <Chip chipKey="all">All</Chip>
|
|
2975
|
+
* <Chip chipKey="active">Active</Chip>
|
|
2976
|
+
* <Chip chipKey="archived">Archived</Chip>
|
|
2977
|
+
* </ChipGroup>
|
|
2978
|
+
* ```
|
|
2979
|
+
*
|
|
2980
|
+
* @example Multi-select group
|
|
2981
|
+
* ```tsx
|
|
2982
|
+
* <ChipGroup
|
|
2983
|
+
* selectionMode="multiple"
|
|
2984
|
+
* selectedKeys={selectedTags}
|
|
2985
|
+
* onSelectionChange={setSelectedTags}
|
|
2986
|
+
* wrap
|
|
2987
|
+
* >
|
|
2988
|
+
* {availableTags.map(tag => (
|
|
2989
|
+
* <Chip key={tag} chipKey={tag}>{tag}</Chip>
|
|
2990
|
+
* ))}
|
|
2991
|
+
* </ChipGroup>
|
|
2992
|
+
* ```
|
|
2993
|
+
*/
|
|
2994
|
+
function ChipGroup({ children, direction = 'horizontal', wrap = false, gap = 'sm', selectionMode = 'none', selectedKeys = [], onSelectionChange, className = '', }) {
|
|
2995
|
+
const handleChipClick = (chipKey) => {
|
|
2996
|
+
if (selectionMode === 'none' || !onSelectionChange)
|
|
2997
|
+
return;
|
|
2998
|
+
if (selectionMode === 'single') {
|
|
2999
|
+
// Toggle single selection
|
|
3000
|
+
if (selectedKeys.includes(chipKey)) {
|
|
3001
|
+
onSelectionChange([]);
|
|
3002
|
+
}
|
|
3003
|
+
else {
|
|
3004
|
+
onSelectionChange([chipKey]);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
else if (selectionMode === 'multiple') {
|
|
3008
|
+
// Toggle in array
|
|
3009
|
+
if (selectedKeys.includes(chipKey)) {
|
|
3010
|
+
onSelectionChange(selectedKeys.filter(k => k !== chipKey));
|
|
3011
|
+
}
|
|
3012
|
+
else {
|
|
3013
|
+
onSelectionChange([...selectedKeys, chipKey]);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
};
|
|
3017
|
+
// Clone children to inject selection props
|
|
3018
|
+
const enhancedChildren = React.Children.map(children, (child) => {
|
|
3019
|
+
if (!React.isValidElement(child))
|
|
3020
|
+
return child;
|
|
3021
|
+
const chipKey = child.props.chipKey;
|
|
3022
|
+
if (!chipKey || selectionMode === 'none')
|
|
3023
|
+
return child;
|
|
3024
|
+
const isSelected = selectedKeys.includes(chipKey);
|
|
3025
|
+
return React.cloneElement(child, {
|
|
3026
|
+
...child.props,
|
|
3027
|
+
selected: isSelected,
|
|
3028
|
+
onClick: () => {
|
|
3029
|
+
// Call original onClick if exists
|
|
3030
|
+
if (child.props.onClick) {
|
|
3031
|
+
child.props.onClick();
|
|
3032
|
+
}
|
|
3033
|
+
handleChipClick(chipKey);
|
|
3034
|
+
},
|
|
3035
|
+
});
|
|
3036
|
+
});
|
|
3037
|
+
return (jsxRuntime.jsx("div", { className: `
|
|
3038
|
+
flex
|
|
3039
|
+
${direction === 'vertical' ? 'flex-col' : 'flex-row'}
|
|
3040
|
+
${wrap ? 'flex-wrap' : ''}
|
|
3041
|
+
${gapClasses[gap]}
|
|
3042
|
+
${className}
|
|
3043
|
+
`, role: selectionMode !== 'none' ? 'group' : undefined, "aria-label": selectionMode !== 'none' ? 'Chip selection group' : undefined, children: enhancedChildren }));
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
function FilterPills({ pills, onRemove, onClearAll, totalCount, className = '', }) {
|
|
3047
|
+
if (pills.length === 0)
|
|
3048
|
+
return null;
|
|
3049
|
+
return (jsxRuntime.jsxs("div", { className: `flex items-center gap-2 px-4 py-2 border-b border-paper-200 bg-paper-50 ${className}`, children: [jsxRuntime.jsx(lucideReact.Filter, { className: "h-3.5 w-3.5 text-ink-400 shrink-0" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 flex-wrap flex-1", children: [pills.map((pill) => (jsxRuntime.jsxs(Chip, { size: "sm", variant: "primary", onClose: () => onRemove(pill.key), children: [pill.label, ": ", pill.displayValue] }, pill.key))), pills.length >= 2 && (jsxRuntime.jsx("button", { type: "button", onClick: onClearAll, className: "text-xs text-ink-500 hover:text-ink-700 underline underline-offset-2 ml-1", children: "Clear all" }))] }), totalCount !== undefined && (jsxRuntime.jsxs("span", { className: "text-xs text-ink-500 shrink-0 tabular-nums", children: [totalCount.toLocaleString(), " ", totalCount === 1 ? 'record' : 'records'] }))] }));
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
|
3053
|
+
function LetterNav({ activeLetter, onChange, availableLetters, className = '', }) {
|
|
3054
|
+
const hasAvailability = availableLetters && availableLetters.length > 0;
|
|
3055
|
+
const availableSet = hasAvailability
|
|
3056
|
+
? new Set(availableLetters.map((l) => l.toUpperCase()))
|
|
3057
|
+
: null;
|
|
3058
|
+
return (jsxRuntime.jsxs("div", { className: `flex items-center gap-0.5 px-4 py-1.5 border-b border-paper-200 bg-white overflow-x-auto ${className}`, children: [jsxRuntime.jsx("button", { type: "button", onClick: () => onChange(null), className: `px-2 py-1 text-xs font-medium rounded transition-colors ${activeLetter === null
|
|
3059
|
+
? 'bg-accent-500 text-white'
|
|
3060
|
+
: 'text-ink-600 hover:bg-paper-100'}`, children: "All" }), LETTERS.map((letter) => {
|
|
3061
|
+
const isActive = activeLetter === letter;
|
|
3062
|
+
const isAvailable = !availableSet || availableSet.has(letter);
|
|
3063
|
+
return (jsxRuntime.jsx("button", { type: "button", onClick: () => onChange(isActive ? null : letter), className: `w-7 h-7 text-xs font-medium rounded transition-colors ${isActive
|
|
3064
|
+
? 'bg-accent-500 text-white'
|
|
3065
|
+
: isAvailable
|
|
3066
|
+
? 'text-ink-600 hover:bg-paper-100'
|
|
3067
|
+
: 'text-ink-300'}`, children: letter }, letter));
|
|
3068
|
+
}), jsxRuntime.jsx("button", { type: "button", onClick: () => onChange(activeLetter === '#' ? null : '#'), className: `px-2 py-1 text-xs font-medium rounded transition-colors ${activeLetter === '#'
|
|
3069
|
+
? 'bg-accent-500 text-white'
|
|
3070
|
+
: 'text-ink-600 hover:bg-paper-100'}`, children: "#" })] }));
|
|
3071
|
+
}
|
|
3072
|
+
|
|
2763
3073
|
function Loading({ variant = 'spinner', size = 'md', text }) {
|
|
2764
3074
|
const sizeClasses = {
|
|
2765
3075
|
sm: 'h-4 w-4',
|
|
@@ -4610,7 +4920,7 @@ function BottomSheetActions({ children, className = '' }) {
|
|
|
4610
4920
|
|
|
4611
4921
|
// Selector for all focusable elements
|
|
4612
4922
|
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
4613
|
-
const sizeClasses$
|
|
4923
|
+
const sizeClasses$a = {
|
|
4614
4924
|
sm: 'max-w-md',
|
|
4615
4925
|
md: 'max-w-lg',
|
|
4616
4926
|
lg: 'max-w-2xl',
|
|
@@ -4799,7 +5109,7 @@ function Modal({ isOpen, onClose, title, children, size = 'md', showCloseButton
|
|
|
4799
5109
|
return reactDom.createPortal(jsxRuntime.jsx(BottomSheet, { isOpen: isOpen, onClose: onClose, title: title, height: mobileHeight, showHandle: mobileShowHandle, showCloseButton: showCloseButton, children: children }), document.body);
|
|
4800
5110
|
}
|
|
4801
5111
|
// Render as standard modal on desktop
|
|
4802
|
-
const modalContent = (jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-ink-900 bg-opacity-50 backdrop-blur-sm animate-fade-in", onMouseDown: handleBackdropMouseDown, onClick: handleBackdropClick, children: jsxRuntime.jsxs("div", { ref: modalRef, className: `${sizeClasses$
|
|
5112
|
+
const modalContent = (jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-ink-900 bg-opacity-50 backdrop-blur-sm animate-fade-in", onMouseDown: handleBackdropMouseDown, onClick: handleBackdropClick, children: jsxRuntime.jsxs("div", { ref: modalRef, className: `${sizeClasses$a[size]} w-full bg-white bg-subtle-grain rounded-xl shadow-2xl border border-paper-200 ${getAnimationClass()}`, role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, tabIndex: -1, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-paper-200", children: [jsxRuntime.jsx("h3", { id: titleId, className: "text-lg font-medium text-ink-900", children: title }), showCloseButton && (jsxRuntime.jsx("button", { onClick: onClose, className: "text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Close modal", children: jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" }) }))] }), jsxRuntime.jsx("div", { className: `px-6 py-4 ${scrollable || maxHeight ? 'overflow-y-auto' : ''}`, style: {
|
|
4803
5113
|
maxHeight: maxHeight || (scrollable ? 'calc(100vh - 200px)' : undefined),
|
|
4804
5114
|
}, children: children })] }) }));
|
|
4805
5115
|
return reactDom.createPortal(modalContent, document.body);
|
|
@@ -5870,7 +6180,7 @@ function Alert({ variant = 'info', title, children, onClose, className = '', act
|
|
|
5870
6180
|
return (jsxRuntime.jsx("div", { className: `rounded-lg border p-4 ${styles.container} ${className}`, role: "alert", children: jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [jsxRuntime.jsx("div", { className: "flex-shrink-0 mt-0.5", children: styles.icon }), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [title && jsxRuntime.jsx("h4", { className: "text-sm font-medium mb-1", children: title }), jsxRuntime.jsx("div", { className: "text-sm", children: children }), actions.length > 0 && (jsxRuntime.jsx("div", { className: "flex gap-2 mt-3", children: actions.map((action, index) => (jsxRuntime.jsx("button", { onClick: action.onClick, className: getButtonStyles(action.variant), children: action.label }, index))) }))] }), onClose && (jsxRuntime.jsx("button", { onClick: onClose, className: "flex-shrink-0 text-current opacity-70 hover:opacity-100 transition-opacity", "aria-label": "Close alert", children: jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) }))] }) }));
|
|
5871
6181
|
}
|
|
5872
6182
|
|
|
5873
|
-
const sizeClasses$
|
|
6183
|
+
const sizeClasses$9 = {
|
|
5874
6184
|
left: {
|
|
5875
6185
|
sm: 'w-64',
|
|
5876
6186
|
md: 'w-96',
|
|
@@ -5949,7 +6259,7 @@ function Drawer({ isOpen, onClose, title, children, placement = 'right', size =
|
|
|
5949
6259
|
const isHorizontal = placement === 'left' || placement === 'right';
|
|
5950
6260
|
return (jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex", children: [showOverlay && (jsxRuntime.jsx("div", { className: "fixed inset-0 bg-ink-900 bg-opacity-50 backdrop-blur-sm animate-fade-in", onClick: handleOverlayClick, "aria-hidden": "true" })), jsxRuntime.jsxs("div", { className: `
|
|
5951
6261
|
fixed ${placementClasses[placement]}
|
|
5952
|
-
${sizeClasses$
|
|
6262
|
+
${sizeClasses$9[placement][size]}
|
|
5953
6263
|
bg-white border-paper-200 shadow-2xl
|
|
5954
6264
|
${isHorizontal ? 'border-r' : 'border-b'}
|
|
5955
6265
|
${animationClasses[placement].enter}
|
|
@@ -6091,14 +6401,14 @@ function useConfirmDialog() {
|
|
|
6091
6401
|
};
|
|
6092
6402
|
}
|
|
6093
6403
|
|
|
6094
|
-
const sizeClasses$
|
|
6404
|
+
const sizeClasses$8 = {
|
|
6095
6405
|
sm: 'h-3.5 w-3.5',
|
|
6096
6406
|
md: 'h-4 w-4',
|
|
6097
6407
|
lg: 'h-5 w-5',
|
|
6098
6408
|
};
|
|
6099
6409
|
function HelpTooltip({ content, icon = 'help', size = 'md', position = 'top', className = '', }) {
|
|
6100
6410
|
const IconComponent = icon === 'info' ? lucideReact.Info : lucideReact.HelpCircle;
|
|
6101
|
-
return (jsxRuntime.jsx(Tooltip, { content: content, position: position, children: jsxRuntime.jsx("span", { className: `inline-flex items-center justify-center text-ink-400 hover:text-ink-600 cursor-help transition-colors ${className}`, role: "button", "aria-label": "Help", tabIndex: 0, children: jsxRuntime.jsx(IconComponent, { className: sizeClasses$
|
|
6411
|
+
return (jsxRuntime.jsx(Tooltip, { content: content, position: position, children: jsxRuntime.jsx("span", { className: `inline-flex items-center justify-center text-ink-400 hover:text-ink-600 cursor-help transition-colors ${className}`, role: "button", "aria-label": "Help", tabIndex: 0, children: jsxRuntime.jsx(IconComponent, { className: sizeClasses$8[size] }) }) }));
|
|
6102
6412
|
}
|
|
6103
6413
|
|
|
6104
6414
|
/**
|
|
@@ -8476,210 +8786,6 @@ function MenuDivider() {
|
|
|
8476
8786
|
return { divider: true, id: `divider-${Date.now()}`, label: '' };
|
|
8477
8787
|
}
|
|
8478
8788
|
|
|
8479
|
-
const variantClasses$4 = {
|
|
8480
|
-
primary: {
|
|
8481
|
-
default: 'bg-primary-100 text-primary-700 border-primary-200',
|
|
8482
|
-
hover: 'hover:bg-primary-200',
|
|
8483
|
-
close: 'hover:bg-primary-300 text-primary-600',
|
|
8484
|
-
selected: 'bg-primary-200 border-primary-400 ring-2 ring-primary-300',
|
|
8485
|
-
},
|
|
8486
|
-
secondary: {
|
|
8487
|
-
default: 'bg-ink-100 text-ink-700 border-ink-200',
|
|
8488
|
-
hover: 'hover:bg-ink-200',
|
|
8489
|
-
close: 'hover:bg-ink-300 text-ink-600',
|
|
8490
|
-
selected: 'bg-ink-200 border-ink-400 ring-2 ring-ink-300',
|
|
8491
|
-
},
|
|
8492
|
-
success: {
|
|
8493
|
-
default: 'bg-success-100 text-success-700 border-success-200',
|
|
8494
|
-
hover: 'hover:bg-success-200',
|
|
8495
|
-
close: 'hover:bg-success-300 text-success-600',
|
|
8496
|
-
selected: 'bg-success-200 border-success-400 ring-2 ring-success-300',
|
|
8497
|
-
},
|
|
8498
|
-
warning: {
|
|
8499
|
-
default: 'bg-warning-100 text-warning-700 border-warning-200',
|
|
8500
|
-
hover: 'hover:bg-warning-200',
|
|
8501
|
-
close: 'hover:bg-warning-300 text-warning-600',
|
|
8502
|
-
selected: 'bg-warning-200 border-warning-400 ring-2 ring-warning-300',
|
|
8503
|
-
},
|
|
8504
|
-
error: {
|
|
8505
|
-
default: 'bg-error-100 text-error-700 border-error-200',
|
|
8506
|
-
hover: 'hover:bg-error-200',
|
|
8507
|
-
close: 'hover:bg-error-300 text-error-600',
|
|
8508
|
-
selected: 'bg-error-200 border-error-400 ring-2 ring-error-300',
|
|
8509
|
-
},
|
|
8510
|
-
info: {
|
|
8511
|
-
default: 'bg-accent-100 text-accent-700 border-accent-200',
|
|
8512
|
-
hover: 'hover:bg-accent-200',
|
|
8513
|
-
close: 'hover:bg-accent-300 text-accent-600',
|
|
8514
|
-
selected: 'bg-accent-200 border-accent-400 ring-2 ring-accent-300',
|
|
8515
|
-
},
|
|
8516
|
-
};
|
|
8517
|
-
const sizeClasses$8 = {
|
|
8518
|
-
sm: {
|
|
8519
|
-
container: 'h-6 px-2 text-xs gap-1',
|
|
8520
|
-
icon: 'h-3 w-3',
|
|
8521
|
-
close: 'h-3 w-3 ml-1',
|
|
8522
|
-
},
|
|
8523
|
-
md: {
|
|
8524
|
-
container: 'h-7 px-2.5 text-sm gap-1.5',
|
|
8525
|
-
icon: 'h-3.5 w-3.5',
|
|
8526
|
-
close: 'h-3.5 w-3.5 ml-1.5',
|
|
8527
|
-
},
|
|
8528
|
-
lg: {
|
|
8529
|
-
container: 'h-8 px-3 text-base gap-2',
|
|
8530
|
-
icon: 'h-4 w-4',
|
|
8531
|
-
close: 'h-4 w-4 ml-2',
|
|
8532
|
-
},
|
|
8533
|
-
};
|
|
8534
|
-
const gapClasses = {
|
|
8535
|
-
xs: 'gap-1',
|
|
8536
|
-
sm: 'gap-1.5',
|
|
8537
|
-
md: 'gap-2',
|
|
8538
|
-
lg: 'gap-3',
|
|
8539
|
-
};
|
|
8540
|
-
/**
|
|
8541
|
-
* Chip - Compact element for displaying values with optional remove functionality
|
|
8542
|
-
*
|
|
8543
|
-
* @example Basic chip
|
|
8544
|
-
* ```tsx
|
|
8545
|
-
* <Chip>Tag Name</Chip>
|
|
8546
|
-
* ```
|
|
8547
|
-
*
|
|
8548
|
-
* @example Removable chip
|
|
8549
|
-
* ```tsx
|
|
8550
|
-
* <Chip onClose={() => removeTag(tag)}>
|
|
8551
|
-
* {tag.name}
|
|
8552
|
-
* </Chip>
|
|
8553
|
-
* ```
|
|
8554
|
-
*
|
|
8555
|
-
* @example With icon and selected state
|
|
8556
|
-
* ```tsx
|
|
8557
|
-
* <Chip
|
|
8558
|
-
* icon={<Star className="h-3 w-3" />}
|
|
8559
|
-
* selected={isSelected}
|
|
8560
|
-
* onClick={() => toggleSelection()}
|
|
8561
|
-
* >
|
|
8562
|
-
* Favorite
|
|
8563
|
-
* </Chip>
|
|
8564
|
-
* ```
|
|
8565
|
-
*/
|
|
8566
|
-
function Chip({ children, variant = 'secondary', size = 'md', onClose, icon, disabled = false, className = '', onClick, selected = false, maxWidth, chipKey, }) {
|
|
8567
|
-
const variantStyle = variantClasses$4[variant];
|
|
8568
|
-
const sizeStyle = sizeClasses$8[size];
|
|
8569
|
-
const isClickable = !disabled && (onClick || onClose);
|
|
8570
|
-
return (jsxRuntime.jsxs("div", { className: `
|
|
8571
|
-
inline-flex items-center rounded-full border font-medium
|
|
8572
|
-
transition-colors
|
|
8573
|
-
${selected ? variantStyle.selected : variantStyle.default}
|
|
8574
|
-
${isClickable && !disabled && !selected ? variantStyle.hover : ''}
|
|
8575
|
-
${sizeStyle.container}
|
|
8576
|
-
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
|
8577
|
-
${onClick && !disabled ? 'cursor-pointer' : ''}
|
|
8578
|
-
${className}
|
|
8579
|
-
`, onClick: onClick && !disabled ? onClick : undefined, role: onClick ? 'button' : undefined, "aria-disabled": disabled, "aria-pressed": onClick ? selected : undefined, "data-chip-key": chipKey, style: { maxWidth: maxWidth || undefined }, children: [icon && (jsxRuntime.jsx("span", { className: `flex-shrink-0 ${sizeStyle.icon}`, children: icon })), jsxRuntime.jsx("span", { className: "truncate", children: children }), onClose && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
8580
|
-
e.stopPropagation();
|
|
8581
|
-
if (!disabled)
|
|
8582
|
-
onClose();
|
|
8583
|
-
}, disabled: disabled, className: `
|
|
8584
|
-
flex-shrink-0 rounded-full transition-colors
|
|
8585
|
-
${variantStyle.close}
|
|
8586
|
-
${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}
|
|
8587
|
-
${sizeStyle.close}
|
|
8588
|
-
`, "aria-label": "Remove", children: jsxRuntime.jsx(lucideReact.X, { className: "w-full h-full" }) }))] }));
|
|
8589
|
-
}
|
|
8590
|
-
/**
|
|
8591
|
-
* ChipGroup - Container for multiple chips with layout and selection support
|
|
8592
|
-
*
|
|
8593
|
-
* @example Basic group
|
|
8594
|
-
* ```tsx
|
|
8595
|
-
* <ChipGroup wrap gap="sm">
|
|
8596
|
-
* {tags.map(tag => (
|
|
8597
|
-
* <Chip key={tag.id} onClose={() => removeTag(tag)}>
|
|
8598
|
-
* {tag.name}
|
|
8599
|
-
* </Chip>
|
|
8600
|
-
* ))}
|
|
8601
|
-
* </ChipGroup>
|
|
8602
|
-
* ```
|
|
8603
|
-
*
|
|
8604
|
-
* @example Selectable group (single)
|
|
8605
|
-
* ```tsx
|
|
8606
|
-
* <ChipGroup
|
|
8607
|
-
* selectionMode="single"
|
|
8608
|
-
* selectedKeys={[selectedCategory]}
|
|
8609
|
-
* onSelectionChange={(keys) => setSelectedCategory(keys[0])}
|
|
8610
|
-
* >
|
|
8611
|
-
* <Chip chipKey="all">All</Chip>
|
|
8612
|
-
* <Chip chipKey="active">Active</Chip>
|
|
8613
|
-
* <Chip chipKey="archived">Archived</Chip>
|
|
8614
|
-
* </ChipGroup>
|
|
8615
|
-
* ```
|
|
8616
|
-
*
|
|
8617
|
-
* @example Multi-select group
|
|
8618
|
-
* ```tsx
|
|
8619
|
-
* <ChipGroup
|
|
8620
|
-
* selectionMode="multiple"
|
|
8621
|
-
* selectedKeys={selectedTags}
|
|
8622
|
-
* onSelectionChange={setSelectedTags}
|
|
8623
|
-
* wrap
|
|
8624
|
-
* >
|
|
8625
|
-
* {availableTags.map(tag => (
|
|
8626
|
-
* <Chip key={tag} chipKey={tag}>{tag}</Chip>
|
|
8627
|
-
* ))}
|
|
8628
|
-
* </ChipGroup>
|
|
8629
|
-
* ```
|
|
8630
|
-
*/
|
|
8631
|
-
function ChipGroup({ children, direction = 'horizontal', wrap = false, gap = 'sm', selectionMode = 'none', selectedKeys = [], onSelectionChange, className = '', }) {
|
|
8632
|
-
const handleChipClick = (chipKey) => {
|
|
8633
|
-
if (selectionMode === 'none' || !onSelectionChange)
|
|
8634
|
-
return;
|
|
8635
|
-
if (selectionMode === 'single') {
|
|
8636
|
-
// Toggle single selection
|
|
8637
|
-
if (selectedKeys.includes(chipKey)) {
|
|
8638
|
-
onSelectionChange([]);
|
|
8639
|
-
}
|
|
8640
|
-
else {
|
|
8641
|
-
onSelectionChange([chipKey]);
|
|
8642
|
-
}
|
|
8643
|
-
}
|
|
8644
|
-
else if (selectionMode === 'multiple') {
|
|
8645
|
-
// Toggle in array
|
|
8646
|
-
if (selectedKeys.includes(chipKey)) {
|
|
8647
|
-
onSelectionChange(selectedKeys.filter(k => k !== chipKey));
|
|
8648
|
-
}
|
|
8649
|
-
else {
|
|
8650
|
-
onSelectionChange([...selectedKeys, chipKey]);
|
|
8651
|
-
}
|
|
8652
|
-
}
|
|
8653
|
-
};
|
|
8654
|
-
// Clone children to inject selection props
|
|
8655
|
-
const enhancedChildren = React.Children.map(children, (child) => {
|
|
8656
|
-
if (!React.isValidElement(child))
|
|
8657
|
-
return child;
|
|
8658
|
-
const chipKey = child.props.chipKey;
|
|
8659
|
-
if (!chipKey || selectionMode === 'none')
|
|
8660
|
-
return child;
|
|
8661
|
-
const isSelected = selectedKeys.includes(chipKey);
|
|
8662
|
-
return React.cloneElement(child, {
|
|
8663
|
-
...child.props,
|
|
8664
|
-
selected: isSelected,
|
|
8665
|
-
onClick: () => {
|
|
8666
|
-
// Call original onClick if exists
|
|
8667
|
-
if (child.props.onClick) {
|
|
8668
|
-
child.props.onClick();
|
|
8669
|
-
}
|
|
8670
|
-
handleChipClick(chipKey);
|
|
8671
|
-
},
|
|
8672
|
-
});
|
|
8673
|
-
});
|
|
8674
|
-
return (jsxRuntime.jsx("div", { className: `
|
|
8675
|
-
flex
|
|
8676
|
-
${direction === 'vertical' ? 'flex-col' : 'flex-row'}
|
|
8677
|
-
${wrap ? 'flex-wrap' : ''}
|
|
8678
|
-
${gapClasses[gap]}
|
|
8679
|
-
${className}
|
|
8680
|
-
`, role: selectionMode !== 'none' ? 'group' : undefined, "aria-label": selectionMode !== 'none' ? 'Chip selection group' : undefined, children: enhancedChildren }));
|
|
8681
|
-
}
|
|
8682
|
-
|
|
8683
8789
|
const sizeClasses$7 = {
|
|
8684
8790
|
sm: {
|
|
8685
8791
|
item: 'py-1.5 px-2',
|
|
@@ -11799,7 +11905,7 @@ function Tabs({ tabs, activeTab: controlledActiveTab, defaultTab, variant = 'und
|
|
|
11799
11905
|
}) })] }));
|
|
11800
11906
|
}
|
|
11801
11907
|
|
|
11802
|
-
function Pagination({ currentPage, totalPages, onPageChange, showPageNumbers = true, maxPageNumbers = 5, showPageJump = false, }) {
|
|
11908
|
+
function Pagination({ currentPage, totalPages, onPageChange, showPageNumbers = true, maxPageNumbers = 5, showPageJump = false, totalItems, pageSize, pageSizeOptions, onPageSizeChange, showRecordCount = false, }) {
|
|
11803
11909
|
const [jumpValue, setJumpValue] = React.useState('');
|
|
11804
11910
|
const getPageNumbers = () => {
|
|
11805
11911
|
const pages = [];
|
|
@@ -11840,16 +11946,20 @@ function Pagination({ currentPage, totalPages, onPageChange, showPageNumbers = t
|
|
|
11840
11946
|
setJumpValue('');
|
|
11841
11947
|
}
|
|
11842
11948
|
};
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11949
|
+
const showLeftSection = showRecordCount && totalItems !== undefined && pageSize;
|
|
11950
|
+
const showRightSection = onPageSizeChange && pageSizeOptions && pageSizeOptions.length > 0;
|
|
11951
|
+
const rangeStart = totalItems ? (currentPage - 1) * (pageSize || 0) + 1 : 0;
|
|
11952
|
+
const rangeEnd = totalItems ? Math.min(currentPage * (pageSize || 0), totalItems) : 0;
|
|
11953
|
+
return (jsxRuntime.jsxs("nav", { className: `flex items-center gap-2 ${showLeftSection || showRightSection ? 'justify-between' : 'justify-center'}`, "aria-label": "Pagination", children: [showLeftSection ? (jsxRuntime.jsxs("span", { className: "text-sm text-ink-500 tabular-nums shrink-0", children: ["Showing ", rangeStart.toLocaleString(), "\u2013", rangeEnd.toLocaleString(), " of ", totalItems.toLocaleString()] })) : showRightSection ? jsxRuntime.jsx("div", {}) : null, jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("button", { onClick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, className: "inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-ink-700 bg-white border border-paper-300 rounded-lg hover:bg-paper-50 hover:border-paper-400 disabled:opacity-40 disabled:cursor-not-allowed transition-all shadow-xs hover:shadow-sm", "aria-label": "Previous page", children: [jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Previous" })] }), showPageNumbers && (jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: pageNumbers.map((page, index) => {
|
|
11954
|
+
if (page === '...') {
|
|
11955
|
+
return (jsxRuntime.jsx("span", { className: "px-3 py-2 text-ink-500", children: "..." }, `ellipsis-${index}`));
|
|
11956
|
+
}
|
|
11957
|
+
const pageNum = page;
|
|
11958
|
+
const isActive = pageNum === currentPage;
|
|
11959
|
+
return (jsxRuntime.jsx("button", { onClick: () => onPageChange(pageNum), className: `px-3 py-2 text-sm font-medium rounded-lg transition-all ${isActive
|
|
11960
|
+
? 'bg-accent-500 text-white shadow-sm'
|
|
11961
|
+
: 'text-ink-700 bg-white border border-paper-300 hover:bg-paper-50 hover:border-paper-400'}`, "aria-label": `Page ${pageNum}`, "aria-current": isActive ? 'page' : undefined, children: pageNum }, pageNum));
|
|
11962
|
+
}) })), jsxRuntime.jsxs("button", { onClick: () => onPageChange(currentPage + 1), disabled: currentPage === totalPages, className: "inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-ink-700 bg-white border border-paper-300 rounded-lg hover:bg-paper-50 hover:border-paper-400 disabled:opacity-40 disabled:cursor-not-allowed transition-all shadow-xs hover:shadow-sm", "aria-label": "Next page", children: [jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Next" }), jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })] }), showPageJump && (jsxRuntime.jsxs("form", { onSubmit: handlePageJump, className: "flex items-center gap-2 ml-2", children: [jsxRuntime.jsx("span", { className: "text-sm text-ink-600 hidden sm:inline", children: "Go to:" }), jsxRuntime.jsx("input", { type: "number", min: "1", max: totalPages, value: jumpValue, onChange: (e) => setJumpValue(e.target.value), placeholder: "#", className: "w-16 px-2 py-1.5 text-sm text-center border border-paper-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", "aria-label": "Jump to page" }), jsxRuntime.jsx("button", { type: "submit", disabled: !jumpValue, className: "px-3 py-1.5 text-sm font-medium text-white bg-accent-500 rounded-lg hover:bg-accent-600 disabled:opacity-40 disabled:cursor-not-allowed transition-all", children: "Go" })] }))] }), showRightSection ? (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [jsxRuntime.jsx("span", { className: "text-sm text-ink-500 hidden sm:inline", children: "Per page:" }), jsxRuntime.jsx("select", { value: pageSize || pageSizeOptions[0], onChange: (e) => onPageSizeChange(Number(e.target.value)), className: "px-2 py-1.5 text-sm border border-paper-300 rounded-lg bg-white text-ink-700 focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400 cursor-pointer", "aria-label": "Items per page", children: pageSizeOptions.map((size) => (jsxRuntime.jsx("option", { value: size, children: size }, size))) })] })) : showLeftSection ? jsxRuntime.jsx("div", {}) : null] }));
|
|
11853
11963
|
}
|
|
11854
11964
|
|
|
11855
11965
|
function StepIndicator({ steps, currentStep, variant = 'horizontal', onStepClick, }) {
|
|
@@ -15706,52 +15816,44 @@ function getAugmentedNamespace(n) {
|
|
|
15706
15816
|
* (A1, A1:C5, ...)
|
|
15707
15817
|
*/
|
|
15708
15818
|
|
|
15709
|
-
|
|
15710
|
-
var hasRequiredCollection;
|
|
15711
|
-
|
|
15712
|
-
function requireCollection () {
|
|
15713
|
-
if (hasRequiredCollection) return collection;
|
|
15714
|
-
hasRequiredCollection = 1;
|
|
15715
|
-
class Collection {
|
|
15819
|
+
let Collection$3 = class Collection {
|
|
15716
15820
|
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15821
|
+
constructor(data, refs) {
|
|
15822
|
+
if (data == null && refs == null) {
|
|
15823
|
+
this._data = [];
|
|
15824
|
+
this._refs = [];
|
|
15825
|
+
} else {
|
|
15826
|
+
if (data.length !== refs.length)
|
|
15827
|
+
throw Error('Collection: data length should match references length.');
|
|
15828
|
+
this._data = data;
|
|
15829
|
+
this._refs = refs;
|
|
15830
|
+
}
|
|
15831
|
+
}
|
|
15728
15832
|
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
15833
|
+
get data() {
|
|
15834
|
+
return this._data;
|
|
15835
|
+
}
|
|
15732
15836
|
|
|
15733
|
-
|
|
15734
|
-
|
|
15735
|
-
|
|
15837
|
+
get refs() {
|
|
15838
|
+
return this._refs;
|
|
15839
|
+
}
|
|
15736
15840
|
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15841
|
+
get length() {
|
|
15842
|
+
return this._data.length;
|
|
15843
|
+
}
|
|
15740
15844
|
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15845
|
+
/**
|
|
15846
|
+
* Add data and references to this collection.
|
|
15847
|
+
* @param {{}} obj - data
|
|
15848
|
+
* @param {{}} ref - reference
|
|
15849
|
+
*/
|
|
15850
|
+
add(obj, ref) {
|
|
15851
|
+
this._data.push(obj);
|
|
15852
|
+
this._refs.push(ref);
|
|
15853
|
+
}
|
|
15854
|
+
};
|
|
15751
15855
|
|
|
15752
|
-
|
|
15753
|
-
return collection;
|
|
15754
|
-
}
|
|
15856
|
+
var collection = Collection$3;
|
|
15755
15857
|
|
|
15756
15858
|
var helpers;
|
|
15757
15859
|
var hasRequiredHelpers;
|
|
@@ -15760,7 +15862,7 @@ function requireHelpers () {
|
|
|
15760
15862
|
if (hasRequiredHelpers) return helpers;
|
|
15761
15863
|
hasRequiredHelpers = 1;
|
|
15762
15864
|
const FormulaError = requireError();
|
|
15763
|
-
const Collection =
|
|
15865
|
+
const Collection = collection;
|
|
15764
15866
|
|
|
15765
15867
|
const Types = {
|
|
15766
15868
|
NUMBER: 0,
|
|
@@ -25414,7 +25516,7 @@ var engineering = EngineeringFunctions;
|
|
|
25414
25516
|
|
|
25415
25517
|
const FormulaError$b = requireError();
|
|
25416
25518
|
const {FormulaHelpers: FormulaHelpers$8, Types: Types$6, WildCard, Address: Address$3} = requireHelpers();
|
|
25417
|
-
const Collection$2 =
|
|
25519
|
+
const Collection$2 = collection;
|
|
25418
25520
|
const H$5 = FormulaHelpers$8;
|
|
25419
25521
|
|
|
25420
25522
|
const ReferenceFunctions$1 = {
|
|
@@ -37042,7 +37144,7 @@ var parsing = {
|
|
|
37042
37144
|
const FormulaError$4 = requireError();
|
|
37043
37145
|
const {Address: Address$1} = requireHelpers();
|
|
37044
37146
|
const {Prefix: Prefix$1, Postfix: Postfix$1, Infix: Infix$1, Operators: Operators$1} = operators;
|
|
37045
|
-
const Collection$1 =
|
|
37147
|
+
const Collection$1 = collection;
|
|
37046
37148
|
const MAX_ROW$1 = 1048576, MAX_COLUMN$1 = 16384;
|
|
37047
37149
|
const {NotAllInputParsedException} = require$$4;
|
|
37048
37150
|
|
|
@@ -37804,7 +37906,7 @@ var hooks$1 = {
|
|
|
37804
37906
|
const FormulaError$2 = requireError();
|
|
37805
37907
|
const {FormulaHelpers: FormulaHelpers$1, Types, Address} = requireHelpers();
|
|
37806
37908
|
const {Prefix, Postfix, Infix, Operators} = operators;
|
|
37807
|
-
const Collection =
|
|
37909
|
+
const Collection = collection;
|
|
37808
37910
|
const MAX_ROW = 1048576, MAX_COLUMN = 16384;
|
|
37809
37911
|
|
|
37810
37912
|
let Utils$1 = class Utils {
|
|
@@ -62855,6 +62957,7 @@ exports.FieldArray = FieldArray;
|
|
|
62855
62957
|
exports.FileUpload = FileUpload;
|
|
62856
62958
|
exports.FilterBar = FilterBar;
|
|
62857
62959
|
exports.FilterControls = FilterControls;
|
|
62960
|
+
exports.FilterPills = FilterPills;
|
|
62858
62961
|
exports.FilterStatusBanner = FilterStatusBanner;
|
|
62859
62962
|
exports.FloatingActionButton = FloatingActionButton;
|
|
62860
62963
|
exports.Form = Form;
|
|
@@ -62874,6 +62977,7 @@ exports.InsightsPanelUI = InsightsPanelUI;
|
|
|
62874
62977
|
exports.InviteCard = InviteCard;
|
|
62875
62978
|
exports.KanbanBoard = KanbanBoard;
|
|
62876
62979
|
exports.Layout = Layout;
|
|
62980
|
+
exports.LetterNav = LetterNav;
|
|
62877
62981
|
exports.Loading = Loading;
|
|
62878
62982
|
exports.LoadingOverlay = LoadingOverlay;
|
|
62879
62983
|
exports.Logo = Logo;
|