@firecms/ui 3.3.0-canary.d3242eb → 3.4.0-canary.63041d0
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/Card.d.ts +2 -3
- package/dist/components/Checkbox.d.ts +2 -1
- package/dist/components/Chip.d.ts +5 -1
- package/dist/components/Dialog.d.ts +2 -1
- package/dist/components/FilterChip.d.ts +34 -0
- package/dist/components/IconButton.d.ts +1 -0
- package/dist/components/Popover.d.ts +3 -1
- package/dist/components/SearchBar.d.ts +8 -1
- package/dist/components/Select.d.ts +1 -1
- package/dist/components/TextField.d.ts +2 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useDebounceCallback.d.ts +13 -0
- package/dist/index.css +43 -32
- package/dist/index.es.js +1027 -561
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1026 -560
- package/dist/index.umd.js.map +1 -1
- package/dist/styles.d.ts +2 -2
- package/package.json +6 -6
- package/src/components/Alert.tsx +7 -7
- package/src/components/Avatar.tsx +1 -1
- package/src/components/BooleanSwitch.tsx +6 -3
- package/src/components/Button.tsx +17 -13
- package/src/components/Card.tsx +22 -5
- package/src/components/CenteredView.tsx +1 -1
- package/src/components/Checkbox.tsx +10 -4
- package/src/components/Chip.tsx +70 -11
- package/src/components/Collapse.tsx +2 -0
- package/src/components/Dialog.tsx +8 -5
- package/src/components/ExpandablePanel.tsx +3 -2
- package/src/components/FilterChip.tsx +79 -0
- package/src/components/IconButton.tsx +11 -6
- package/src/components/InfoLabel.tsx +1 -1
- package/src/components/InputLabel.tsx +2 -2
- package/src/components/Label.tsx +1 -1
- package/src/components/LoadingButton.tsx +1 -1
- package/src/components/Menu.tsx +2 -0
- package/src/components/Popover.tsx +9 -3
- package/src/components/RadioGroup.tsx +1 -1
- package/src/components/SearchBar.tsx +9 -1
- package/src/components/Select.tsx +8 -6
- package/src/components/Separator.tsx +2 -2
- package/src/components/Skeleton.tsx +25 -8
- package/src/components/TextField.tsx +27 -13
- package/src/components/ToggleButtonGroup.tsx +4 -2
- package/src/components/index.tsx +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useDebounceCallback.tsx +47 -0
- package/src/index.css +43 -32
- package/src/styles.ts +2 -2
|
@@ -11,16 +11,17 @@ export type IconButtonProps<C extends React.ElementType> =
|
|
|
11
11
|
disabled?: boolean;
|
|
12
12
|
toggled?: boolean;
|
|
13
13
|
component?: C;
|
|
14
|
-
onClick?: React.MouseEventHandler<any
|
|
14
|
+
onClick?: React.MouseEventHandler<any>;
|
|
15
|
+
"aria-label"?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const buttonClasses = "hover:bg-surface-accent-200 hover:bg-opacity-75 hover:bg-surface-accent-200/75 dark:hover:bg-surface-accent-800 hover:scale-
|
|
18
|
+
const buttonClasses = "hover:bg-surface-accent-200 hover:bg-opacity-75 hover:bg-surface-accent-200/75 dark:hover:bg-surface-accent-800 hover:scale-[1.04] active:scale-95 transition-transform";
|
|
18
19
|
const baseClasses = "inline-flex items-center justify-center p-2 text-sm font-medium focus:outline-none transition-colors ease-in-out duration-150";
|
|
19
|
-
const colorClasses = "text-surface-accent-
|
|
20
|
+
const colorClasses = "text-surface-accent-500 visited:text-surface-accent-500 dark:text-surface-accent-300 dark:visited:text-surface-300";
|
|
20
21
|
const sizeClasses = {
|
|
21
22
|
medium: "w-10 !h-10 min-w-10 min-h-10",
|
|
22
23
|
small: "w-8 !h-8 min-w-8 min-h-8",
|
|
23
|
-
smallest: "w-
|
|
24
|
+
smallest: "w-7 !h-7 min-w-7 min-h-7",
|
|
24
25
|
large: "w-12 !h-12 min-w-12 min-h-12"
|
|
25
26
|
}
|
|
26
27
|
const shapeClasses = {
|
|
@@ -40,11 +41,15 @@ const IconButtonInner = <C extends React.ElementType = "button">({
|
|
|
40
41
|
...props
|
|
41
42
|
}: IconButtonProps<C>, ref: React.ForwardedRef<HTMLButtonElement>) => {
|
|
42
43
|
|
|
43
|
-
const bgClasses = variant === "ghost" ? "bg-transparent" : "bg-surface-accent-200 bg-opacity-50 bg-surface-accent-200/50 dark:bg-surface-
|
|
44
|
+
const bgClasses = variant === "ghost" ? "bg-transparent" : "bg-surface-accent-200 bg-opacity-50 bg-surface-accent-200/50 dark:bg-surface-900 dark:bg-opacity-50 dark:bg-surface-900/50";
|
|
44
45
|
const Component: React.ElementType<any> = component || "button";
|
|
46
|
+
const isNativeButton = Component === "button";
|
|
45
47
|
return (
|
|
46
48
|
<Component
|
|
47
|
-
type="button"
|
|
49
|
+
type={isNativeButton ? "button" : undefined}
|
|
50
|
+
role={isNativeButton ? undefined : "button"}
|
|
51
|
+
aria-disabled={disabled || undefined}
|
|
52
|
+
tabIndex={disabled ? -1 : undefined}
|
|
48
53
|
ref={ref}
|
|
49
54
|
{...props}
|
|
50
55
|
className={cls(
|
|
@@ -10,7 +10,7 @@ export type InputLabelProps = {
|
|
|
10
10
|
} & React.LabelHTMLAttributes<HTMLLabelElement>;
|
|
11
11
|
|
|
12
12
|
const defaultClasses = {
|
|
13
|
-
root: "origin-left transition-transform block whitespace-nowrap overflow-hidden text-
|
|
13
|
+
root: "origin-left transition-transform block whitespace-nowrap overflow-hidden text-ellipsis max-w-full",
|
|
14
14
|
shrink: "transform translate-y-[2px] scale-75 translate-x-[12px]",
|
|
15
15
|
expanded: "translate-x-[16px] top-0 transform translate-y-[16px] scale-100"
|
|
16
16
|
};
|
|
@@ -30,7 +30,7 @@ export const InputLabel = React.forwardRef<HTMLLabelElement, InputLabelProps>(fu
|
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
32
|
<label
|
|
33
|
-
className={cls("text-sm font-medium peer-disabled:cursor-not-allowed
|
|
33
|
+
className={cls("text-sm font-medium peer-disabled:cursor-not-allowed",
|
|
34
34
|
defaultBorderMixin, computedClassName)}
|
|
35
35
|
data-shrink={shrink}
|
|
36
36
|
ref={ref}
|
package/src/components/Label.tsx
CHANGED
|
@@ -20,7 +20,7 @@ const Label = React.forwardRef<
|
|
|
20
20
|
<LabelPrimitive.Root
|
|
21
21
|
ref={ref}
|
|
22
22
|
onClick={onClick}
|
|
23
|
-
className={cls("text-sm font-medium peer-disabled:cursor-not-allowed
|
|
23
|
+
className={cls("text-sm font-medium peer-disabled:cursor-not-allowed",
|
|
24
24
|
border && "border border-surface-300 dark:border-surface-700 rounded-md px-3 py-1.5",
|
|
25
25
|
onClick && "hover:cursor-pointer hover:bg-surface-200 dark:hover:bg-surface-800",
|
|
26
26
|
defaultBorderMixin, className)}
|
|
@@ -19,7 +19,7 @@ export function LoadingButton<P extends React.ElementType = "button">({
|
|
|
19
19
|
<Button
|
|
20
20
|
disabled={loading || disabled}
|
|
21
21
|
onClick={onClick}
|
|
22
|
-
component={props.component as
|
|
22
|
+
component={props.component as React.ElementType}
|
|
23
23
|
{...props}
|
|
24
24
|
>
|
|
25
25
|
{loading && (
|
package/src/components/Menu.tsx
CHANGED
|
@@ -27,6 +27,8 @@ export interface PopoverProps {
|
|
|
27
27
|
modal?: boolean;
|
|
28
28
|
className?: string;
|
|
29
29
|
portalContainer?: HTMLElement | null;
|
|
30
|
+
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
|
31
|
+
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export function Popover({
|
|
@@ -45,7 +47,9 @@ export function Popover({
|
|
|
45
47
|
enabled = true,
|
|
46
48
|
modal = false,
|
|
47
49
|
portalContainer,
|
|
48
|
-
className
|
|
50
|
+
className,
|
|
51
|
+
onMouseEnter,
|
|
52
|
+
onMouseLeave
|
|
49
53
|
}: PopoverProps) {
|
|
50
54
|
|
|
51
55
|
useInjectStyles("Popover", popoverStyles);
|
|
@@ -78,10 +82,12 @@ export function Popover({
|
|
|
78
82
|
arrowPadding={arrowPadding}
|
|
79
83
|
sticky={sticky}
|
|
80
84
|
hideWhenDetached={hideWhenDetached}
|
|
81
|
-
avoidCollisions={avoidCollisions}
|
|
85
|
+
avoidCollisions={avoidCollisions}
|
|
86
|
+
onMouseEnter={onMouseEnter}
|
|
87
|
+
onMouseLeave={onMouseLeave}>
|
|
82
88
|
|
|
83
89
|
{children}
|
|
84
|
-
<PopoverPrimitive.Arrow className="fill-white dark:fill-surface-
|
|
90
|
+
<PopoverPrimitive.Arrow className="fill-white dark:fill-surface-800"/>
|
|
85
91
|
</PopoverPrimitive.Content>
|
|
86
92
|
</PopoverPrimitive.Portal>
|
|
87
93
|
</PopoverPrimitive.Root>;
|
|
@@ -58,7 +58,7 @@ const RadioGroupItem = React.forwardRef<
|
|
|
58
58
|
<RadioGroupPrimitive.Item
|
|
59
59
|
ref={ref}
|
|
60
60
|
className={cls(
|
|
61
|
-
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-
|
|
61
|
+
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
62
62
|
className
|
|
63
63
|
)}
|
|
64
64
|
{...props}
|
|
@@ -11,6 +11,13 @@ interface SearchBarProps {
|
|
|
11
11
|
onClick?: () => void;
|
|
12
12
|
onTextSearch?: (searchString?: string) => void;
|
|
13
13
|
placeholder?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Text the input is initialised with. Use it when the search term is restored from
|
|
16
|
+
* somewhere else, e.g. a URL query parameter, so that the input reflects the search
|
|
17
|
+
* that is actually being applied. Only read when the component mounts; the search bar
|
|
18
|
+
* owns the text from then on.
|
|
19
|
+
*/
|
|
20
|
+
initialValue?: string;
|
|
14
21
|
expandable?: boolean;
|
|
15
22
|
/**
|
|
16
23
|
* Size of the search bar.
|
|
@@ -35,6 +42,7 @@ export function SearchBar({
|
|
|
35
42
|
onClick,
|
|
36
43
|
onTextSearch,
|
|
37
44
|
placeholder = "Search",
|
|
45
|
+
initialValue,
|
|
38
46
|
expandable = false,
|
|
39
47
|
size = "medium",
|
|
40
48
|
large,
|
|
@@ -46,7 +54,7 @@ export function SearchBar({
|
|
|
46
54
|
inputRef
|
|
47
55
|
}: SearchBarProps) {
|
|
48
56
|
|
|
49
|
-
const [searchText, setSearchText] = useState<string>("");
|
|
57
|
+
const [searchText, setSearchText] = useState<string>(initialValue ?? "");
|
|
50
58
|
const [active, setActive] = useState<boolean>(false);
|
|
51
59
|
|
|
52
60
|
const deferredValues = useDebounceValue(searchText, 200);
|
|
@@ -36,7 +36,7 @@ export type SelectProps<T extends SelectValue = string> = {
|
|
|
36
36
|
error?: boolean,
|
|
37
37
|
position?: "item-aligned" | "popper",
|
|
38
38
|
endAdornment?: React.ReactNode,
|
|
39
|
-
inputRef?: React.
|
|
39
|
+
inputRef?: React.Ref<HTMLButtonElement>,
|
|
40
40
|
padding?: boolean,
|
|
41
41
|
invisible?: boolean,
|
|
42
42
|
children?: React.ReactNode;
|
|
@@ -136,7 +136,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
136
136
|
{...props}>
|
|
137
137
|
{typeof label === "string" ? <SelectInputLabel error={error}>{label}</SelectInputLabel> : label}
|
|
138
138
|
<div className={cls(
|
|
139
|
-
"select-none rounded-
|
|
139
|
+
"select-none rounded-lg text-sm",
|
|
140
140
|
invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin,
|
|
141
141
|
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
142
142
|
"relative flex items-center",
|
|
@@ -155,6 +155,8 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
155
155
|
id={id}
|
|
156
156
|
asChild={false}
|
|
157
157
|
type="button"
|
|
158
|
+
aria-label={typeof label === "string" ? label : (typeof renderValue === "string" ? renderValue : "Select an option")}
|
|
159
|
+
aria-invalid={error || undefined}
|
|
158
160
|
className={cls(
|
|
159
161
|
"h-full",
|
|
160
162
|
padding ? {
|
|
@@ -164,7 +166,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
164
166
|
} : "",
|
|
165
167
|
"outline-hidden focus:outline-hidden",
|
|
166
168
|
"outline-none focus:outline-none",
|
|
167
|
-
"select-none rounded-
|
|
169
|
+
"select-none rounded-lg text-sm",
|
|
168
170
|
error ? "text-red-500 dark:text-red-600" : "focus:text-text-primary dark:focus:text-text-primary-dark",
|
|
169
171
|
error ? "border border-red-500 dark:border-red-600" : "",
|
|
170
172
|
disabled ? "text-surface-accent-600 dark:text-surface-accent-400" : "text-surface-accent-800 dark:text-white",
|
|
@@ -232,7 +234,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
232
234
|
{/* Pass the calculated finalContainer */}
|
|
233
235
|
<SelectPrimitive.Portal container={finalContainer}>
|
|
234
236
|
<SelectPrimitive.Content position={position}
|
|
235
|
-
className={cls(focusedDisabled, "z-50 relative overflow-hidden border bg-white dark:bg-surface-
|
|
237
|
+
className={cls(focusedDisabled, "z-50 relative overflow-hidden border bg-white dark:bg-surface-800 p-2 rounded-lg shadow-lg", defaultBorderMixin)}>
|
|
236
238
|
<SelectPrimitive.Viewport className={cls("p-1", viewportClassName)}
|
|
237
239
|
style={{ maxHeight: "var(--radix-select-content-available-height)" }}>
|
|
238
240
|
{children}
|
|
@@ -270,8 +272,8 @@ export const SelectItem = React.memo(function SelectItem<T extends SelectValue =
|
|
|
270
272
|
"w-full",
|
|
271
273
|
"relative flex items-center p-2 rounded-md text-sm text-surface-accent-700 dark:text-surface-accent-300",
|
|
272
274
|
"focus:z-10",
|
|
273
|
-
"data-[state=checked]:bg-surface-accent-100 data-[state=checked]:dark:bg-surface-accent-800 focus:bg-surface-accent-100 dark:focus:bg-surface-
|
|
274
|
-
"data-[state=checked]:focus:bg-surface-accent-200 data-[state=checked]:dark:focus:bg-surface-
|
|
275
|
+
"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",
|
|
276
|
+
"data-[state=checked]:focus:bg-surface-accent-200 data-[state=checked]:dark:focus:bg-surface-900",
|
|
275
277
|
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
|
276
278
|
"[&>*]:w-full",
|
|
277
279
|
"overflow-visible",
|
|
@@ -15,12 +15,12 @@ export function Separator({
|
|
|
15
15
|
<SeparatorPrimitive.Root
|
|
16
16
|
decorative={decorative}
|
|
17
17
|
orientation="horizontal"
|
|
18
|
-
className={cls("dark:bg-
|
|
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
19
|
);
|
|
20
20
|
else
|
|
21
21
|
return (
|
|
22
22
|
<SeparatorPrimitive.Root
|
|
23
|
-
className={cls("dark:bg-
|
|
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
24
|
decorative={decorative}
|
|
25
25
|
orientation="vertical"
|
|
26
26
|
/>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { cls } from "../util";
|
|
3
|
+
import { useInjectStyles } from "../hooks";
|
|
3
4
|
|
|
4
5
|
export type SkeletonProps = {
|
|
5
6
|
width?: number;
|
|
@@ -7,24 +8,40 @@ export type SkeletonProps = {
|
|
|
7
8
|
className?: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
const styles = `
|
|
12
|
+
@keyframes shimmer {
|
|
13
|
+
0% {
|
|
14
|
+
transform: translateX(-150%);
|
|
15
|
+
}
|
|
16
|
+
100% {
|
|
17
|
+
transform: translateX(150%);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
10
22
|
export function Skeleton({
|
|
11
23
|
width,
|
|
12
24
|
height,
|
|
13
25
|
className
|
|
14
26
|
}: SkeletonProps) {
|
|
27
|
+
|
|
28
|
+
useInjectStyles("Skeleton", styles);
|
|
29
|
+
|
|
15
30
|
return <span
|
|
16
31
|
style={{
|
|
17
|
-
width: width
|
|
18
|
-
height: height
|
|
32
|
+
width: width ? `${width}px` : "100%",
|
|
33
|
+
height: height ? `${height}px` : "12px"
|
|
19
34
|
}}
|
|
20
35
|
className={
|
|
21
36
|
cls(
|
|
22
|
-
"block",
|
|
23
|
-
"bg-surface-accent-
|
|
24
|
-
"animate-pulse",
|
|
37
|
+
"block relative overflow-hidden",
|
|
38
|
+
"bg-surface-accent-50 dark:bg-surface-accent-800 rounded-md",
|
|
25
39
|
"max-w-full max-h-full",
|
|
26
|
-
width === undefined ? "w-full" : "",
|
|
27
|
-
height === undefined ? "h-3" : "",
|
|
28
40
|
className)
|
|
29
|
-
}
|
|
41
|
+
}>
|
|
42
|
+
<span
|
|
43
|
+
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/50 dark:via-white/8 to-transparent"
|
|
44
|
+
style={{ animation: "shimmer 1.8s ease-in-out infinite" }}
|
|
45
|
+
/>
|
|
46
|
+
</span>;
|
|
30
47
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import React, { ForwardedRef, forwardRef, useEffect, useRef } from "react";
|
|
2
|
+
import React, { ForwardedRef, forwardRef, useEffect, useId, useRef } from "react";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
fieldBackgroundDisabledMixin,
|
|
6
6
|
fieldBackgroundHoverMixin,
|
|
7
7
|
fieldBackgroundInvisibleMixin,
|
|
8
8
|
fieldBackgroundMixin,
|
|
9
|
-
focusedInvisibleMixin
|
|
9
|
+
focusedInvisibleMixin
|
|
10
10
|
} from "../styles";
|
|
11
11
|
import { InputLabel } from "./InputLabel";
|
|
12
12
|
import { cls } from "../util";
|
|
@@ -53,7 +53,7 @@ export type TextFieldProps<T extends string | number> = {
|
|
|
53
53
|
* @default 1
|
|
54
54
|
*/
|
|
55
55
|
minRows?: number | string;
|
|
56
|
-
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">;
|
|
56
|
+
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "value">;
|
|
57
57
|
|
|
58
58
|
export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | number>>(
|
|
59
59
|
<T extends string | number>(
|
|
@@ -82,14 +82,18 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
82
82
|
ref: ForwardedRef<HTMLDivElement>
|
|
83
83
|
) => {
|
|
84
84
|
|
|
85
|
-
const
|
|
85
|
+
const fallbackRef = useRef(null);
|
|
86
|
+
const inputRef = inputRefProp ?? fallbackRef;
|
|
87
|
+
const autoId = useId();
|
|
88
|
+
const inputId = inputProps.id ?? autoId;
|
|
89
|
+
const labelId = `${inputId}-label`;
|
|
86
90
|
|
|
87
91
|
const [focused, setFocused] = React.useState(false);
|
|
88
92
|
const hasValue = value !== undefined && value !== null && value !== "";
|
|
89
93
|
|
|
90
94
|
useEffect(() => {
|
|
91
|
-
|
|
92
|
-
if (
|
|
95
|
+
const element = inputRef && "current" in inputRef ? inputRef.current : null;
|
|
96
|
+
if (element && document.activeElement === element) {
|
|
93
97
|
setFocused(true);
|
|
94
98
|
}
|
|
95
99
|
}, []);
|
|
@@ -111,8 +115,12 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
111
115
|
|
|
112
116
|
const input = multiline ? (
|
|
113
117
|
<textarea
|
|
114
|
-
{...(inputProps as
|
|
118
|
+
{...(inputProps as React.TextareaHTMLAttributes<HTMLTextAreaElement>)}
|
|
115
119
|
ref={inputRef}
|
|
120
|
+
id={inputId}
|
|
121
|
+
aria-labelledby={label ? labelId : undefined}
|
|
122
|
+
aria-invalid={error || undefined}
|
|
123
|
+
aria-disabled={disabled || undefined}
|
|
116
124
|
placeholder={focused || hasValue || !label ? placeholder : undefined}
|
|
117
125
|
autoFocus={autoFocus}
|
|
118
126
|
rows={typeof minRows === "string" ? parseInt(minRows) : (minRows ?? 3)}
|
|
@@ -123,7 +131,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
123
131
|
style={inputStyle}
|
|
124
132
|
className={cls(
|
|
125
133
|
invisible ? focusedInvisibleMixin : "",
|
|
126
|
-
"rounded-
|
|
134
|
+
"rounded-lg resize-none w-full outline-none text-base bg-transparent min-h-[64px] px-3",
|
|
127
135
|
label ? "pt-8 pb-2" : "py-2",
|
|
128
136
|
disabled && "outline-none opacity-50 text-surface-accent-600 dark:text-surface-accent-500",
|
|
129
137
|
inputClassName
|
|
@@ -133,11 +141,15 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
133
141
|
<input
|
|
134
142
|
{...inputProps}
|
|
135
143
|
ref={inputRef}
|
|
144
|
+
id={inputId}
|
|
145
|
+
aria-labelledby={label ? labelId : undefined}
|
|
146
|
+
aria-invalid={error || undefined}
|
|
147
|
+
aria-disabled={disabled || undefined}
|
|
136
148
|
disabled={disabled}
|
|
137
149
|
style={inputStyle}
|
|
138
150
|
className={cls(
|
|
139
151
|
"w-full outline-none bg-transparent leading-normal px-3",
|
|
140
|
-
"rounded-
|
|
152
|
+
"rounded-lg",
|
|
141
153
|
"focused:text-text-primary focused:dark:text-text-primary-dark",
|
|
142
154
|
invisible ? focusedInvisibleMixin : "",
|
|
143
155
|
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
@@ -145,7 +157,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
145
157
|
"min-h-[28px]": size === "smallest",
|
|
146
158
|
"min-h-[32px]": size === "small",
|
|
147
159
|
"min-h-[44px]": size === "medium",
|
|
148
|
-
"min-h-[64px]": size === "large"
|
|
160
|
+
"min-h-[64px]": size === "large"
|
|
149
161
|
},
|
|
150
162
|
label
|
|
151
163
|
? size === "large"
|
|
@@ -175,7 +187,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
175
187
|
<div
|
|
176
188
|
ref={ref}
|
|
177
189
|
className={cls(
|
|
178
|
-
"rounded-
|
|
190
|
+
"rounded-lg relative max-w-full",
|
|
179
191
|
invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin,
|
|
180
192
|
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
181
193
|
error ? "border border-red-500 dark:border-red-600" : "",
|
|
@@ -183,7 +195,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
183
195
|
"min-h-[28px]": size === "smallest",
|
|
184
196
|
"min-h-[32px]": size === "small",
|
|
185
197
|
"min-h-[44px]": size === "medium",
|
|
186
|
-
"min-h-[64px]": size === "large"
|
|
198
|
+
"min-h-[64px]": size === "large"
|
|
187
199
|
},
|
|
188
200
|
className
|
|
189
201
|
)}
|
|
@@ -191,6 +203,8 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
191
203
|
>
|
|
192
204
|
{label && (
|
|
193
205
|
<InputLabel
|
|
206
|
+
id={labelId}
|
|
207
|
+
htmlFor={inputId}
|
|
194
208
|
className={cls(
|
|
195
209
|
"pointer-events-none absolute",
|
|
196
210
|
size === "large" ? "top-1" : "top-[-1px]",
|
|
@@ -216,7 +230,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
|
|
|
216
230
|
{
|
|
217
231
|
"mr-4": size === "large",
|
|
218
232
|
"mr-3": size === "medium",
|
|
219
|
-
"mr-2": size === "small" || size === "smallest"
|
|
233
|
+
"mr-2": size === "small" || size === "smallest"
|
|
220
234
|
}
|
|
221
235
|
)}
|
|
222
236
|
>
|
|
@@ -38,11 +38,13 @@ export function ToggleButtonGroup<T extends string = string>({
|
|
|
38
38
|
className
|
|
39
39
|
}: ToggleButtonGroupProps<T>) {
|
|
40
40
|
return (
|
|
41
|
-
<div className={cls("inline-flex flex-row bg-surface-100 dark:bg-surface-
|
|
41
|
+
<div role="group" aria-label="Toggle options" className={cls("inline-flex flex-row bg-surface-100 dark:bg-surface-900 rounded-lg p-1 gap-1", className)}>
|
|
42
42
|
{options.map((option) => (
|
|
43
43
|
<button
|
|
44
44
|
key={option.value}
|
|
45
45
|
type="button"
|
|
46
|
+
aria-pressed={value === option.value}
|
|
47
|
+
aria-disabled={option.disabled || undefined}
|
|
46
48
|
onClick={(e) => {
|
|
47
49
|
e.stopPropagation();
|
|
48
50
|
if (!option.disabled) {
|
|
@@ -53,7 +55,7 @@ export function ToggleButtonGroup<T extends string = string>({
|
|
|
53
55
|
className={cls(
|
|
54
56
|
"flex flex-row items-center justify-center gap-2 py-3 px-4 rounded-md transition-colors",
|
|
55
57
|
value === option.value
|
|
56
|
-
? "bg-white dark:bg-surface-
|
|
58
|
+
? "bg-white dark:bg-surface-900 text-primary dark:text-primary-300 shadow-sm"
|
|
57
59
|
: "text-surface-500 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-700",
|
|
58
60
|
option.disabled && "opacity-50 cursor-not-allowed"
|
|
59
61
|
)}
|
package/src/components/index.tsx
CHANGED
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a debounced version of the provided callback. The returned function
|
|
5
|
+
* keeps the same signature as the input callback; each invocation resets the
|
|
6
|
+
* timer, so the wrapped callback only runs once calls stop for `delay` ms.
|
|
7
|
+
*
|
|
8
|
+
* Unlike {@link useDebounceValue}, which debounces a *value*, this debounces a
|
|
9
|
+
* *function call* — useful for search inputs, autosave, resize handlers, etc.
|
|
10
|
+
*
|
|
11
|
+
* @param callback the function to debounce. May be undefined.
|
|
12
|
+
* @param delay debounce delay in milliseconds. Defaults to 200ms.
|
|
13
|
+
* @group Hooks
|
|
14
|
+
*/
|
|
15
|
+
export function useDebounceCallback<T extends (...args: any[]) => unknown>(
|
|
16
|
+
callback?: T,
|
|
17
|
+
delay?: number
|
|
18
|
+
): T {
|
|
19
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
20
|
+
|
|
21
|
+
// Keep the latest callback without recreating the debounced function.
|
|
22
|
+
const callbackRef = useRef(callback);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
callbackRef.current = callback;
|
|
25
|
+
}, [callback]);
|
|
26
|
+
|
|
27
|
+
// Clear any pending timer on unmount.
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
return () => {
|
|
30
|
+
if (timeoutRef.current !== null) {
|
|
31
|
+
clearTimeout(timeoutRef.current);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const debouncedCallback = useCallback((...args: Parameters<T>) => {
|
|
37
|
+
if (timeoutRef.current !== null) {
|
|
38
|
+
clearTimeout(timeoutRef.current);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
timeoutRef.current = setTimeout(() => {
|
|
42
|
+
callbackRef.current?.(...args);
|
|
43
|
+
}, delay ?? 200);
|
|
44
|
+
}, [delay]);
|
|
45
|
+
|
|
46
|
+
return debouncedCallback as T;
|
|
47
|
+
}
|