@firecms/ui 3.1.0-canary.1df3b2c → 3.1.0-canary.501d471
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/BooleanSwitchWithLabel.d.ts +2 -1
- package/dist/components/Chip.d.ts +1 -1
- package/dist/components/MultiSelect.d.ts +1 -1
- package/dist/components/ResizablePanels.d.ts +16 -0
- package/dist/components/SearchableSelect.d.ts +48 -0
- package/dist/components/Select.d.ts +1 -1
- package/dist/components/Tabs.d.ts +8 -1
- package/dist/components/Tooltip.d.ts +18 -2
- package/dist/components/index.d.ts +2 -0
- package/dist/hooks/useOutsideAlerter.d.ts +1 -1
- package/dist/icons/FirestoreIcon.d.ts +6 -0
- package/dist/icons/components/DatabaseIcon.d.ts +6 -0
- package/dist/icons/index.d.ts +2 -0
- package/dist/index.es.js +1444 -431
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1446 -433
- package/dist/index.umd.js.map +1 -1
- package/package.json +6 -6
- package/src/components/BooleanSwitchWithLabel.tsx +4 -0
- package/src/components/Button.tsx +2 -1
- package/src/components/Chip.tsx +4 -3
- package/src/components/DateTimeField.tsx +7 -2
- package/src/components/DebouncedTextField.tsx +3 -3
- package/src/components/MultiSelect.tsx +27 -10
- package/src/components/ResizablePanels.tsx +181 -0
- package/src/components/SearchableSelect.tsx +335 -0
- package/src/components/Select.tsx +62 -62
- package/src/components/Skeleton.tsx +4 -2
- package/src/components/Tabs.tsx +150 -34
- package/src/components/TextareaAutosize.tsx +77 -212
- package/src/components/Tooltip.tsx +7 -6
- package/src/components/index.tsx +2 -0
- package/src/hooks/useOutsideAlerter.tsx +1 -1
- package/src/icons/FirestoreIcon.tsx +47 -0
- package/src/icons/components/DatabaseIcon.tsx +10 -0
- package/src/icons/index.ts +2 -0
package/src/components/Tabs.tsx
CHANGED
|
@@ -1,34 +1,137 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { createContext, useContext, useRef, useState, useEffect } from "react";
|
|
2
2
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
3
3
|
import { cls } from "../util";
|
|
4
4
|
import { defaultBorderMixin } from "../styles";
|
|
5
|
+
import { IconButton } from "./IconButton";
|
|
6
|
+
import { ChevronLeftIcon, ChevronRightIcon } from "../icons";
|
|
7
|
+
|
|
8
|
+
type TabsMode = "primary" | "secondary";
|
|
9
|
+
const TabsModeContext = createContext<TabsMode>("primary");
|
|
5
10
|
|
|
6
11
|
export type TabsProps = {
|
|
7
12
|
value: string,
|
|
8
13
|
children: React.ReactNode,
|
|
9
14
|
innerClassName?: string,
|
|
10
15
|
className?: string,
|
|
11
|
-
onValueChange: (value: string) => void
|
|
16
|
+
onValueChange: (value: string) => void,
|
|
17
|
+
/**
|
|
18
|
+
* "primary" renders the default pill-style tabs.
|
|
19
|
+
* "secondary" renders underline-style tabs suitable for inner/nested panels.
|
|
20
|
+
*/
|
|
21
|
+
mode?: TabsMode
|
|
12
22
|
};
|
|
13
23
|
|
|
14
24
|
export function Tabs({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{
|
|
30
|
-
|
|
25
|
+
value,
|
|
26
|
+
onValueChange,
|
|
27
|
+
className,
|
|
28
|
+
innerClassName,
|
|
29
|
+
children,
|
|
30
|
+
mode = "primary"
|
|
31
|
+
}: TabsProps) {
|
|
32
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
33
|
+
const [showLeftScroll, setShowLeftScroll] = useState(false);
|
|
34
|
+
const [showRightScroll, setShowRightScroll] = useState(false);
|
|
35
|
+
const [isScrollable, setIsScrollable] = useState(false);
|
|
36
|
+
|
|
37
|
+
const checkScroll = () => {
|
|
38
|
+
if (scrollContainerRef.current) {
|
|
39
|
+
const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
|
|
40
|
+
setShowLeftScroll(scrollLeft > 0);
|
|
41
|
+
setShowRightScroll(Math.ceil(scrollLeft + clientWidth) < scrollWidth);
|
|
42
|
+
setIsScrollable(scrollWidth > clientWidth);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
checkScroll();
|
|
48
|
+
window.addEventListener("resize", checkScroll);
|
|
49
|
+
|
|
50
|
+
let observer: ResizeObserver;
|
|
51
|
+
if (scrollContainerRef.current) {
|
|
52
|
+
observer = new ResizeObserver(checkScroll);
|
|
53
|
+
observer.observe(scrollContainerRef.current);
|
|
54
|
+
if (scrollContainerRef.current.firstElementChild) {
|
|
55
|
+
observer.observe(scrollContainerRef.current.firstElementChild);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
window.removeEventListener("resize", checkScroll);
|
|
61
|
+
observer?.disconnect();
|
|
62
|
+
};
|
|
63
|
+
}, [children]);
|
|
64
|
+
|
|
65
|
+
const scroll = (direction: "left" | "right") => {
|
|
66
|
+
if (scrollContainerRef.current) {
|
|
67
|
+
const container = scrollContainerRef.current;
|
|
68
|
+
const scrollAmount = Math.max(container.clientWidth / 2, 200);
|
|
69
|
+
const targetScroll = container.scrollLeft + (direction === "left" ? -scrollAmount : scrollAmount);
|
|
70
|
+
|
|
71
|
+
container.scrollTo({
|
|
72
|
+
left: targetScroll,
|
|
73
|
+
behavior: "smooth"
|
|
74
|
+
});
|
|
75
|
+
// checkScroll will be called by onScroll event
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return <TabsModeContext.Provider value={mode}>
|
|
80
|
+
<TabsPrimitive.Root value={value} onValueChange={onValueChange} className={cls("flex flex-row items-center min-w-0", className)}>
|
|
81
|
+
{isScrollable && (
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
disabled={!showLeftScroll}
|
|
85
|
+
onClick={() => scroll("left")}
|
|
86
|
+
className={cls(
|
|
87
|
+
"flex-shrink-0 z-10 flex items-center justify-center rounded-md px-0.5 py-1.5 transition-all h-10 w-6",
|
|
88
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400",
|
|
89
|
+
"disabled:pointer-events-none disabled:opacity-0",
|
|
90
|
+
"text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800",
|
|
91
|
+
mode === "primary" && "mr-1 bg-surface-50 dark:bg-surface-900 border",
|
|
92
|
+
mode === "primary" && defaultBorderMixin,
|
|
93
|
+
mode === "secondary" && "mr-1"
|
|
94
|
+
)}
|
|
95
|
+
>
|
|
96
|
+
<ChevronLeftIcon size="small" />
|
|
97
|
+
</button>
|
|
98
|
+
)}
|
|
99
|
+
<div
|
|
100
|
+
ref={scrollContainerRef}
|
|
101
|
+
className="flex-1 overflow-x-auto no-scrollbar min-w-0"
|
|
102
|
+
onScroll={checkScroll}
|
|
103
|
+
style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
|
|
104
|
+
>
|
|
105
|
+
<TabsPrimitive.List className={cls(
|
|
106
|
+
mode === "primary" && "border",
|
|
107
|
+
mode === "primary" && defaultBorderMixin,
|
|
108
|
+
mode === "primary" && "gap-2 inline-flex h-10 items-center justify-center rounded-md bg-surface-50 p-1 text-surface-600 dark:bg-surface-900 dark:text-surface-400",
|
|
109
|
+
mode === "secondary" && "gap-1 inline-flex h-9 items-center text-surface-500 dark:text-surface-400",
|
|
110
|
+
innerClassName)
|
|
111
|
+
}>
|
|
112
|
+
{children}
|
|
113
|
+
</TabsPrimitive.List>
|
|
114
|
+
</div>
|
|
115
|
+
{isScrollable && (
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
disabled={!showRightScroll}
|
|
119
|
+
onClick={() => scroll("right")}
|
|
120
|
+
className={cls(
|
|
121
|
+
"flex-shrink-0 z-10 flex items-center justify-center rounded-md px-0.5 py-1.5 transition-all h-10 w-6",
|
|
122
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400",
|
|
123
|
+
"disabled:pointer-events-none disabled:opacity-0",
|
|
124
|
+
"text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800",
|
|
125
|
+
mode === "primary" && "ml-1 bg-surface-50 dark:bg-surface-900 border",
|
|
126
|
+
mode === "primary" && defaultBorderMixin,
|
|
127
|
+
mode === "secondary" && "ml-1"
|
|
128
|
+
)}
|
|
129
|
+
>
|
|
130
|
+
<ChevronRightIcon size="small" />
|
|
131
|
+
</button>
|
|
132
|
+
)}
|
|
31
133
|
</TabsPrimitive.Root>
|
|
134
|
+
</TabsModeContext.Provider>
|
|
32
135
|
}
|
|
33
136
|
|
|
34
137
|
export type TabProps = {
|
|
@@ -40,23 +143,36 @@ export type TabProps = {
|
|
|
40
143
|
};
|
|
41
144
|
|
|
42
145
|
export function Tab({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
146
|
+
value,
|
|
147
|
+
className,
|
|
148
|
+
innerClassName,
|
|
149
|
+
children,
|
|
150
|
+
disabled
|
|
151
|
+
}: TabProps) {
|
|
152
|
+
const mode = useContext(TabsModeContext);
|
|
153
|
+
|
|
154
|
+
const primaryClasses = cls(
|
|
155
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-white transition-all",
|
|
156
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400 focus-visible:ring-offset-2",
|
|
157
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
158
|
+
"data-[state=active]:bg-white data-[state=active]:text-surface-900 dark:data-[state=active]:bg-surface-950 dark:data-[state=active]:text-surface-50",
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const secondaryClasses = cls(
|
|
162
|
+
"inline-flex items-center justify-center whitespace-nowrap px-3 py-1.5 text-sm font-medium transition-all",
|
|
163
|
+
"border-b-2 border-transparent -mb-px",
|
|
164
|
+
"focus-visible:outline-none",
|
|
165
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
166
|
+
"hover:text-surface-700 dark:hover:text-surface-300",
|
|
167
|
+
"data-[state=active]:border-b-primary data-[state=active]:text-primary dark:data-[state=active]:border-b-primary dark:data-[state=active]:text-primary-dark",
|
|
168
|
+
);
|
|
169
|
+
|
|
49
170
|
return <TabsPrimitive.Trigger value={value}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"data-[state=active]:bg-white data-[state=active]:text-surface-900 dark:data-[state=active]:bg-surface-950 dark:data-[state=active]:text-surface-50",
|
|
56
|
-
// "data-[state=active]:border",
|
|
57
|
-
// defaultBorderMixin,
|
|
58
|
-
className,
|
|
59
|
-
innerClassName)}>
|
|
171
|
+
disabled={disabled}
|
|
172
|
+
className={cls(
|
|
173
|
+
mode === "secondary" ? secondaryClasses : primaryClasses,
|
|
174
|
+
className,
|
|
175
|
+
innerClassName)}>
|
|
60
176
|
{children}
|
|
61
177
|
</TabsPrimitive.Trigger>;
|
|
62
178
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { useLayoutEffect } from "react";
|
|
4
|
-
import
|
|
5
|
-
import { cls, debounce } from "../util";
|
|
4
|
+
import { debounce } from "../util";
|
|
6
5
|
|
|
7
6
|
type State = {
|
|
8
7
|
outerHeightStyle: number;
|
|
@@ -13,33 +12,6 @@ function getStyleValue(value: string) {
|
|
|
13
12
|
return parseInt(value, 10) || 0;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const styles: {
|
|
17
|
-
shadow: React.CSSProperties;
|
|
18
|
-
} = {
|
|
19
|
-
shadow: {
|
|
20
|
-
// Visibility needed to hide the extra text area on iPads
|
|
21
|
-
visibility: "hidden",
|
|
22
|
-
// Remove from the content flow
|
|
23
|
-
position: "absolute",
|
|
24
|
-
// Ignore the scrollbar width
|
|
25
|
-
overflow: "hidden",
|
|
26
|
-
height: 0,
|
|
27
|
-
top: 0,
|
|
28
|
-
left: 0,
|
|
29
|
-
// Create a new layer, increase the isolation of the computed values
|
|
30
|
-
transform: "translateZ(0)"
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function isEmpty(obj: State) {
|
|
35
|
-
return (
|
|
36
|
-
obj === undefined ||
|
|
37
|
-
obj === null ||
|
|
38
|
-
Object.keys(obj).length === 0 ||
|
|
39
|
-
(obj.outerHeightStyle === 0 && !obj.overflow)
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
15
|
export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
|
|
44
16
|
props: TextareaAutosizeProps,
|
|
45
17
|
ref: React.ForwardedRef<Element>
|
|
@@ -60,166 +32,96 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
|
|
|
60
32
|
} = props;
|
|
61
33
|
|
|
62
34
|
const { current: isControlled } = React.useRef(value != null);
|
|
63
|
-
const inputRef = React.useRef<
|
|
35
|
+
const inputRef = React.useRef<HTMLTextAreaElement>(null);
|
|
64
36
|
const handleRef = useForkRef(ref, inputRef);
|
|
65
|
-
const shadowRef = React.useRef<HTMLTextAreaElement>(null);
|
|
66
|
-
const renders = React.useRef(0);
|
|
67
|
-
const [state, setState] = React.useState<State>({
|
|
68
|
-
outerHeightStyle: 0
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const getUpdatedState = React.useCallback(() => {
|
|
72
|
-
|
|
73
|
-
const input = inputRef.current!;
|
|
74
|
-
if (typeof window === "undefined") {
|
|
75
|
-
return {
|
|
76
|
-
outerHeightStyle: 0
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
37
|
|
|
80
|
-
|
|
81
|
-
const
|
|
38
|
+
const syncHeight = React.useCallback(() => {
|
|
39
|
+
const el = inputRef.current;
|
|
40
|
+
if (!el || typeof window === "undefined") return;
|
|
41
|
+
if (el.offsetWidth === 0) return;
|
|
82
42
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
43
|
+
const cs = window.getComputedStyle(el);
|
|
44
|
+
const paddingY =
|
|
45
|
+
getStyleValue(cs.paddingTop) + getStyleValue(cs.paddingBottom);
|
|
46
|
+
const borderY =
|
|
47
|
+
getStyleValue(cs.borderTopWidth) + getStyleValue(cs.borderBottomWidth);
|
|
48
|
+
const boxSizing = cs.boxSizing;
|
|
89
49
|
|
|
90
|
-
|
|
91
|
-
const
|
|
50
|
+
// ── measure by temporarily collapsing the real element ──
|
|
51
|
+
const prevHeight = el.style.height;
|
|
52
|
+
const prevOverflow = el.style.overflowY;
|
|
53
|
+
el.style.overflowY = "hidden";
|
|
54
|
+
el.style.height = "0px";
|
|
92
55
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (inputShallow.value.slice(-1) === "\n") {
|
|
96
|
-
// Certain fonts which overflow the line height will cause the textarea
|
|
97
|
-
// to report a different scrollHeight depending on whether the last line
|
|
98
|
-
// is empty. Make it non-empty to avoid this issue.
|
|
99
|
-
inputShallow.value += " ";
|
|
100
|
-
}
|
|
56
|
+
// scrollHeight = content + padding (always, regardless of box-sizing)
|
|
57
|
+
const scrollH = el.scrollHeight;
|
|
101
58
|
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const minHeight = getStyleValue(computedStyle.minHeight);
|
|
59
|
+
// Measure single-row height for minRows / maxRows
|
|
60
|
+
const prevValue = el.value;
|
|
61
|
+
el.value = "x";
|
|
62
|
+
const singleRowScrollH = el.scrollHeight;
|
|
63
|
+
el.value = prevValue;
|
|
108
64
|
|
|
109
|
-
//
|
|
110
|
-
|
|
65
|
+
// Restore immediately — all of this happens before paint (useLayoutEffect)
|
|
66
|
+
el.style.height = prevHeight;
|
|
67
|
+
el.style.overflowY = prevOverflow;
|
|
111
68
|
|
|
112
|
-
|
|
113
|
-
inputShallow.value = "x";
|
|
114
|
-
const singleRowHeight = sizeReferenceElement.scrollHeight;
|
|
69
|
+
const lineHeight = singleRowScrollH - paddingY;
|
|
115
70
|
|
|
116
|
-
|
|
117
|
-
let outerHeight = innerHeight;
|
|
71
|
+
let targetHeight = scrollH; // includes padding
|
|
118
72
|
|
|
119
73
|
if (minRows) {
|
|
120
|
-
|
|
74
|
+
targetHeight = Math.max(
|
|
75
|
+
Number(minRows) * lineHeight + paddingY,
|
|
76
|
+
targetHeight
|
|
77
|
+
);
|
|
121
78
|
}
|
|
122
|
-
if (maxRows) {
|
|
123
|
-
outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight);
|
|
124
|
-
}
|
|
125
|
-
outerHeight = Math.max(outerHeight, singleRowHeight, minHeight);
|
|
126
|
-
|
|
127
|
-
// Take the box sizing into account for applying this value as a style.
|
|
128
|
-
const outerHeightStyle = outerHeight + (!ignoreBoxSizing && boxSizing === "border-box" ? padding + border : 0);
|
|
129
79
|
|
|
130
|
-
const
|
|
80
|
+
const unclampedHeight = targetHeight;
|
|
131
81
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const updateState = React.useCallback((prevState: State, newState: State) => {
|
|
139
|
-
const {
|
|
140
|
-
outerHeightStyle,
|
|
141
|
-
overflow
|
|
142
|
-
} = newState;
|
|
143
|
-
// Need a large enough difference to update the height.
|
|
144
|
-
// This prevents infinite rendering loop.
|
|
145
|
-
if (
|
|
146
|
-
renders.current < 20 &&
|
|
147
|
-
((outerHeightStyle > 0 &&
|
|
148
|
-
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
149
|
-
prevState.overflow !== overflow)
|
|
150
|
-
) {
|
|
151
|
-
renders.current += 1;
|
|
152
|
-
return {
|
|
153
|
-
overflow,
|
|
154
|
-
outerHeightStyle
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
if (process.env.NODE_ENV !== "production") {
|
|
158
|
-
if (renders.current === 20) {
|
|
159
|
-
console.error(
|
|
160
|
-
[
|
|
161
|
-
"MUI: Too many re-renders. The layout is unstable.",
|
|
162
|
-
"TextareaAutosize limits the number of renders to prevent an infinite loop."
|
|
163
|
-
].join("\n")
|
|
164
|
-
);
|
|
165
|
-
}
|
|
82
|
+
if (maxRows) {
|
|
83
|
+
targetHeight = Math.min(
|
|
84
|
+
Number(maxRows) * lineHeight + paddingY,
|
|
85
|
+
targetHeight
|
|
86
|
+
);
|
|
166
87
|
}
|
|
167
|
-
return prevState;
|
|
168
|
-
}, []);
|
|
169
88
|
|
|
170
|
-
|
|
171
|
-
|
|
89
|
+
// For border-box, height CSS prop = content + padding + border.
|
|
90
|
+
// scrollHeight already includes padding, so only add border.
|
|
91
|
+
const extra =
|
|
92
|
+
!ignoreBoxSizing && boxSizing === "border-box" ? borderY : 0;
|
|
93
|
+
const finalHeight = Math.ceil(targetHeight + extra);
|
|
172
94
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
if (onResize) {
|
|
177
|
-
onResize(newState);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
setState((prevState) => {
|
|
181
|
-
return updateState(prevState, newState);
|
|
182
|
-
});
|
|
183
|
-
}, [getUpdatedState, onResize, updateState]);
|
|
95
|
+
const shouldScroll =
|
|
96
|
+
Math.abs(unclampedHeight - targetHeight) > 1;
|
|
184
97
|
|
|
185
|
-
|
|
186
|
-
|
|
98
|
+
el.style.height = `${finalHeight}px`;
|
|
99
|
+
el.style.overflowY = shouldScroll ? "auto" : "hidden";
|
|
187
100
|
|
|
188
|
-
if (
|
|
189
|
-
|
|
101
|
+
if (onResize) {
|
|
102
|
+
onResize({ outerHeightStyle: finalHeight, overflow: !shouldScroll });
|
|
190
103
|
}
|
|
104
|
+
}, [maxRows, minRows, ignoreBoxSizing, onResize]);
|
|
191
105
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
setState((prevState) => {
|
|
197
|
-
return updateState(prevState, newState);
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
}, [getUpdatedState, updateState]);
|
|
106
|
+
// ── sync on every layout ──
|
|
107
|
+
useLayoutEffect(() => {
|
|
108
|
+
syncHeight();
|
|
109
|
+
});
|
|
201
110
|
|
|
111
|
+
// ── sync on window resize / element resize ──
|
|
202
112
|
React.useEffect(() => {
|
|
203
113
|
const handleResize = debounce(() => {
|
|
204
|
-
renders.current = 0;
|
|
205
|
-
|
|
206
|
-
// If the TextareaAutosize component is replaced by Suspense with a fallback, the last
|
|
207
|
-
// ResizeObserver's handler that runs because of the change in the layout is trying to
|
|
208
|
-
// access a dom node that is no longer there (as the fallback component is being shown instead).
|
|
209
114
|
if (inputRef.current) {
|
|
210
|
-
|
|
115
|
+
syncHeight();
|
|
211
116
|
}
|
|
212
117
|
});
|
|
213
|
-
let resizeObserver: ResizeObserver;
|
|
214
118
|
|
|
215
119
|
const input = inputRef.current!;
|
|
216
|
-
|
|
217
|
-
if (typeof window === "undefined") {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
120
|
+
if (typeof window === "undefined") return;
|
|
220
121
|
|
|
221
|
-
|
|
122
|
+
window.addEventListener("resize", handleResize);
|
|
222
123
|
|
|
124
|
+
let resizeObserver: ResizeObserver | undefined;
|
|
223
125
|
if (typeof ResizeObserver !== "undefined") {
|
|
224
126
|
resizeObserver = new ResizeObserver(handleResize);
|
|
225
127
|
resizeObserver.observe(input);
|
|
@@ -227,67 +129,35 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
|
|
|
227
129
|
|
|
228
130
|
return () => {
|
|
229
131
|
handleResize.clear();
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
resizeObserver.disconnect();
|
|
233
|
-
}
|
|
132
|
+
window.removeEventListener("resize", handleResize);
|
|
133
|
+
resizeObserver?.disconnect();
|
|
234
134
|
};
|
|
235
|
-
}, [
|
|
236
|
-
|
|
237
|
-
useLayoutEffect(() => {
|
|
238
|
-
syncHeight();
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
React.useEffect(() => {
|
|
242
|
-
renders.current = 0;
|
|
243
|
-
}, [value]);
|
|
135
|
+
}, [syncHeight]);
|
|
244
136
|
|
|
245
137
|
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
246
|
-
renders.current = 0;
|
|
247
|
-
|
|
248
138
|
if (!isControlled) {
|
|
249
139
|
syncHeight();
|
|
250
140
|
}
|
|
251
|
-
|
|
252
141
|
if (onChange) {
|
|
253
142
|
onChange(event);
|
|
254
143
|
}
|
|
255
144
|
};
|
|
256
145
|
|
|
257
146
|
return (
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
overflow: state.overflow ? "hidden" : undefined,
|
|
273
|
-
...style,
|
|
274
|
-
}}
|
|
275
|
-
onScroll={onScroll}
|
|
276
|
-
{...other}
|
|
277
|
-
/>
|
|
278
|
-
<textarea
|
|
279
|
-
aria-hidden
|
|
280
|
-
className={cls(props.className, props.shadowClassName)}
|
|
281
|
-
readOnly
|
|
282
|
-
ref={shadowRef}
|
|
283
|
-
tabIndex={-1}
|
|
284
|
-
style={{
|
|
285
|
-
padding: 0,
|
|
286
|
-
...styles.shadow,
|
|
287
|
-
...style,
|
|
288
|
-
}}
|
|
289
|
-
/>
|
|
290
|
-
</React.Fragment>
|
|
147
|
+
<textarea
|
|
148
|
+
value={value}
|
|
149
|
+
onChange={handleChange}
|
|
150
|
+
className={props.className}
|
|
151
|
+
ref={handleRef}
|
|
152
|
+
onFocus={onFocus}
|
|
153
|
+
onBlur={onBlur}
|
|
154
|
+
rows={minRows as number}
|
|
155
|
+
style={{
|
|
156
|
+
...style,
|
|
157
|
+
}}
|
|
158
|
+
onScroll={onScroll}
|
|
159
|
+
{...other}
|
|
160
|
+
/>
|
|
291
161
|
);
|
|
292
162
|
}) as React.FC<TextareaAutosizeProps & { ref?: React.ForwardedRef<Element> }>;
|
|
293
163
|
|
|
@@ -337,11 +207,6 @@ export type TextareaAutosizeProps = Omit<React.InputHTMLAttributes<HTMLTextAreaE
|
|
|
337
207
|
function useForkRef<Instance>(
|
|
338
208
|
...refs: Array<React.Ref<Instance> | undefined>
|
|
339
209
|
): React.RefCallback<Instance> | null {
|
|
340
|
-
/**
|
|
341
|
-
* This will create a new function if the refs passed to this hook change and are all defined.
|
|
342
|
-
* This means react will call the old forkRef with `null` and the new forkRef
|
|
343
|
-
* with the ref. Cleanup naturally emerges from this behavior.
|
|
344
|
-
*/
|
|
345
210
|
return React.useMemo(() => {
|
|
346
211
|
if (refs.every((ref) => ref == null)) {
|
|
347
212
|
return null;
|
|
@@ -22,9 +22,9 @@ export type TooltipProps = {
|
|
|
22
22
|
className?: string,
|
|
23
23
|
container?: HTMLElement,
|
|
24
24
|
style?: React.CSSProperties;
|
|
25
|
-
}
|
|
25
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, "title">;
|
|
26
26
|
|
|
27
|
-
export const Tooltip = ({
|
|
27
|
+
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(({
|
|
28
28
|
open,
|
|
29
29
|
defaultOpen,
|
|
30
30
|
side = "bottom",
|
|
@@ -39,8 +39,9 @@ export const Tooltip = ({
|
|
|
39
39
|
asChild = false,
|
|
40
40
|
container,
|
|
41
41
|
className,
|
|
42
|
-
style
|
|
43
|
-
|
|
42
|
+
style,
|
|
43
|
+
...props
|
|
44
|
+
}, ref) => {
|
|
44
45
|
|
|
45
46
|
useInjectStyles("Tooltip", styles);
|
|
46
47
|
|
|
@@ -58,7 +59,7 @@ export const Tooltip = ({
|
|
|
58
59
|
{children}
|
|
59
60
|
</TooltipPrimitive.Trigger>
|
|
60
61
|
: <TooltipPrimitive.Trigger asChild={true}>
|
|
61
|
-
<div style={style} className={className}>
|
|
62
|
+
<div style={style} className={className} ref={ref} {...props}>
|
|
62
63
|
{children}
|
|
63
64
|
</div>
|
|
64
65
|
</TooltipPrimitive.Trigger>;
|
|
@@ -83,7 +84,7 @@ export const Tooltip = ({
|
|
|
83
84
|
</TooltipPrimitive.Root>
|
|
84
85
|
</TooltipPrimitive.Provider>
|
|
85
86
|
);
|
|
86
|
-
};
|
|
87
|
+
});
|
|
87
88
|
|
|
88
89
|
const styles = `
|
|
89
90
|
|
package/src/components/index.tsx
CHANGED
|
@@ -30,7 +30,9 @@ export * from "./Menubar";
|
|
|
30
30
|
export * from "./MultiSelect";
|
|
31
31
|
export * from "./Paper";
|
|
32
32
|
export * from "./RadioGroup";
|
|
33
|
+
export * from "./ResizablePanels";
|
|
33
34
|
export * from "./SearchBar";
|
|
35
|
+
export * from "./SearchableSelect";
|
|
34
36
|
export * from "./Select";
|
|
35
37
|
export * from "./Separator";
|
|
36
38
|
export * from "./Slider";
|
|
@@ -5,7 +5,7 @@ import { RefObject, useEffect } from "react";
|
|
|
5
5
|
/**
|
|
6
6
|
* Hook that alerts clicks outside the passed ref
|
|
7
7
|
*/
|
|
8
|
-
export function useOutsideAlerter(ref: RefObject<HTMLElement>, onOutsideClick: () => void, active = true): void {
|
|
8
|
+
export function useOutsideAlerter(ref: RefObject<HTMLElement | null>, onOutsideClick: () => void, active = true): void {
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
if (!active)
|
|
11
11
|
return;
|