@rebasepro/ui 0.17.3 → 0.18.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/README.md +4 -0
- package/dist/components/MultiSelect.d.ts +10 -0
- package/dist/index.es.js +2 -1
- package/dist/index.es.js.map +1 -1
- package/package.json +32 -23
- package/src/components/Alert.tsx +0 -118
- package/src/components/Autocomplete.tsx +0 -93
- package/src/components/Avatar.tsx +0 -73
- package/src/components/Badge.tsx +0 -44
- package/src/components/BooleanSwitch.tsx +0 -116
- package/src/components/BooleanSwitchWithLabel.tsx +0 -120
- package/src/components/Button.tsx +0 -110
- package/src/components/Card.tsx +0 -43
- package/src/components/CenteredView.tsx +0 -35
- package/src/components/Checkbox.tsx +0 -129
- package/src/components/Chip.tsx +0 -131
- package/src/components/CircularProgress.tsx +0 -53
- package/src/components/CircularProgressCenter.tsx +0 -27
- package/src/components/Collapse.tsx +0 -66
- package/src/components/ColorPicker.tsx +0 -140
- package/src/components/Container.tsx +0 -51
- package/src/components/DateTimeField.tsx +0 -363
- package/src/components/DebouncedTextField.tsx +0 -76
- package/src/components/Dialog.tsx +0 -159
- package/src/components/DialogActions.tsx +0 -29
- package/src/components/DialogContent.tsx +0 -27
- package/src/components/DialogTitle.tsx +0 -41
- package/src/components/ErrorBoundary.tsx +0 -236
- package/src/components/ExpandablePanel.tsx +0 -130
- package/src/components/FileUpload.tsx +0 -103
- package/src/components/FilterChip.tsx +0 -89
- package/src/components/IconButton.tsx +0 -101
- package/src/components/InfoLabel.tsx +0 -23
- package/src/components/InputLabel.tsx +0 -40
- package/src/components/Label.tsx +0 -32
- package/src/components/LoadingButton.tsx +0 -32
- package/src/components/Markdown.tsx +0 -55
- package/src/components/Menu.tsx +0 -114
- package/src/components/Menubar.tsx +0 -355
- package/src/components/MultiSelect.tsx +0 -433
- package/src/components/Paper.tsx +0 -23
- package/src/components/Popover.tsx +0 -162
- package/src/components/RadioGroup.tsx +0 -74
- package/src/components/ResizablePanels.tsx +0 -189
- package/src/components/SearchBar.tsx +0 -136
- package/src/components/Select.tsx +0 -326
- package/src/components/Separator.tsx +0 -28
- package/src/components/Sheet.tsx +0 -135
- package/src/components/Skeleton.tsx +0 -54
- package/src/components/Slider.tsx +0 -110
- package/src/components/Table.tsx +0 -178
- package/src/components/Tabs.tsx +0 -185
- package/src/components/TextField.tsx +0 -270
- package/src/components/TextareaAutosize.tsx +0 -368
- package/src/components/ToggleButtonGroup.tsx +0 -69
- package/src/components/Tooltip.tsx +0 -153
- package/src/components/Typography.tsx +0 -132
- package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
- package/src/components/VirtualTable/VirtualTable.tsx +0 -700
- package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
- package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
- package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
- package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
- package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
- package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
- package/src/components/VirtualTable/index.tsx +0 -10
- package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
- package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
- package/src/components/VirtualTable/types.tsx +0 -47
- package/src/components/common/SelectInputLabel.tsx +0 -23
- package/src/components/index.tsx +0 -62
- package/src/hooks/PortalContainerContext.tsx +0 -50
- package/src/hooks/index.ts +0 -7
- package/src/hooks/useDebounceCallback.tsx +0 -21
- package/src/hooks/useDebounceValue.tsx +0 -20
- package/src/hooks/useDebouncedCallback.ts +0 -39
- package/src/hooks/useInjectStyles.tsx +0 -31
- package/src/hooks/useOutsideAlerter.tsx +0 -46
- package/src/icons/GitHubIcon.tsx +0 -21
- package/src/icons/HandleIcon.tsx +0 -23
- package/src/icons/Icon.tsx +0 -33
- package/src/icons/LucideIconByName.tsx +0 -117
- package/src/icons/cool_icon_keys.ts +0 -12
- package/src/icons/icon_keys.ts +0 -1936
- package/src/icons/index.ts +0 -179
- package/src/index.css +0 -169
- package/src/index.ts +0 -47
- package/src/scripts/fixIconSizes.cjs +0 -92
- package/src/scripts/fixIconSizesToConst.cjs +0 -106
- package/src/scripts/generateIconKeys.ts +0 -41
- package/src/scripts/generateIcons.ts +0 -4
- package/src/scripts/saveIconFiles.ts +0 -89
- package/src/styles.ts +0 -141
- package/src/theme.css +0 -168
- package/src/util/chip_colors.ts +0 -242
- package/src/util/cls.ts +0 -8
- package/src/util/debounce.ts +0 -28
- package/src/util/hash.ts +0 -11
- package/src/util/index.ts +0 -4
- package/src/util/key_to_icon_component.ts +0 -11
- package/src/util/lazy_chunk.ts +0 -101
- package/src/views/CardView.tsx +0 -281
- package/src/views/CollectionView/CollectionCardView.tsx +0 -265
- package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
- package/src/views/CollectionView/CollectionListView.tsx +0 -240
- package/src/views/CollectionView/CollectionTableView.tsx +0 -229
- package/src/views/CollectionView/CollectionView.tsx +0 -450
- package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
- package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
- package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
- package/src/views/CollectionView/index.ts +0 -39
- package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
- package/src/views/CollectionView/utils.ts +0 -14
- package/src/views/Kanban/Board.tsx +0 -384
- package/src/views/Kanban/BoardColumn.tsx +0 -138
- package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
- package/src/views/Kanban/BoardSortableList.tsx +0 -182
- package/src/views/Kanban/board_types.ts +0 -80
- package/src/views/Kanban/index.tsx +0 -5
- package/src/views/Kanban/placement.ts +0 -62
- package/src/views/KanbanView.tsx +0 -32
- package/src/views/ListView.tsx +0 -296
- package/src/views/index.ts +0 -5
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import { cls } from "../util";
|
|
3
|
-
import { defaultBorderMixin } from "../styles";
|
|
4
|
-
|
|
5
|
-
export type ResizablePanelsProps = {
|
|
6
|
-
firstPanel: React.ReactNode;
|
|
7
|
-
secondPanel: React.ReactNode;
|
|
8
|
-
/** Whether the first panel is visible (e.g. Sidebar) */
|
|
9
|
-
showFirstPanel?: boolean;
|
|
10
|
-
/** Whether the second panel is visible (e.g. Results) */
|
|
11
|
-
showSecondPanel?: boolean;
|
|
12
|
-
/** 0-100 representing the width/height of the first panel */
|
|
13
|
-
panelSizePercent: number;
|
|
14
|
-
onPanelSizeChange: (sizePercent: number) => void;
|
|
15
|
-
minPanelSizePx?: number;
|
|
16
|
-
orientation?: "horizontal" | "vertical";
|
|
17
|
-
animateLayout?: boolean;
|
|
18
|
-
className?: string;
|
|
19
|
-
stacked?: boolean;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function ResizablePanels({
|
|
23
|
-
firstPanel,
|
|
24
|
-
secondPanel,
|
|
25
|
-
showFirstPanel = true,
|
|
26
|
-
showSecondPanel = true,
|
|
27
|
-
panelSizePercent,
|
|
28
|
-
onPanelSizeChange,
|
|
29
|
-
minPanelSizePx = 200,
|
|
30
|
-
orientation = "horizontal",
|
|
31
|
-
animateLayout = true,
|
|
32
|
-
className,
|
|
33
|
-
stacked = false
|
|
34
|
-
}: ResizablePanelsProps) {
|
|
35
|
-
|
|
36
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
37
|
-
const isResizingRef = useRef(false);
|
|
38
|
-
|
|
39
|
-
// For local layout tracking without triggering React rerenders during drag
|
|
40
|
-
const firstPanelRef = useRef<HTMLDivElement>(null);
|
|
41
|
-
const startPosRef = useRef(0);
|
|
42
|
-
const startSizeRef = useRef(0);
|
|
43
|
-
|
|
44
|
-
const [isResizing, setIsResizing] = useState(false);
|
|
45
|
-
const isHorizontal = orientation === "horizontal";
|
|
46
|
-
|
|
47
|
-
const handleResizeStart = useCallback((e: React.MouseEvent) => {
|
|
48
|
-
if (!showFirstPanel || !showSecondPanel) return;
|
|
49
|
-
|
|
50
|
-
e.preventDefault();
|
|
51
|
-
isResizingRef.current = true;
|
|
52
|
-
setIsResizing(true);
|
|
53
|
-
|
|
54
|
-
startPosRef.current = isHorizontal ? e.clientX : e.clientY;
|
|
55
|
-
|
|
56
|
-
if (firstPanelRef.current) {
|
|
57
|
-
const rect = firstPanelRef.current.getBoundingClientRect();
|
|
58
|
-
startSizeRef.current = isHorizontal ? rect.width : rect.height;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
document.body.style.cursor = isHorizontal ? "col-resize" : "row-resize";
|
|
62
|
-
document.body.style.userSelect = "none";
|
|
63
|
-
}, [isHorizontal, showFirstPanel, showSecondPanel]);
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
67
|
-
if (!isResizingRef.current || !containerRef.current) return;
|
|
68
|
-
|
|
69
|
-
const currentPos = isHorizontal ? e.clientX : e.clientY;
|
|
70
|
-
const delta = currentPos - startPosRef.current; // Dragging right/down increases first panel size
|
|
71
|
-
|
|
72
|
-
let newSize = startSizeRef.current + delta;
|
|
73
|
-
|
|
74
|
-
const containerRect = containerRef.current.getBoundingClientRect();
|
|
75
|
-
const containerTotal = isHorizontal ? containerRect.width : containerRect.height;
|
|
76
|
-
|
|
77
|
-
// Limit the maximum size to prevent pushing the second panel offscreen
|
|
78
|
-
const maxSize = containerTotal - minPanelSizePx;
|
|
79
|
-
|
|
80
|
-
newSize = Math.max(minPanelSizePx, Math.min(newSize, maxSize));
|
|
81
|
-
|
|
82
|
-
// Directly update the DOM for performance while dragging
|
|
83
|
-
if (firstPanelRef.current) {
|
|
84
|
-
firstPanelRef.current.style.flexBasis = `${newSize}px`;
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const handleMouseUp = () => {
|
|
89
|
-
if (isResizingRef.current && containerRef.current && firstPanelRef.current) {
|
|
90
|
-
// Calculate the final percentage
|
|
91
|
-
const containerRect = containerRef.current.getBoundingClientRect();
|
|
92
|
-
const firstPanelRect = firstPanelRef.current.getBoundingClientRect();
|
|
93
|
-
|
|
94
|
-
const containerSize = isHorizontal ? containerRect.width : containerRect.height;
|
|
95
|
-
const finalSize = isHorizontal ? firstPanelRect.width : firstPanelRect.height;
|
|
96
|
-
|
|
97
|
-
// Pre-set the DOM to the exact % value BEFORE re-enabling transitions.
|
|
98
|
-
// This prevents the visual "snap" that occurs when switching from px → %
|
|
99
|
-
// with the CSS transition active.
|
|
100
|
-
if (containerSize > 0) {
|
|
101
|
-
const newPercent = (finalSize / containerSize) * 100;
|
|
102
|
-
firstPanelRef.current.style.flexBasis = `${newPercent}%`;
|
|
103
|
-
onPanelSizeChange(Math.max(0, Math.min(100, newPercent)));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
isResizingRef.current = false;
|
|
107
|
-
setIsResizing(false);
|
|
108
|
-
document.body.style.cursor = "";
|
|
109
|
-
document.body.style.userSelect = "";
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
114
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
115
|
-
|
|
116
|
-
return () => {
|
|
117
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
118
|
-
window.removeEventListener("mouseup", handleMouseUp);
|
|
119
|
-
};
|
|
120
|
-
}, [onPanelSizeChange, isHorizontal, minPanelSizePx]);
|
|
121
|
-
|
|
122
|
-
// Calculate applied size
|
|
123
|
-
const appliedBasis = !showFirstPanel ? "0%" : (showSecondPanel ? `${panelSizePercent}%` : "100%");
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<div
|
|
127
|
-
ref={containerRef}
|
|
128
|
-
className={cls(
|
|
129
|
-
"relative w-full h-full flex overflow-hidden",
|
|
130
|
-
!stacked && (isHorizontal ? "flex-row" : "flex-col"),
|
|
131
|
-
className
|
|
132
|
-
)}
|
|
133
|
-
>
|
|
134
|
-
{/* First Panel */}
|
|
135
|
-
<div
|
|
136
|
-
ref={firstPanelRef}
|
|
137
|
-
className={cls(
|
|
138
|
-
stacked ? "absolute inset-0" : "relative flex-shrink-0",
|
|
139
|
-
"flex flex-col overflow-hidden",
|
|
140
|
-
!stacked && !isResizing && animateLayout && "transition-[flex-basis] duration-150 ease-out",
|
|
141
|
-
!showFirstPanel && "hidden"
|
|
142
|
-
)}
|
|
143
|
-
style={stacked ? undefined : {
|
|
144
|
-
flexBasis: appliedBasis,
|
|
145
|
-
minWidth: isHorizontal && showFirstPanel && showSecondPanel ? `${minPanelSizePx}px` : undefined,
|
|
146
|
-
minHeight: !isHorizontal && showFirstPanel && showSecondPanel ? `${minPanelSizePx}px` : undefined,
|
|
147
|
-
maxWidth: showSecondPanel ? undefined : "100%",
|
|
148
|
-
maxHeight: showSecondPanel ? undefined : "100%"
|
|
149
|
-
}}
|
|
150
|
-
>
|
|
151
|
-
{firstPanel}
|
|
152
|
-
</div>
|
|
153
|
-
|
|
154
|
-
{/* Divider */}
|
|
155
|
-
{!stacked && showFirstPanel && showSecondPanel && (
|
|
156
|
-
<div
|
|
157
|
-
className={cls(
|
|
158
|
-
"relative z-10 flex flex-shrink-0 items-center justify-center",
|
|
159
|
-
defaultBorderMixin,
|
|
160
|
-
isHorizontal ? "border-l w-px h-full cursor-col-resize" : "border-t h-px w-full cursor-row-resize"
|
|
161
|
-
)}
|
|
162
|
-
onMouseDown={handleResizeStart}
|
|
163
|
-
>
|
|
164
|
-
{/* Transparent Hit Area with Pill inside */}
|
|
165
|
-
<div className={cls(
|
|
166
|
-
"absolute flex items-center justify-center group",
|
|
167
|
-
isHorizontal ? "w-4 h-full cursor-col-resize top-0" : "h-4 w-full cursor-row-resize left-0"
|
|
168
|
-
)}>
|
|
169
|
-
<div className={cls(
|
|
170
|
-
"bg-primary/60 dark:bg-primary rounded-full opacity-0 group-hover:opacity-100 transition-all duration-200",
|
|
171
|
-
isHorizontal ? "w-1 h-8" : "h-1 w-8"
|
|
172
|
-
)}/>
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
)}
|
|
176
|
-
|
|
177
|
-
{/* Second Panel */}
|
|
178
|
-
<div
|
|
179
|
-
className={cls(
|
|
180
|
-
stacked ? "absolute inset-0" : "flex-grow relative min-w-0 min-h-0",
|
|
181
|
-
"flex flex-col overflow-hidden",
|
|
182
|
-
!showSecondPanel && "hidden"
|
|
183
|
-
)}
|
|
184
|
-
>
|
|
185
|
-
{secondPanel}
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
"use client";
|
|
3
|
-
import { SearchIcon, XIcon } from "lucide-react";
|
|
4
|
-
import { iconSize } from "../icons/Icon";
|
|
5
|
-
import React, { useCallback, useState } from "react";
|
|
6
|
-
|
|
7
|
-
import { defaultBorderMixin } from "../styles";
|
|
8
|
-
import { CircularProgress } from "./CircularProgress";
|
|
9
|
-
import { IconButton } from "./IconButton";
|
|
10
|
-
|
|
11
|
-
import { cls } from "../util";
|
|
12
|
-
import { useDebounceValue } from "../hooks";
|
|
13
|
-
|
|
14
|
-
interface SearchBarProps {
|
|
15
|
-
onClick?: () => void;
|
|
16
|
-
onTextSearch?: (searchString?: string) => void;
|
|
17
|
-
placeholder?: string;
|
|
18
|
-
expandable?: boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Size of the search bar.
|
|
21
|
-
* - "small": 32px height (matches TextField small)
|
|
22
|
-
* - "medium": 44px height (matches TextField medium)
|
|
23
|
-
* @default "medium"
|
|
24
|
-
*/
|
|
25
|
-
size?: "smallest" | "small" | "medium" | "large";
|
|
26
|
-
innerClassName?: string;
|
|
27
|
-
className?: string;
|
|
28
|
-
autoFocus?: boolean;
|
|
29
|
-
disabled?: boolean;
|
|
30
|
-
loading?: boolean;
|
|
31
|
-
inputRef?: React.Ref<HTMLInputElement>;
|
|
32
|
-
/**
|
|
33
|
-
* Optional initial value for the search input, e.g. from URL params.
|
|
34
|
-
*/
|
|
35
|
-
initialValue?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function SearchBar({
|
|
39
|
-
onClick,
|
|
40
|
-
onTextSearch,
|
|
41
|
-
placeholder = "Search",
|
|
42
|
-
expandable = false,
|
|
43
|
-
size = "medium",
|
|
44
|
-
innerClassName,
|
|
45
|
-
className,
|
|
46
|
-
autoFocus,
|
|
47
|
-
disabled,
|
|
48
|
-
loading,
|
|
49
|
-
inputRef,
|
|
50
|
-
initialValue
|
|
51
|
-
}: SearchBarProps) {
|
|
52
|
-
|
|
53
|
-
const [searchText, setSearchText] = useState<string>(initialValue ?? "");
|
|
54
|
-
const [active, setActive] = useState<boolean>(false);
|
|
55
|
-
|
|
56
|
-
const deferredValues = useDebounceValue(searchText, 200);
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Debounce on SearchIcon text update
|
|
60
|
-
*/
|
|
61
|
-
React.useEffect(() => {
|
|
62
|
-
if (!onTextSearch) return;
|
|
63
|
-
if (deferredValues) {
|
|
64
|
-
onTextSearch(deferredValues);
|
|
65
|
-
} else {
|
|
66
|
-
onTextSearch(undefined);
|
|
67
|
-
}
|
|
68
|
-
}, [deferredValues]);
|
|
69
|
-
|
|
70
|
-
const clearText = useCallback(() => {
|
|
71
|
-
if (!onTextSearch) return;
|
|
72
|
-
setSearchText("");
|
|
73
|
-
onTextSearch(undefined);
|
|
74
|
-
}, [onTextSearch]);
|
|
75
|
-
|
|
76
|
-
// Height classes matching TextField sizes
|
|
77
|
-
// Heights come from the shared control scale (styles.ts) so a SearchBar
|
|
78
|
-
// lines up with the Button and Select beside it in a toolbar.
|
|
79
|
-
const heightClass = { smallest: "h-[28px]", small: "h-[32px]", medium: "h-[40px]", large: "h-[48px]" }[size];
|
|
80
|
-
const iconPaddingClass = size === "smallest" || size === "small" ? "px-2" : size === "medium" ? "px-3" : "px-4";
|
|
81
|
-
const inputPaddingClass = size === "smallest" || size === "small" ? "pl-8" : size === "medium" ? "pl-10" : "pl-12";
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<div
|
|
85
|
-
role="search"
|
|
86
|
-
aria-label="Search"
|
|
87
|
-
onClick={onClick}
|
|
88
|
-
className={cls("relative transition-all",
|
|
89
|
-
heightClass,
|
|
90
|
-
"bg-surface-accent-50 dark:bg-surface-900 border",
|
|
91
|
-
defaultBorderMixin,
|
|
92
|
-
"focus-within:ring-1 focus-within:ring-primary/60 focus-within:border-primary focus-within:shadow-[0_0_0_3px_rgba(0,112,244,0.1)]",
|
|
93
|
-
"rounded-lg overflow-hidden",
|
|
94
|
-
className)}>
|
|
95
|
-
<div
|
|
96
|
-
className={cls("absolute p-0 h-full pointer-events-none flex items-center justify-center top-0", iconPaddingClass)}>
|
|
97
|
-
{loading ? <CircularProgress size={"smallest"}/> : <SearchIcon className={"text-text-disabled dark:text-text-disabled-dark"} size={size === "smallest" ? 14 : size === "small" ? 18 : 20}/>}
|
|
98
|
-
</div>
|
|
99
|
-
<input
|
|
100
|
-
value={searchText ?? ""}
|
|
101
|
-
ref={inputRef}
|
|
102
|
-
onClick={onClick}
|
|
103
|
-
placeholder={placeholder}
|
|
104
|
-
aria-label={placeholder}
|
|
105
|
-
readOnly={!onTextSearch}
|
|
106
|
-
onChange={onTextSearch
|
|
107
|
-
? (event) => {
|
|
108
|
-
setSearchText(event.target.value);
|
|
109
|
-
}
|
|
110
|
-
: undefined}
|
|
111
|
-
autoFocus={autoFocus}
|
|
112
|
-
onFocus={() => setActive(true)}
|
|
113
|
-
onBlur={() => setActive(false)}
|
|
114
|
-
className={cls(
|
|
115
|
-
(disabled || loading) && "pointer-events-none",
|
|
116
|
-
"placeholder-text-disabled dark:placeholder-text-disabled-dark",
|
|
117
|
-
"relative flex items-center transition-all bg-transparent outline-none focus:outline-none focus:ring-0 appearance-none border-none focus:border-transparent",
|
|
118
|
-
inputPaddingClass, "h-full w-full text-current",
|
|
119
|
-
size === "small" ? "text-sm" : "",
|
|
120
|
-
expandable ? (active ? "w-[220px]" : "w-[180px]") : "",
|
|
121
|
-
innerClassName
|
|
122
|
-
)}
|
|
123
|
-
/>
|
|
124
|
-
{searchText
|
|
125
|
-
? <IconButton
|
|
126
|
-
className={`${size === "small" ? "mr-0 top-0" : "mr-1 top-0"} absolute right-0 z-10`}
|
|
127
|
-
size={"small"}
|
|
128
|
-
aria-label="Clear search"
|
|
129
|
-
onClick={clearText}>
|
|
130
|
-
<XIcon size={iconSize.smallest}/>
|
|
131
|
-
</IconButton>
|
|
132
|
-
: <div style={{ width: 26 }}/>
|
|
133
|
-
}
|
|
134
|
-
</div>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
"use client";
|
|
3
|
-
import { CheckIcon, ChevronDownIcon } from "lucide-react";
|
|
4
|
-
import { iconSize } from "../icons/Icon";
|
|
5
|
-
import React, { ChangeEvent, Children, forwardRef, useCallback, useEffect, useMemo, useState } from "react";
|
|
6
|
-
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
7
|
-
import {
|
|
8
|
-
defaultBorderMixin,
|
|
9
|
-
fieldBackgroundDisabledMixin,
|
|
10
|
-
fieldBackgroundHoverMixin,
|
|
11
|
-
fieldBackgroundInvisibleMixin,
|
|
12
|
-
fieldBackgroundMixin,
|
|
13
|
-
focusedDisabled
|
|
14
|
-
} from "../styles";
|
|
15
|
-
|
|
16
|
-
import { cls } from "../util";
|
|
17
|
-
import { SelectInputLabel } from "./common/SelectInputLabel";
|
|
18
|
-
import { usePortalContainer } from "../hooks/PortalContainerContext";
|
|
19
|
-
|
|
20
|
-
export type SelectValue = string | number | boolean;
|
|
21
|
-
|
|
22
|
-
export type SelectProps<T extends SelectValue = string> = {
|
|
23
|
-
open?: boolean,
|
|
24
|
-
name?: string,
|
|
25
|
-
fullWidth?: boolean,
|
|
26
|
-
id?: string,
|
|
27
|
-
onOpenChange?: (open: boolean) => void,
|
|
28
|
-
value?: T,
|
|
29
|
-
className?: string,
|
|
30
|
-
inputClassName?: string,
|
|
31
|
-
viewportClassName?: string,
|
|
32
|
-
onChange?: React.EventHandler<ChangeEvent<HTMLSelectElement>>,
|
|
33
|
-
onValueChange?: (updatedValue: T) => void,
|
|
34
|
-
placeholder?: React.ReactNode,
|
|
35
|
-
renderValue?: (value: T) => React.ReactNode,
|
|
36
|
-
size?: "smallest" | "small" | "medium" | "large",
|
|
37
|
-
label?: React.ReactNode | string,
|
|
38
|
-
disabled?: boolean,
|
|
39
|
-
error?: boolean,
|
|
40
|
-
position?: "item-aligned" | "popper",
|
|
41
|
-
endAdornment?: React.ReactNode,
|
|
42
|
-
inputRef?: React.Ref<HTMLButtonElement>,
|
|
43
|
-
padding?: boolean,
|
|
44
|
-
invisible?: boolean,
|
|
45
|
-
children?: React.ReactNode;
|
|
46
|
-
dataType?: "string" | "number" | "boolean";
|
|
47
|
-
portalContainer?: HTMLElement | null; // Explicitly added to props type if missing
|
|
48
|
-
/**
|
|
49
|
-
* Accessible name for the trigger, for a select whose `label` is a node
|
|
50
|
-
* rather than a string — or which shows no label at all.
|
|
51
|
-
*
|
|
52
|
-
* Without it such a select announces the literal fallback "Select an
|
|
53
|
-
* option", which is indistinguishable from every other unlabelled select
|
|
54
|
-
* on the screen.
|
|
55
|
-
*/
|
|
56
|
-
"aria-label"?: string;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
60
|
-
inputRef,
|
|
61
|
-
open,
|
|
62
|
-
name,
|
|
63
|
-
fullWidth = false,
|
|
64
|
-
id,
|
|
65
|
-
onOpenChange,
|
|
66
|
-
value,
|
|
67
|
-
onChange,
|
|
68
|
-
onValueChange,
|
|
69
|
-
className,
|
|
70
|
-
inputClassName,
|
|
71
|
-
viewportClassName,
|
|
72
|
-
placeholder,
|
|
73
|
-
renderValue,
|
|
74
|
-
label,
|
|
75
|
-
size = "large",
|
|
76
|
-
error,
|
|
77
|
-
disabled,
|
|
78
|
-
padding = true,
|
|
79
|
-
position = "item-aligned",
|
|
80
|
-
endAdornment,
|
|
81
|
-
invisible,
|
|
82
|
-
children,
|
|
83
|
-
dataType = "string",
|
|
84
|
-
portalContainer: manualContainer, // Rename to avoid confusion
|
|
85
|
-
"aria-label": ariaLabel,
|
|
86
|
-
...props
|
|
87
|
-
}, ref) => {
|
|
88
|
-
|
|
89
|
-
const [openInternal, setOpenInternal] = useState(open ?? false);
|
|
90
|
-
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
setOpenInternal(open ?? false);
|
|
93
|
-
}, [open]);
|
|
94
|
-
|
|
95
|
-
// Get the portal container from context
|
|
96
|
-
const contextContainer = usePortalContainer();
|
|
97
|
-
|
|
98
|
-
// Resolve final container (Manual Prop > Context Container > Undefined/Body)
|
|
99
|
-
const finalContainer = (manualContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;
|
|
100
|
-
|
|
101
|
-
const onValueChangeInternal = useCallback((newValue: string) => {
|
|
102
|
-
let typedValue: SelectValue = newValue;
|
|
103
|
-
if (dataType === "boolean") {
|
|
104
|
-
if (newValue === "true") typedValue = true;
|
|
105
|
-
else if (newValue === "false") typedValue = false;
|
|
106
|
-
} else if (dataType === "number") {
|
|
107
|
-
if (!isNaN(Number(newValue)) && newValue.trim() !== "") typedValue = Number(newValue);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
onValueChange?.(typedValue as SelectValue & typeof value);
|
|
111
|
-
if (onChange) {
|
|
112
|
-
const event = {
|
|
113
|
-
target: {
|
|
114
|
-
name,
|
|
115
|
-
value: typedValue
|
|
116
|
-
}
|
|
117
|
-
} as ChangeEvent<HTMLSelectElement>;
|
|
118
|
-
onChange(event);
|
|
119
|
-
}
|
|
120
|
-
}, [onChange, onValueChange, name, dataType]);
|
|
121
|
-
|
|
122
|
-
const hasValue = Array.isArray(value) ? value.length > 0 : (value != null && value !== "" && value !== undefined);
|
|
123
|
-
const stringValue = value !== undefined ? String(value) : undefined;
|
|
124
|
-
|
|
125
|
-
const displayChildren = useMemo(() => {
|
|
126
|
-
if (!hasValue || renderValue) return null;
|
|
127
|
-
|
|
128
|
-
// Find the child that matches the current value to display its content
|
|
129
|
-
let found: React.ReactNode = null;
|
|
130
|
-
Children.forEach(children, (child) => {
|
|
131
|
-
if (React.isValidElement<SelectItemProps>(child) && String(child.props.value) === String(value)) {
|
|
132
|
-
found = child.props.children;
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
return found;
|
|
136
|
-
}, [children, hasValue, renderValue, value]);
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<SelectPrimitive.Root
|
|
140
|
-
name={name}
|
|
141
|
-
value={stringValue}
|
|
142
|
-
open={openInternal}
|
|
143
|
-
disabled={disabled}
|
|
144
|
-
onValueChange={onValueChangeInternal}
|
|
145
|
-
onOpenChange={(open) => {
|
|
146
|
-
onOpenChange?.(open);
|
|
147
|
-
setOpenInternal(open);
|
|
148
|
-
}}
|
|
149
|
-
{...props}>
|
|
150
|
-
{typeof label === "string" ? <SelectInputLabel error={error}>{label}</SelectInputLabel> : label}
|
|
151
|
-
<div className={cls(
|
|
152
|
-
"select-none rounded-lg text-sm",
|
|
153
|
-
invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin,
|
|
154
|
-
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
155
|
-
"relative flex items-center",
|
|
156
|
-
className,
|
|
157
|
-
{
|
|
158
|
-
"min-h-[28px]": size === "smallest",
|
|
159
|
-
"min-h-[32px]": size === "small",
|
|
160
|
-
"min-h-[40px]": size === "medium",
|
|
161
|
-
"min-h-[48px]": size === "large",
|
|
162
|
-
"w-fit": !fullWidth,
|
|
163
|
-
"w-full": fullWidth
|
|
164
|
-
}
|
|
165
|
-
)}>
|
|
166
|
-
<SelectPrimitive.Trigger
|
|
167
|
-
ref={inputRef}
|
|
168
|
-
id={id}
|
|
169
|
-
asChild={false}
|
|
170
|
-
type="button"
|
|
171
|
-
aria-label={ariaLabel ?? (typeof label === "string" ? label : (typeof renderValue === "string" ? renderValue : "Select an option"))}
|
|
172
|
-
aria-invalid={error || undefined}
|
|
173
|
-
className={cls(
|
|
174
|
-
"h-full",
|
|
175
|
-
padding ? {
|
|
176
|
-
"px-4": size === "large",
|
|
177
|
-
"px-3": size === "medium",
|
|
178
|
-
"px-2": size === "small" || size === "smallest"
|
|
179
|
-
} : "",
|
|
180
|
-
"outline-hidden focus:outline-hidden",
|
|
181
|
-
"outline-none focus:outline-none",
|
|
182
|
-
"select-none rounded-lg text-sm",
|
|
183
|
-
error ? "text-red-500 dark:text-red-600" : "focus:text-text-primary dark:focus:text-text-primary-dark",
|
|
184
|
-
error ? "border border-red-500 dark:border-red-600" : "",
|
|
185
|
-
disabled ? "text-surface-accent-600 dark:text-surface-accent-400" : "text-surface-accent-800 dark:text-white",
|
|
186
|
-
"relative flex flex-row items-center",
|
|
187
|
-
{
|
|
188
|
-
"min-h-[28px]": size === "smallest",
|
|
189
|
-
"min-h-[32px]": size === "small",
|
|
190
|
-
"min-h-[40px]": size === "medium",
|
|
191
|
-
"min-h-[48px]": size === "large",
|
|
192
|
-
"w-full": fullWidth,
|
|
193
|
-
"w-fit": !fullWidth
|
|
194
|
-
},
|
|
195
|
-
inputClassName
|
|
196
|
-
)}
|
|
197
|
-
>
|
|
198
|
-
<div
|
|
199
|
-
ref={ref}
|
|
200
|
-
className={cls(
|
|
201
|
-
"flex-grow max-w-full flex flex-row gap-2 items-center",
|
|
202
|
-
"overflow-visible",
|
|
203
|
-
{
|
|
204
|
-
"min-h-[28px]": size === "smallest",
|
|
205
|
-
"min-h-[32px]": size === "small",
|
|
206
|
-
"min-h-[40px]": size === "medium",
|
|
207
|
-
"min-h-[48px]": size === "large"
|
|
208
|
-
}
|
|
209
|
-
)}>
|
|
210
|
-
<SelectPrimitive.Value
|
|
211
|
-
onClick={(e) => {
|
|
212
|
-
e.preventDefault();
|
|
213
|
-
e.stopPropagation();
|
|
214
|
-
}}
|
|
215
|
-
placeholder={placeholder}
|
|
216
|
-
className={"w-full"}>
|
|
217
|
-
|
|
218
|
-
{hasValue && value !== undefined && renderValue
|
|
219
|
-
? renderValue(value)
|
|
220
|
-
: (displayChildren || placeholder)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
</SelectPrimitive.Value>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
<SelectPrimitive.Icon asChild>
|
|
227
|
-
<ChevronDownIcon size={size === "large" ? iconSize.medium : iconSize.small}
|
|
228
|
-
className={cls("transition", open ? "rotate-180" : "", {
|
|
229
|
-
"px-2": size === "large",
|
|
230
|
-
"px-1": size === "medium" || size === "small"
|
|
231
|
-
})}/>
|
|
232
|
-
</SelectPrimitive.Icon>
|
|
233
|
-
</SelectPrimitive.Trigger>
|
|
234
|
-
|
|
235
|
-
{endAdornment && (
|
|
236
|
-
<div
|
|
237
|
-
className={cls("h-full flex items-center absolute right-0 pr-12")}
|
|
238
|
-
onClick={(e) => {
|
|
239
|
-
e.preventDefault();
|
|
240
|
-
e.stopPropagation();
|
|
241
|
-
}}>
|
|
242
|
-
{endAdornment}
|
|
243
|
-
</div>
|
|
244
|
-
)}
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
{/* Pass the calculated finalContainer */}
|
|
248
|
-
<SelectPrimitive.Portal container={finalContainer}>
|
|
249
|
-
<SelectPrimitive.Content position={position}
|
|
250
|
-
className={cls(focusedDisabled, "z-50 relative overflow-hidden border bg-white dark:bg-surface-800 p-2 rounded-lg shadow-lg animate-in fade-in-0 zoom-in-95 duration-150", defaultBorderMixin)}>
|
|
251
|
-
<SelectPrimitive.Viewport className={cls("p-1", viewportClassName)}
|
|
252
|
-
style={{ maxHeight: "var(--radix-select-content-available-height)" }}>
|
|
253
|
-
{children}
|
|
254
|
-
</SelectPrimitive.Viewport>
|
|
255
|
-
</SelectPrimitive.Content>
|
|
256
|
-
</SelectPrimitive.Portal>
|
|
257
|
-
</SelectPrimitive.Root>
|
|
258
|
-
);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
Select.displayName = "Select";
|
|
262
|
-
|
|
263
|
-
// ... (SelectItem and SelectGroup remain unchanged)
|
|
264
|
-
export type SelectItemProps<T extends SelectValue = string> = {
|
|
265
|
-
value: T,
|
|
266
|
-
children?: React.ReactNode,
|
|
267
|
-
disabled?: boolean,
|
|
268
|
-
className?: string,
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
export const SelectItem = React.memo(function SelectItem<T extends SelectValue = string>({
|
|
272
|
-
value,
|
|
273
|
-
children,
|
|
274
|
-
disabled,
|
|
275
|
-
className
|
|
276
|
-
}: SelectItemProps<T>) {
|
|
277
|
-
// Convert value to string for Radix UI
|
|
278
|
-
const stringValue = String(value);
|
|
279
|
-
|
|
280
|
-
return <SelectPrimitive.Item
|
|
281
|
-
key={stringValue}
|
|
282
|
-
value={stringValue}
|
|
283
|
-
disabled={disabled}
|
|
284
|
-
className={cls(
|
|
285
|
-
"w-full",
|
|
286
|
-
"relative flex items-center p-2 rounded-md text-sm text-surface-accent-700 dark:text-surface-accent-300",
|
|
287
|
-
"focus:z-10",
|
|
288
|
-
"data-[state=checked]:bg-surface-accent-100 data-[state=checked]:dark:bg-surface-accent-800 focus:bg-surface-accent-100 dark:focus:bg-surface-900",
|
|
289
|
-
"data-[state=checked]:focus:bg-surface-accent-200 data-[state=checked]:dark:focus:bg-surface-900",
|
|
290
|
-
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
|
291
|
-
"[&>*]:w-full",
|
|
292
|
-
"overflow-visible",
|
|
293
|
-
className
|
|
294
|
-
)}>
|
|
295
|
-
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
296
|
-
<div
|
|
297
|
-
className="absolute left-1 data-[state=checked]:block hidden">
|
|
298
|
-
<CheckIcon size={iconSize.smallest}/>
|
|
299
|
-
</div>
|
|
300
|
-
</SelectPrimitive.Item>;
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
export type SelectGroupProps = {
|
|
304
|
-
label: React.ReactNode,
|
|
305
|
-
children: React.ReactNode,
|
|
306
|
-
className?: string
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
export const SelectGroup = React.memo(function SelectGroup({
|
|
310
|
-
label,
|
|
311
|
-
children,
|
|
312
|
-
className
|
|
313
|
-
}: SelectGroupProps) {
|
|
314
|
-
return <>
|
|
315
|
-
<SelectPrimitive.Group
|
|
316
|
-
className={cls(
|
|
317
|
-
"text-xs text-surface-accent-900 dark:text-white uppercase tracking-wider font-semibold mt-6 first:mt-2",
|
|
318
|
-
"px-2 py-2",
|
|
319
|
-
className
|
|
320
|
-
)}>
|
|
321
|
-
{label}
|
|
322
|
-
</SelectPrimitive.Group>
|
|
323
|
-
|
|
324
|
-
{children}
|
|
325
|
-
</>;
|
|
326
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
2
|
-
import { cls } from "../util";
|
|
3
|
-
|
|
4
|
-
export function Separator({
|
|
5
|
-
orientation,
|
|
6
|
-
decorative,
|
|
7
|
-
className
|
|
8
|
-
}: {
|
|
9
|
-
orientation: "horizontal" | "vertical",
|
|
10
|
-
decorative?: boolean,
|
|
11
|
-
className?: string
|
|
12
|
-
}) {
|
|
13
|
-
if (orientation === "horizontal")
|
|
14
|
-
return (
|
|
15
|
-
<SeparatorPrimitive.Root
|
|
16
|
-
decorative={decorative}
|
|
17
|
-
orientation="horizontal"
|
|
18
|
-
className={cls("dark:bg-surface-700 bg-surface-200 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px my-4", className)}/>
|
|
19
|
-
);
|
|
20
|
-
else
|
|
21
|
-
return (
|
|
22
|
-
<SeparatorPrimitive.Root
|
|
23
|
-
className={cls("dark:bg-surface-700 bg-surface-200 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px mx-4", className)}
|
|
24
|
-
decorative={decorative}
|
|
25
|
-
orientation="vertical"
|
|
26
|
-
/>
|
|
27
|
-
);
|
|
28
|
-
}
|