@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.
Files changed (127) hide show
  1. package/README.md +4 -0
  2. package/dist/components/MultiSelect.d.ts +10 -0
  3. package/dist/index.es.js +2 -1
  4. package/dist/index.es.js.map +1 -1
  5. package/package.json +32 -23
  6. package/src/components/Alert.tsx +0 -118
  7. package/src/components/Autocomplete.tsx +0 -93
  8. package/src/components/Avatar.tsx +0 -73
  9. package/src/components/Badge.tsx +0 -44
  10. package/src/components/BooleanSwitch.tsx +0 -116
  11. package/src/components/BooleanSwitchWithLabel.tsx +0 -120
  12. package/src/components/Button.tsx +0 -110
  13. package/src/components/Card.tsx +0 -43
  14. package/src/components/CenteredView.tsx +0 -35
  15. package/src/components/Checkbox.tsx +0 -129
  16. package/src/components/Chip.tsx +0 -131
  17. package/src/components/CircularProgress.tsx +0 -53
  18. package/src/components/CircularProgressCenter.tsx +0 -27
  19. package/src/components/Collapse.tsx +0 -66
  20. package/src/components/ColorPicker.tsx +0 -140
  21. package/src/components/Container.tsx +0 -51
  22. package/src/components/DateTimeField.tsx +0 -363
  23. package/src/components/DebouncedTextField.tsx +0 -76
  24. package/src/components/Dialog.tsx +0 -159
  25. package/src/components/DialogActions.tsx +0 -29
  26. package/src/components/DialogContent.tsx +0 -27
  27. package/src/components/DialogTitle.tsx +0 -41
  28. package/src/components/ErrorBoundary.tsx +0 -236
  29. package/src/components/ExpandablePanel.tsx +0 -130
  30. package/src/components/FileUpload.tsx +0 -103
  31. package/src/components/FilterChip.tsx +0 -89
  32. package/src/components/IconButton.tsx +0 -101
  33. package/src/components/InfoLabel.tsx +0 -23
  34. package/src/components/InputLabel.tsx +0 -40
  35. package/src/components/Label.tsx +0 -32
  36. package/src/components/LoadingButton.tsx +0 -32
  37. package/src/components/Markdown.tsx +0 -55
  38. package/src/components/Menu.tsx +0 -114
  39. package/src/components/Menubar.tsx +0 -355
  40. package/src/components/MultiSelect.tsx +0 -433
  41. package/src/components/Paper.tsx +0 -23
  42. package/src/components/Popover.tsx +0 -162
  43. package/src/components/RadioGroup.tsx +0 -74
  44. package/src/components/ResizablePanels.tsx +0 -189
  45. package/src/components/SearchBar.tsx +0 -136
  46. package/src/components/Select.tsx +0 -326
  47. package/src/components/Separator.tsx +0 -28
  48. package/src/components/Sheet.tsx +0 -135
  49. package/src/components/Skeleton.tsx +0 -54
  50. package/src/components/Slider.tsx +0 -110
  51. package/src/components/Table.tsx +0 -178
  52. package/src/components/Tabs.tsx +0 -185
  53. package/src/components/TextField.tsx +0 -270
  54. package/src/components/TextareaAutosize.tsx +0 -368
  55. package/src/components/ToggleButtonGroup.tsx +0 -69
  56. package/src/components/Tooltip.tsx +0 -153
  57. package/src/components/Typography.tsx +0 -132
  58. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
  59. package/src/components/VirtualTable/VirtualTable.tsx +0 -700
  60. package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
  61. package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
  62. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
  63. package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
  64. package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
  65. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
  66. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
  67. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
  68. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
  69. package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
  70. package/src/components/VirtualTable/index.tsx +0 -10
  71. package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
  72. package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
  73. package/src/components/VirtualTable/types.tsx +0 -47
  74. package/src/components/common/SelectInputLabel.tsx +0 -23
  75. package/src/components/index.tsx +0 -62
  76. package/src/hooks/PortalContainerContext.tsx +0 -50
  77. package/src/hooks/index.ts +0 -7
  78. package/src/hooks/useDebounceCallback.tsx +0 -21
  79. package/src/hooks/useDebounceValue.tsx +0 -20
  80. package/src/hooks/useDebouncedCallback.ts +0 -39
  81. package/src/hooks/useInjectStyles.tsx +0 -31
  82. package/src/hooks/useOutsideAlerter.tsx +0 -46
  83. package/src/icons/GitHubIcon.tsx +0 -21
  84. package/src/icons/HandleIcon.tsx +0 -23
  85. package/src/icons/Icon.tsx +0 -33
  86. package/src/icons/LucideIconByName.tsx +0 -117
  87. package/src/icons/cool_icon_keys.ts +0 -12
  88. package/src/icons/icon_keys.ts +0 -1936
  89. package/src/icons/index.ts +0 -179
  90. package/src/index.css +0 -169
  91. package/src/index.ts +0 -47
  92. package/src/scripts/fixIconSizes.cjs +0 -92
  93. package/src/scripts/fixIconSizesToConst.cjs +0 -106
  94. package/src/scripts/generateIconKeys.ts +0 -41
  95. package/src/scripts/generateIcons.ts +0 -4
  96. package/src/scripts/saveIconFiles.ts +0 -89
  97. package/src/styles.ts +0 -141
  98. package/src/theme.css +0 -168
  99. package/src/util/chip_colors.ts +0 -242
  100. package/src/util/cls.ts +0 -8
  101. package/src/util/debounce.ts +0 -28
  102. package/src/util/hash.ts +0 -11
  103. package/src/util/index.ts +0 -4
  104. package/src/util/key_to_icon_component.ts +0 -11
  105. package/src/util/lazy_chunk.ts +0 -101
  106. package/src/views/CardView.tsx +0 -281
  107. package/src/views/CollectionView/CollectionCardView.tsx +0 -265
  108. package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
  109. package/src/views/CollectionView/CollectionListView.tsx +0 -240
  110. package/src/views/CollectionView/CollectionTableView.tsx +0 -229
  111. package/src/views/CollectionView/CollectionView.tsx +0 -450
  112. package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
  113. package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
  114. package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
  115. package/src/views/CollectionView/index.ts +0 -39
  116. package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
  117. package/src/views/CollectionView/utils.ts +0 -14
  118. package/src/views/Kanban/Board.tsx +0 -384
  119. package/src/views/Kanban/BoardColumn.tsx +0 -138
  120. package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
  121. package/src/views/Kanban/BoardSortableList.tsx +0 -182
  122. package/src/views/Kanban/board_types.ts +0 -80
  123. package/src/views/Kanban/index.tsx +0 -5
  124. package/src/views/Kanban/placement.ts +0 -62
  125. package/src/views/KanbanView.tsx +0 -32
  126. package/src/views/ListView.tsx +0 -296
  127. 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
- }