@rebasepro/ui 0.3.0 → 0.5.0
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 +124 -66
- package/dist/components/Card.d.ts +2 -3
- package/dist/components/FilterChip.d.ts +2 -10
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +8 -2
- package/dist/index.css +1 -1
- package/dist/index.es.js +129 -110
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +128 -109
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -5
- package/src/components/BooleanSwitchWithLabel.tsx +4 -3
- package/src/components/Button.tsx +6 -6
- package/src/components/Card.tsx +1 -1
- package/src/components/DebouncedTextField.tsx +60 -17
- package/src/components/FilterChip.tsx +8 -13
- package/src/components/IconButton.tsx +4 -4
- package/src/components/Tabs.tsx +2 -2
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +3 -2
- package/src/components/VirtualTable/VirtualTableProps.tsx +21 -12
- package/src/index.css +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -67,12 +67,10 @@
|
|
|
67
67
|
"fast-equals": "6.0.0",
|
|
68
68
|
"lucide-react": "1.14.0",
|
|
69
69
|
"markdown-it": "^14.1.1",
|
|
70
|
-
"
|
|
70
|
+
"tailwind-merge": "^2.6.1",
|
|
71
71
|
"react-dropzone": "^14.4.1",
|
|
72
72
|
"react-use-measure": "^2.1.7",
|
|
73
|
-
"react-window": "^1.8.11"
|
|
74
|
-
"tailwind-merge": "^2.6.1",
|
|
75
|
-
"@rebasepro/types": "0.3.0"
|
|
73
|
+
"react-window": "^1.8.11"
|
|
76
74
|
},
|
|
77
75
|
"peerDependencies": {
|
|
78
76
|
"react": ">=19.0.0",
|
|
@@ -86,9 +86,10 @@ export const BooleanSwitchWithLabel = function BooleanSwitchWithLabel({
|
|
|
86
86
|
)}
|
|
87
87
|
onClick={disabled ? undefined : (e) => {
|
|
88
88
|
if (props.allowIndeterminate) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
else
|
|
89
|
+
const onChange = onValueChange as ((newValue: boolean | null) => void) | undefined;
|
|
90
|
+
if (value === null || value === undefined) onChange?.(true)
|
|
91
|
+
else if (value) onChange?.(false)
|
|
92
|
+
else onChange?.(null);
|
|
92
93
|
} else {
|
|
93
94
|
onValueChange?.(!value);
|
|
94
95
|
}
|
|
@@ -32,7 +32,7 @@ const ButtonInner = React.memo(React.forwardRef<
|
|
|
32
32
|
}: ButtonProps<React.ElementType>, ref) => {
|
|
33
33
|
|
|
34
34
|
const baseClasses =
|
|
35
|
-
"typography-button h-fit rounded-md whitespace-nowrap inline-flex items-center justify-center p-
|
|
35
|
+
"typography-button h-fit rounded-md whitespace-nowrap inline-flex items-center justify-center p-2 px-4 focus:outline-none transition-all ease-in-out duration-150 gap-2 active:scale-[0.98]";
|
|
36
36
|
|
|
37
37
|
const buttonClasses = cls({
|
|
38
38
|
"w-full": fullWidth,
|
|
@@ -68,11 +68,11 @@ const ButtonInner = React.memo(React.forwardRef<
|
|
|
68
68
|
|
|
69
69
|
const sizeClasses = cls(
|
|
70
70
|
{
|
|
71
|
-
"py-
|
|
72
|
-
"py-
|
|
73
|
-
"py-2 px-
|
|
74
|
-
"py-
|
|
75
|
-
"py-
|
|
71
|
+
"py-1 px-2": size === "small",
|
|
72
|
+
"py-2 px-4": size === "medium",
|
|
73
|
+
"py-2.5 px-5": size === "large",
|
|
74
|
+
"py-3 px-6": size === "xl",
|
|
75
|
+
"py-4 px-10": size === "2xl"
|
|
76
76
|
}
|
|
77
77
|
);
|
|
78
78
|
|
package/src/components/Card.tsx
CHANGED
|
@@ -1,33 +1,76 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import React, {
|
|
2
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { TextField, TextFieldProps } from "./index";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
type TextFieldChangeEvent = React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
const [internalValue, setInternalValue] = React.useState(props.value);
|
|
7
|
+
export function DebouncedTextField<T extends string | number>(props: TextFieldProps<T>) {
|
|
9
8
|
|
|
10
|
-
const
|
|
9
|
+
const [internalValue, setInternalValue] = useState<T | string>(props.value ?? "");
|
|
10
|
+
const lastSentValueRef = useRef<T | string>(props.value ?? "");
|
|
11
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
11
12
|
|
|
13
|
+
// Sync state with props.value when it changes externally
|
|
12
14
|
useEffect(() => {
|
|
13
|
-
|
|
15
|
+
const externalValue = props.value ?? "";
|
|
16
|
+
if (externalValue !== lastSentValueRef.current) {
|
|
17
|
+
setInternalValue(externalValue);
|
|
18
|
+
lastSentValueRef.current = externalValue;
|
|
19
|
+
}
|
|
14
20
|
}, [props.value]);
|
|
15
21
|
|
|
22
|
+
// Cleanup timer on unmount
|
|
16
23
|
useEffect(() => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
return () => {
|
|
25
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
26
|
+
};
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const flushChange = useCallback((value: T | string, event?: TextFieldChangeEvent) => {
|
|
30
|
+
if (timerRef.current) {
|
|
31
|
+
clearTimeout(timerRef.current);
|
|
32
|
+
timerRef.current = undefined;
|
|
33
|
+
}
|
|
34
|
+
if (value !== props.value && props.onChange) {
|
|
35
|
+
lastSentValueRef.current = value;
|
|
36
|
+
const e = {
|
|
37
|
+
...event,
|
|
38
|
+
target: {
|
|
39
|
+
...event?.target,
|
|
40
|
+
value: value,
|
|
41
|
+
name: props.name
|
|
42
|
+
}
|
|
43
|
+
} as TextFieldChangeEvent;
|
|
44
|
+
props.onChange(e);
|
|
22
45
|
}
|
|
23
|
-
}, [
|
|
46
|
+
}, [props.value, props.onChange, props.name]);
|
|
24
47
|
|
|
25
|
-
const internalOnChange = useCallback((event:
|
|
26
|
-
|
|
27
|
-
setInternalValue(
|
|
28
|
-
|
|
48
|
+
const internalOnChange = useCallback((event: TextFieldChangeEvent) => {
|
|
49
|
+
const newValue = event.target.value;
|
|
50
|
+
setInternalValue(newValue);
|
|
51
|
+
|
|
52
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
53
|
+
|
|
54
|
+
const eventCopy = {
|
|
55
|
+
...event,
|
|
56
|
+
target: {
|
|
57
|
+
...event?.target,
|
|
58
|
+
name: event?.target?.name
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
timerRef.current = setTimeout(() => {
|
|
63
|
+
flushChange(newValue, eventCopy as TextFieldChangeEvent);
|
|
64
|
+
}, 150);
|
|
65
|
+
}, [flushChange]);
|
|
66
|
+
|
|
67
|
+
const internalOnBlur = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
|
|
68
|
+
flushChange(internalValue, event as TextFieldChangeEvent);
|
|
69
|
+
props.onBlur?.(event);
|
|
70
|
+
}, [internalValue, flushChange, props.onBlur]);
|
|
29
71
|
|
|
30
72
|
return <TextField {...props}
|
|
31
73
|
onChange={internalOnChange}
|
|
32
|
-
|
|
74
|
+
onBlur={internalOnBlur}
|
|
75
|
+
value={internalValue as T}/>;
|
|
33
76
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { cls } from "../util";
|
|
3
3
|
|
|
4
|
-
export interface FilterChipProps {
|
|
4
|
+
export interface FilterChipProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
5
5
|
/**
|
|
6
6
|
* The text label displayed on the chip.
|
|
7
7
|
*/
|
|
@@ -10,10 +10,6 @@ export interface FilterChipProps {
|
|
|
10
10
|
* Whether the chip is currently in an active/selected state.
|
|
11
11
|
*/
|
|
12
12
|
active?: boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Callback when the chip is clicked.
|
|
15
|
-
*/
|
|
16
|
-
onClick?: () => void;
|
|
17
13
|
/**
|
|
18
14
|
* Optional icon rendered before the label.
|
|
19
15
|
*/
|
|
@@ -23,10 +19,6 @@ export interface FilterChipProps {
|
|
|
23
19
|
* @default "medium"
|
|
24
20
|
*/
|
|
25
21
|
size?: "small" | "medium";
|
|
26
|
-
/**
|
|
27
|
-
* Additional class names.
|
|
28
|
-
*/
|
|
29
|
-
className?: string;
|
|
30
22
|
/**
|
|
31
23
|
* Whether the chip is disabled.
|
|
32
24
|
*/
|
|
@@ -47,17 +39,19 @@ const sizeClasses = {
|
|
|
47
39
|
*
|
|
48
40
|
* @group Interactive components
|
|
49
41
|
*/
|
|
50
|
-
export function FilterChip({
|
|
42
|
+
export const FilterChip = React.forwardRef<HTMLButtonElement, FilterChipProps>(function FilterChip({
|
|
51
43
|
children,
|
|
52
44
|
active = false,
|
|
53
45
|
onClick,
|
|
54
46
|
icon,
|
|
55
47
|
size = "medium",
|
|
56
48
|
className,
|
|
57
|
-
disabled = false
|
|
58
|
-
|
|
49
|
+
disabled = false,
|
|
50
|
+
...rest
|
|
51
|
+
}: FilterChipProps, ref) {
|
|
59
52
|
return (
|
|
60
53
|
<button
|
|
54
|
+
ref={ref}
|
|
61
55
|
type="button"
|
|
62
56
|
onClick={onClick}
|
|
63
57
|
disabled={disabled}
|
|
@@ -76,9 +70,10 @@ export function FilterChip({
|
|
|
76
70
|
disabled && "opacity-50 cursor-not-allowed",
|
|
77
71
|
className
|
|
78
72
|
)}
|
|
73
|
+
{...rest}
|
|
79
74
|
>
|
|
80
75
|
{icon}
|
|
81
76
|
{children}
|
|
82
77
|
</button>
|
|
83
78
|
);
|
|
84
|
-
}
|
|
79
|
+
});
|
|
@@ -19,10 +19,10 @@ const buttonClasses = "hover:bg-surface-accent-200 hover:bg-opacity-75 hover:bg-
|
|
|
19
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";
|
|
20
20
|
const colorClasses = "text-surface-accent-500 visited:text-surface-accent-500 dark:text-surface-accent-300 dark:visited:text-surface-300";
|
|
21
21
|
const sizeClasses = {
|
|
22
|
-
medium: "w-
|
|
23
|
-
small: "w-
|
|
24
|
-
smallest: "w-
|
|
25
|
-
large: "w-
|
|
22
|
+
medium: "w-10 !h-10 min-w-10 min-h-10",
|
|
23
|
+
small: "w-8 !h-8 min-w-8 min-h-8",
|
|
24
|
+
smallest: "w-6 !h-6 min-w-6 min-h-6",
|
|
25
|
+
large: "w-12 !h-12 min-w-12 min-h-12"
|
|
26
26
|
}
|
|
27
27
|
const shapeClasses = {
|
|
28
28
|
circular: "rounded-full",
|
package/src/components/Tabs.tsx
CHANGED
|
@@ -107,7 +107,7 @@ export function Tabs({
|
|
|
107
107
|
msOverflowStyle: "none" }}
|
|
108
108
|
>
|
|
109
109
|
<TabsPrimitive.List className={cls(
|
|
110
|
-
variant === "standard" && "inline-flex h-
|
|
110
|
+
variant === "standard" && "inline-flex h-9 items-center justify-start rounded-md bg-surface-50 p-1 text-surface-600 dark:bg-surface-900 dark:text-surface-400 gap-2 border",
|
|
111
111
|
variant === "standard" && defaultBorderMixin,
|
|
112
112
|
variant === "boxy" && "flex items-center h-full",
|
|
113
113
|
variant === "pill" && "flex items-center gap-0.5",
|
|
@@ -161,7 +161,7 @@ export function Tab({
|
|
|
161
161
|
"inline-flex items-center justify-center whitespace-nowrap text-sm font-medium ring-offset-white transition-all",
|
|
162
162
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400 focus-visible:ring-offset-2",
|
|
163
163
|
"disabled:pointer-events-none disabled:opacity-50",
|
|
164
|
-
variant === "standard" && "rounded-sm px-3 py-1
|
|
164
|
+
variant === "standard" && "rounded-sm px-3 py-1 data-[state=active]:bg-white data-[state=active]:text-surface-900 data-[state=active]:shadow-sm dark:data-[state=active]:bg-surface-900 dark:data-[state=active]:text-surface-50",
|
|
165
165
|
variant === "boxy" && cls(
|
|
166
166
|
"flex-shrink-0 flex items-center gap-1.5 px-3.5 h-9 border-r border-surface-200 dark:border-surface-800 cursor-pointer text-[12px] font-medium transition-colors group relative box-border overflow-hidden",
|
|
167
167
|
"border-b-2 border-b-transparent",
|
|
@@ -212,9 +212,10 @@ export const VirtualTableHeaderRow = ({
|
|
|
212
212
|
className={cls(defaultBorderMixin, "z-20 sticky min-w-full flex w-fit flex-row top-0 left-0 border-b bg-surface-50 dark:bg-surface-900")}
|
|
213
213
|
style={{ height: headerHeight }}>
|
|
214
214
|
{columns.map((column, columnIndex) => {
|
|
215
|
+
const columnFilter = column && filter && filter[column.key] ? filter[column.key] : undefined;
|
|
215
216
|
const filterForThisProperty: [VirtualTableWhereFilterOp, unknown] | undefined =
|
|
216
|
-
|
|
217
|
-
?
|
|
217
|
+
columnFilter
|
|
218
|
+
? (Array.isArray(columnFilter[0]) ? (columnFilter as [VirtualTableWhereFilterOp, unknown][])[0] : (columnFilter as [VirtualTableWhereFilterOp, unknown]))
|
|
218
219
|
: undefined;
|
|
219
220
|
|
|
220
221
|
const isDraggable = !column.frozen && !!onColumnsOrderChange;
|
|
@@ -274,6 +274,25 @@ export type OnVirtualTableColumnResizeParams = {
|
|
|
274
274
|
column: VirtualTableColumn
|
|
275
275
|
};
|
|
276
276
|
|
|
277
|
+
/**
|
|
278
|
+
* @see Table
|
|
279
|
+
* @group Components
|
|
280
|
+
*/
|
|
281
|
+
export type WhereFilterOp =
|
|
282
|
+
| "<"
|
|
283
|
+
| "<="
|
|
284
|
+
| "=="
|
|
285
|
+
| "!="
|
|
286
|
+
| ">="
|
|
287
|
+
| ">"
|
|
288
|
+
| "array-contains"
|
|
289
|
+
| "in"
|
|
290
|
+
| "not-in"
|
|
291
|
+
| "array-contains-any";
|
|
292
|
+
|
|
293
|
+
export type FilterValues<Key extends string> =
|
|
294
|
+
Partial<Record<Key, [WhereFilterOp, unknown] | [WhereFilterOp, unknown][]>>;
|
|
295
|
+
|
|
277
296
|
/**
|
|
278
297
|
* @see Table
|
|
279
298
|
* @group Components
|
|
@@ -284,7 +303,7 @@ export type VirtualTableSort = "asc" | "desc" | undefined;
|
|
|
284
303
|
* @see Table
|
|
285
304
|
* @group Components
|
|
286
305
|
*/
|
|
287
|
-
export type VirtualTableFilterValues<Key extends string> =
|
|
306
|
+
export type VirtualTableFilterValues<Key extends string> = FilterValues<Key>;
|
|
288
307
|
|
|
289
308
|
/**
|
|
290
309
|
* Filter conditions in a `Query.where()` clause are specified using the
|
|
@@ -292,14 +311,4 @@ export type VirtualTableFilterValues<Key extends string> = Partial<Record<Key, [
|
|
|
292
311
|
* @see Table
|
|
293
312
|
* @group Models
|
|
294
313
|
*/
|
|
295
|
-
export type VirtualTableWhereFilterOp =
|
|
296
|
-
| "<"
|
|
297
|
-
| "<="
|
|
298
|
-
| "=="
|
|
299
|
-
| "!="
|
|
300
|
-
| ">="
|
|
301
|
-
| ">"
|
|
302
|
-
| "array-contains"
|
|
303
|
-
| "in"
|
|
304
|
-
| "not-in"
|
|
305
|
-
| "array-contains-any";
|
|
314
|
+
export type VirtualTableWhereFilterOp = WhereFilterOp;
|