@rebasepro/ui 0.6.1 → 0.8.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/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +13 -0
- package/dist/components/VirtualTable/fields/VirtualTableInput.d.ts +10 -0
- package/dist/components/VirtualTable/fields/VirtualTableNumberInput.d.ts +9 -0
- package/dist/components/VirtualTable/fields/VirtualTableSelect.d.ts +17 -0
- package/dist/components/VirtualTable/fields/VirtualTableSwitch.d.ts +8 -0
- package/dist/components/VirtualTable/index.d.ts +7 -0
- package/dist/components/VirtualTable/selection/SelectionContext.d.ts +11 -0
- package/dist/components/VirtualTable/selection/SelectionStore.d.ts +14 -0
- package/dist/icons/index.d.ts +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.es.js +2322 -23
- package/dist/index.es.js.map +1 -1
- package/dist/styles.d.ts +1 -1
- package/dist/views/CardView.d.ts +32 -0
- package/dist/views/CollectionView/CollectionCardView.d.ts +30 -0
- package/dist/views/CollectionView/CollectionKanbanView.d.ts +31 -0
- package/dist/views/CollectionView/CollectionListView.d.ts +30 -0
- package/dist/views/CollectionView/CollectionTableView.d.ts +37 -0
- package/dist/views/CollectionView/CollectionView.d.ts +121 -0
- package/dist/views/CollectionView/CollectionViewToolbar.d.ts +31 -0
- package/dist/views/CollectionView/CollectionViewTypes.d.ts +106 -0
- package/dist/views/CollectionView/DefaultCellRenderer.d.ts +9 -0
- package/dist/views/CollectionView/index.d.ts +24 -0
- package/dist/views/CollectionView/utils.d.ts +5 -0
- package/dist/views/Kanban/Board.d.ts +3 -0
- package/dist/views/Kanban/BoardColumn.d.ts +20 -0
- package/dist/views/Kanban/BoardColumnTitle.d.ts +8 -0
- package/dist/views/Kanban/BoardSortableList.d.ts +14 -0
- package/dist/views/Kanban/board_types.d.ts +61 -0
- package/dist/views/Kanban/index.d.ts +5 -0
- package/dist/views/KanbanView.d.ts +24 -0
- package/dist/views/ListView.d.ts +40 -0
- package/dist/views/TableView.d.ts +6 -0
- package/dist/views/index.d.ts +5 -0
- package/package.json +1 -1
- package/src/components/Sheet.tsx +8 -4
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +32 -0
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +82 -0
- package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +71 -0
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +107 -0
- package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +28 -0
- package/src/components/VirtualTable/index.tsx +7 -0
- package/src/components/VirtualTable/selection/SelectionContext.tsx +36 -0
- package/src/components/VirtualTable/selection/SelectionStore.ts +54 -0
- package/src/icons/index.ts +3 -0
- package/src/index.ts +43 -3
- package/src/styles.ts +1 -1
- package/src/views/CardView.tsx +281 -0
- package/src/views/CollectionView/CollectionCardView.tsx +270 -0
- package/src/views/CollectionView/CollectionKanbanView.tsx +251 -0
- package/src/views/CollectionView/CollectionListView.tsx +245 -0
- package/src/views/CollectionView/CollectionTableView.tsx +229 -0
- package/src/views/CollectionView/CollectionView.tsx +388 -0
- package/src/views/CollectionView/CollectionViewToolbar.tsx +229 -0
- package/src/views/CollectionView/CollectionViewTypes.ts +137 -0
- package/src/views/CollectionView/DefaultCellRenderer.tsx +404 -0
- package/src/views/CollectionView/index.ts +44 -0
- package/src/views/CollectionView/utils.ts +14 -0
- package/src/views/Kanban/Board.tsx +421 -0
- package/src/views/Kanban/BoardColumn.tsx +135 -0
- package/src/views/Kanban/BoardColumnTitle.tsx +47 -0
- package/src/views/Kanban/BoardSortableList.tsx +170 -0
- package/src/views/Kanban/board_types.ts +70 -0
- package/src/views/Kanban/index.tsx +5 -0
- package/src/views/KanbanView.tsx +32 -0
- package/src/views/ListView.tsx +281 -0
- package/src/views/TableView.tsx +7 -0
- package/src/views/index.ts +5 -0
package/package.json
CHANGED
package/src/components/Sheet.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
|
|
|
3
3
|
import { cls } from "../util";
|
|
4
4
|
import { defaultBorderMixin } from "../styles";
|
|
5
5
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
6
|
-
import { usePortalContainer } from "../hooks/PortalContainerContext";
|
|
6
|
+
import { usePortalContainer, PortalContainerProvider } from "../hooks/PortalContainerContext";
|
|
7
7
|
|
|
8
8
|
interface SheetProps {
|
|
9
9
|
children: React.ReactNode;
|
|
@@ -45,6 +45,7 @@ export const Sheet: React.FC<SheetProps> = ({
|
|
|
45
45
|
...props
|
|
46
46
|
}) => {
|
|
47
47
|
const [displayed, setDisplayed] = useState(false);
|
|
48
|
+
const [contentEl, setContentEl] = useState<HTMLDivElement | null>(null);
|
|
48
49
|
|
|
49
50
|
// Get the portal container from context
|
|
50
51
|
const contextContainer = usePortalContainer();
|
|
@@ -90,7 +91,8 @@ export const Sheet: React.FC<SheetProps> = ({
|
|
|
90
91
|
/>}
|
|
91
92
|
<DialogPrimitive.Content
|
|
92
93
|
{...props}
|
|
93
|
-
|
|
94
|
+
ref={setContentEl}
|
|
95
|
+
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
94
96
|
onPointerDownOutside={onPointerDownOutside}
|
|
95
97
|
onInteractOutside={onInteractOutside}
|
|
96
98
|
className={cls(
|
|
@@ -112,10 +114,12 @@ export const Sheet: React.FC<SheetProps> = ({
|
|
|
112
114
|
)}
|
|
113
115
|
style={style}
|
|
114
116
|
>
|
|
115
|
-
<DialogPrimitive.Title
|
|
117
|
+
<DialogPrimitive.Title className="sr-only outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus:ring-0">
|
|
116
118
|
{title ?? "Sheet"}
|
|
117
119
|
</DialogPrimitive.Title>
|
|
118
|
-
{
|
|
120
|
+
<PortalContainerProvider container={contentEl}>
|
|
121
|
+
{children}
|
|
122
|
+
</PortalContainerProvider>
|
|
119
123
|
</DialogPrimitive.Content>
|
|
120
124
|
</DialogPrimitive.Portal>
|
|
121
125
|
</DialogPrimitive.Root>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cls } from "../../../util";
|
|
3
|
+
import { focusedDisabled } from "../../../styles";
|
|
4
|
+
import { DateTimeField } from "../../DateTimeField";
|
|
5
|
+
|
|
6
|
+
export function VirtualTableDateField(props: {
|
|
7
|
+
name?: string;
|
|
8
|
+
error?: Error;
|
|
9
|
+
mode?: "date" | "date_time";
|
|
10
|
+
timezone?: string;
|
|
11
|
+
internalValue: Date | undefined | null;
|
|
12
|
+
updateValue: (newValue: (Date | null)) => void;
|
|
13
|
+
focused: boolean;
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
locale?: string;
|
|
16
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
|
|
17
|
+
}) {
|
|
18
|
+
const { disabled, error, mode, timezone, internalValue, updateValue, locale } = props;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<DateTimeField
|
|
22
|
+
value={internalValue ?? undefined}
|
|
23
|
+
onChange={(dateValue) => updateValue(dateValue ?? null)}
|
|
24
|
+
invisible={true}
|
|
25
|
+
inputClassName={cls("w-full h-full", focusedDisabled)}
|
|
26
|
+
className={cls("w-full h-full", focusedDisabled)}
|
|
27
|
+
mode={mode}
|
|
28
|
+
timezone={timezone}
|
|
29
|
+
locale={locale}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useDebouncedCallback } from "../../../hooks/useDebouncedCallback";
|
|
3
|
+
import { focusedDisabled } from "../../../styles";
|
|
4
|
+
import { TextareaAutosize } from "../../TextareaAutosize";
|
|
5
|
+
|
|
6
|
+
export function VirtualTableInput(props: {
|
|
7
|
+
error?: Error;
|
|
8
|
+
value: string;
|
|
9
|
+
multiline?: boolean;
|
|
10
|
+
focused: boolean;
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
updateValue: (newValue: (string | null)) => void;
|
|
13
|
+
onBlur?: () => void;
|
|
14
|
+
}) {
|
|
15
|
+
const ref = React.useRef<HTMLTextAreaElement>(null);
|
|
16
|
+
const { disabled, value, multiline = false, updateValue, focused } = props;
|
|
17
|
+
const prevValue = useRef<string | null>(value);
|
|
18
|
+
const [internalValue, setInternalValue] = useState<typeof value>(value);
|
|
19
|
+
const focusedState = useRef<boolean>(false);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (prevValue.current !== value && value !== internalValue)
|
|
23
|
+
setInternalValue(value);
|
|
24
|
+
prevValue.current = value;
|
|
25
|
+
}, [value]);
|
|
26
|
+
|
|
27
|
+
const doUpdate = React.useCallback(() => {
|
|
28
|
+
const emptyInitialValue = !value;
|
|
29
|
+
if (emptyInitialValue && !internalValue) return;
|
|
30
|
+
if (internalValue !== value && internalValue !== prevValue.current) {
|
|
31
|
+
prevValue.current = internalValue;
|
|
32
|
+
updateValue(internalValue);
|
|
33
|
+
}
|
|
34
|
+
}, [internalValue, updateValue, value]);
|
|
35
|
+
|
|
36
|
+
useDebouncedCallback(internalValue, doUpdate, !focused, 400);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (ref.current && focused && !focusedState.current) {
|
|
40
|
+
focusedState.current = true;
|
|
41
|
+
ref.current.focus({ preventScroll: true });
|
|
42
|
+
ref.current.selectionStart = ref.current.value.length;
|
|
43
|
+
ref.current.selectionEnd = ref.current.value.length;
|
|
44
|
+
} else {
|
|
45
|
+
focusedState.current = focused;
|
|
46
|
+
}
|
|
47
|
+
}, [focused, ref]);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<TextareaAutosize
|
|
51
|
+
className={focusedDisabled}
|
|
52
|
+
ref={ref}
|
|
53
|
+
style={{
|
|
54
|
+
padding: 0,
|
|
55
|
+
margin: 0,
|
|
56
|
+
width: "100%",
|
|
57
|
+
color: "unset",
|
|
58
|
+
fontWeight: "unset",
|
|
59
|
+
fontSize: "unset",
|
|
60
|
+
fontFamily: "unset",
|
|
61
|
+
background: "unset",
|
|
62
|
+
border: "unset",
|
|
63
|
+
resize: "none",
|
|
64
|
+
outline: "none"
|
|
65
|
+
}}
|
|
66
|
+
value={internalValue ?? ""}
|
|
67
|
+
onChange={(evt) => {
|
|
68
|
+
const newValue = evt.target.value as string;
|
|
69
|
+
if (multiline || !newValue.endsWith("\n"))
|
|
70
|
+
setInternalValue(newValue);
|
|
71
|
+
}}
|
|
72
|
+
onFocus={() => {
|
|
73
|
+
focusedState.current = true;
|
|
74
|
+
}}
|
|
75
|
+
onBlur={() => {
|
|
76
|
+
focusedState.current = false;
|
|
77
|
+
doUpdate();
|
|
78
|
+
if (props.onBlur) props.onBlur();
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useDebouncedCallback } from "../../../hooks/useDebouncedCallback";
|
|
3
|
+
import { cls } from "../../../util";
|
|
4
|
+
import { focusedDisabled } from "../../../styles";
|
|
5
|
+
import { TextField } from "../../TextField";
|
|
6
|
+
|
|
7
|
+
export function VirtualTableNumberInput(props: {
|
|
8
|
+
error?: Error;
|
|
9
|
+
value: number;
|
|
10
|
+
align?: "right" | "left" | "center";
|
|
11
|
+
updateValue: (newValue: (number | null)) => void;
|
|
12
|
+
focused: boolean;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
}) {
|
|
15
|
+
const { align = "left", value, updateValue, focused } = props;
|
|
16
|
+
const propStringValue = (value && typeof value === "number") ? value.toString() : "";
|
|
17
|
+
const [internalValue, setInternalValue] = useState<string | null>(propStringValue);
|
|
18
|
+
const prevValue = useRef<number | null>(value);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (prevValue.current !== value && String(value) !== internalValue)
|
|
22
|
+
setInternalValue(value ? value.toString() : null);
|
|
23
|
+
prevValue.current = value;
|
|
24
|
+
}, [value]);
|
|
25
|
+
|
|
26
|
+
const doUpdate = React.useCallback(() => {
|
|
27
|
+
if (internalValue !== propStringValue) {
|
|
28
|
+
if (internalValue !== undefined && internalValue !== null) {
|
|
29
|
+
const numberValue = parseFloat(internalValue);
|
|
30
|
+
if (isNaN(numberValue)) return;
|
|
31
|
+
updateValue(numberValue);
|
|
32
|
+
} else {
|
|
33
|
+
updateValue(null);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, [internalValue, value]);
|
|
37
|
+
|
|
38
|
+
useDebouncedCallback(internalValue, doUpdate, !focused, 400);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!focused && propStringValue !== internalValue)
|
|
42
|
+
setInternalValue(value !== undefined && value !== null ? value.toString() : null);
|
|
43
|
+
}, [value, focused]);
|
|
44
|
+
|
|
45
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (inputRef.current && focused) {
|
|
49
|
+
inputRef.current.focus({ preventScroll: true });
|
|
50
|
+
}
|
|
51
|
+
}, [focused, inputRef]);
|
|
52
|
+
|
|
53
|
+
const regexp = /^-?[0-9]+[,.]?[0-9]*$/;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<TextField
|
|
57
|
+
inputRef={inputRef}
|
|
58
|
+
invisible={true}
|
|
59
|
+
size="small"
|
|
60
|
+
className="w-full"
|
|
61
|
+
inputClassName={cls("p-0 m-0 bg-transparent border-none outline-hidden font-normal leading-normal text-unset", focusedDisabled)}
|
|
62
|
+
inputStyle={{ textAlign: align }}
|
|
63
|
+
value={internalValue ?? ""}
|
|
64
|
+
onChange={(evt) => {
|
|
65
|
+
const newValue = evt.target.value.replace(",", ".");
|
|
66
|
+
if (newValue.length === 0) setInternalValue(null);
|
|
67
|
+
if (regexp.test(newValue) || newValue.startsWith("-")) setInternalValue(newValue);
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { Select, SelectItem } from "../../Select";
|
|
3
|
+
import { MultiSelect, MultiSelectItem } from "../../MultiSelect";
|
|
4
|
+
import { Chip } from "../../Chip";
|
|
5
|
+
|
|
6
|
+
export interface VirtualTableSelectOption {
|
|
7
|
+
value: string | number;
|
|
8
|
+
label: string;
|
|
9
|
+
color?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function VirtualTableSelect(props: {
|
|
13
|
+
options: VirtualTableSelectOption[];
|
|
14
|
+
error?: Error;
|
|
15
|
+
multiple?: boolean;
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
small?: boolean;
|
|
18
|
+
value: string | number | string[] | number[] | undefined;
|
|
19
|
+
updateValue: (newValue: (string | number | string[] | number[] | null)) => void;
|
|
20
|
+
focused: boolean;
|
|
21
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
|
|
22
|
+
}) {
|
|
23
|
+
const { options, value, disabled, small = false, focused, updateValue, multiple = false } = props;
|
|
24
|
+
const validValue = (Array.isArray(value) && multiple) || (!Array.isArray(value) && !multiple);
|
|
25
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (ref.current && focused) {
|
|
29
|
+
ref.current?.focus({ preventScroll: true });
|
|
30
|
+
}
|
|
31
|
+
}, [focused, ref]);
|
|
32
|
+
|
|
33
|
+
const onChange = useCallback((updatedValue: string | string[]) => {
|
|
34
|
+
if (multiple) {
|
|
35
|
+
const isNumber = options.some(o => typeof o.value === "number");
|
|
36
|
+
const newValue = (updatedValue as string[]).map((v) => {
|
|
37
|
+
const opt = options.find(o => String(o.value) === v);
|
|
38
|
+
return typeof opt?.value === "number" ? parseFloat(v) : v;
|
|
39
|
+
});
|
|
40
|
+
if (isNumber) {
|
|
41
|
+
updateValue(newValue as number[]);
|
|
42
|
+
} else {
|
|
43
|
+
updateValue(newValue as string[]);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
const opt = options.find(o => String(o.value) === updatedValue);
|
|
47
|
+
updateValue(typeof opt?.value === "number" ? parseFloat(updatedValue as string) : (updatedValue as string || null));
|
|
48
|
+
}
|
|
49
|
+
}, [multiple, updateValue, options]);
|
|
50
|
+
|
|
51
|
+
const renderValue = (val?: string | number) => {
|
|
52
|
+
const option = options.find(o => o.value === val);
|
|
53
|
+
return (
|
|
54
|
+
<Chip size={small ? "small" : "medium"}>
|
|
55
|
+
{option ? option.label : String(val)}
|
|
56
|
+
</Chip>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleOpenChange = useCallback((open: boolean) => {
|
|
61
|
+
if (!open && ref.current) {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
ref.current?.focus({ preventScroll: true });
|
|
64
|
+
}, 0);
|
|
65
|
+
}
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
return multiple ? (
|
|
69
|
+
<MultiSelect
|
|
70
|
+
inputRef={ref}
|
|
71
|
+
className="w-full h-full p-0 bg-transparent outline-none"
|
|
72
|
+
position={"item-aligned"}
|
|
73
|
+
disabled={disabled}
|
|
74
|
+
includeClear={false}
|
|
75
|
+
useChips={false}
|
|
76
|
+
value={validValue ? (value as (string | number)[]).map(v => String(v)) : []}
|
|
77
|
+
onValueChange={onChange}
|
|
78
|
+
onOpenChange={handleOpenChange}
|
|
79
|
+
>
|
|
80
|
+
{options.map((opt) => (
|
|
81
|
+
<MultiSelectItem key={String(opt.value)} value={String(opt.value)}>
|
|
82
|
+
<Chip size={small ? "small" : "medium"}>{opt.label}</Chip>
|
|
83
|
+
</MultiSelectItem>
|
|
84
|
+
))}
|
|
85
|
+
</MultiSelect>
|
|
86
|
+
) : (
|
|
87
|
+
<Select
|
|
88
|
+
inputRef={ref}
|
|
89
|
+
fullWidth
|
|
90
|
+
className="w-full h-full p-0 bg-transparent outline-none [&_button]:ring-0 [&_button]:ring-offset-0"
|
|
91
|
+
inputClassName="ring-0 ring-offset-0 focus:ring-0 focus-visible:ring-0 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-offset-0"
|
|
92
|
+
position={"item-aligned"}
|
|
93
|
+
disabled={disabled}
|
|
94
|
+
padding={false}
|
|
95
|
+
value={validValue ? String(value) : ""}
|
|
96
|
+
onValueChange={onChange}
|
|
97
|
+
onOpenChange={handleOpenChange}
|
|
98
|
+
renderValue={renderValue}
|
|
99
|
+
>
|
|
100
|
+
{options.map((opt) => (
|
|
101
|
+
<SelectItem key={String(opt.value)} value={String(opt.value)}>
|
|
102
|
+
<Chip size={small ? "small" : "medium"}>{opt.label}</Chip>
|
|
103
|
+
</SelectItem>
|
|
104
|
+
))}
|
|
105
|
+
</Select>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { BooleanSwitch } from "../../BooleanSwitch";
|
|
3
|
+
|
|
4
|
+
export function VirtualTableSwitch(props: {
|
|
5
|
+
error?: Error;
|
|
6
|
+
internalValue?: boolean;
|
|
7
|
+
focused: boolean;
|
|
8
|
+
disabled: boolean;
|
|
9
|
+
updateValue: (newValue: (boolean | null)) => void;
|
|
10
|
+
}) {
|
|
11
|
+
const { internalValue, updateValue, focused } = props;
|
|
12
|
+
const ref = React.useRef<HTMLButtonElement>(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (ref.current && focused) {
|
|
16
|
+
ref.current.focus({ preventScroll: true });
|
|
17
|
+
}
|
|
18
|
+
}, [focused, ref]);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<BooleanSwitch
|
|
22
|
+
ref={ref}
|
|
23
|
+
size={"small"}
|
|
24
|
+
value={Boolean(internalValue)}
|
|
25
|
+
onValueChange={updateValue}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export { VirtualTable } from "./VirtualTable";
|
|
2
2
|
export * from "./VirtualTableProps";
|
|
3
3
|
export type { FilterFormFieldProps } from "./VirtualTableHeader";
|
|
4
|
+
export * from "./selection/SelectionStore";
|
|
5
|
+
export * from "./selection/SelectionContext";
|
|
6
|
+
export * from "./fields/VirtualTableInput";
|
|
7
|
+
export * from "./fields/VirtualTableNumberInput";
|
|
8
|
+
export * from "./fields/VirtualTableSwitch";
|
|
9
|
+
export * from "./fields/VirtualTableDateField";
|
|
10
|
+
export * from "./fields/VirtualTableSelect";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { createContext, useContext } from "react";
|
|
2
|
+
import { VirtualTableSelectionStore, SelectedCell } from "./SelectionStore";
|
|
3
|
+
|
|
4
|
+
export interface VirtualTableSelectionContextProps<T extends SelectedCell = SelectedCell> {
|
|
5
|
+
selectionStore: VirtualTableSelectionStore<T>;
|
|
6
|
+
select: (cell: T | undefined) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const SelectionContext = createContext<VirtualTableSelectionContextProps<any> | null>(null);
|
|
10
|
+
|
|
11
|
+
export const VirtualTableSelectionProvider = <T extends SelectedCell = SelectedCell>({
|
|
12
|
+
store,
|
|
13
|
+
children
|
|
14
|
+
}: {
|
|
15
|
+
store: VirtualTableSelectionStore<T>;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
}) => {
|
|
18
|
+
const value = React.useMemo(() => ({
|
|
19
|
+
selectionStore: store,
|
|
20
|
+
select: store.select
|
|
21
|
+
}), [store]);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<SelectionContext.Provider value={value}>
|
|
25
|
+
{children}
|
|
26
|
+
</SelectionContext.Provider>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const useVirtualTableSelection = <T extends SelectedCell = SelectedCell>() => {
|
|
31
|
+
const context = useContext(SelectionContext);
|
|
32
|
+
if (!context) {
|
|
33
|
+
throw new Error("useVirtualTableSelection must be used within a VirtualTableSelectionProvider");
|
|
34
|
+
}
|
|
35
|
+
return context as VirtualTableSelectionContextProps<T>;
|
|
36
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useSyncExternalStore, useCallback, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
export interface SelectedCell {
|
|
4
|
+
columnKey: string;
|
|
5
|
+
rowId: string | number;
|
|
6
|
+
cellRect?: DOMRect;
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createVirtualTableSelectionStore<T extends SelectedCell = SelectedCell>() {
|
|
12
|
+
let selectedCell: T | undefined = undefined;
|
|
13
|
+
const listeners = new Set<() => void>();
|
|
14
|
+
|
|
15
|
+
function getSnapshot(): T | undefined {
|
|
16
|
+
return selectedCell;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function subscribe(listener: () => void): () => void {
|
|
20
|
+
listeners.add(listener);
|
|
21
|
+
return () => listeners.delete(listener);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function select(cell: T | undefined) {
|
|
25
|
+
selectedCell = cell;
|
|
26
|
+
listeners.forEach(l => l());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
getSnapshot,
|
|
31
|
+
subscribe,
|
|
32
|
+
select
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type VirtualTableSelectionStore<T extends SelectedCell = SelectedCell> = ReturnType<typeof createVirtualTableSelectionStore<T>>;
|
|
37
|
+
|
|
38
|
+
export function useVirtualTableCellSelected<T extends SelectedCell = SelectedCell>(
|
|
39
|
+
store: VirtualTableSelectionStore<T>,
|
|
40
|
+
columnKey: string,
|
|
41
|
+
rowId: string | number
|
|
42
|
+
): boolean {
|
|
43
|
+
const selectorRef = useRef({ columnKey, rowId });
|
|
44
|
+
selectorRef.current = { columnKey, rowId };
|
|
45
|
+
|
|
46
|
+
const getSnapshot = useCallback(() => {
|
|
47
|
+
const cell = store.getSnapshot();
|
|
48
|
+
if (!cell) return false;
|
|
49
|
+
const s = selectorRef.current;
|
|
50
|
+
return cell.columnKey === s.columnKey && cell.rowId === s.rowId;
|
|
51
|
+
}, [store]);
|
|
52
|
+
|
|
53
|
+
return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
|
|
54
|
+
}
|
package/src/icons/index.ts
CHANGED
|
@@ -74,7 +74,9 @@ export {
|
|
|
74
74
|
LanguagesIcon,
|
|
75
75
|
LayoutGridIcon,
|
|
76
76
|
LinkIcon,
|
|
77
|
+
Link2Icon,
|
|
77
78
|
ListIcon,
|
|
79
|
+
LockIcon,
|
|
78
80
|
ListOrderedIcon,
|
|
79
81
|
LoaderIcon,
|
|
80
82
|
LogOutIcon,
|
|
@@ -121,6 +123,7 @@ export {
|
|
|
121
123
|
UploadIcon,
|
|
122
124
|
UserCheckIcon,
|
|
123
125
|
UserIcon,
|
|
126
|
+
UserPlus,
|
|
124
127
|
VideoIcon,
|
|
125
128
|
VoteIcon,
|
|
126
129
|
Wand2Icon,
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Components
|
|
3
|
+
// =============================================================================
|
|
1
4
|
export * from "./components";
|
|
2
|
-
|
|
3
|
-
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Views
|
|
8
|
+
// =============================================================================
|
|
9
|
+
export * from "./views";
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Icons
|
|
13
|
+
// =============================================================================
|
|
4
14
|
export * from "./icons";
|
|
5
|
-
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Design System Utilities
|
|
18
|
+
// =============================================================================
|
|
19
|
+
export * from "./styles";
|
|
20
|
+
export { cls } from "./util/cls";
|
|
21
|
+
export { CHIP_COLORS, getColorSchemeForKey, getColorSchemeForSeed } from "./util/chip_colors";
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Public Hooks
|
|
25
|
+
// =============================================================================
|
|
26
|
+
export { useOutsideAlerter } from "./hooks/useOutsideAlerter";
|
|
27
|
+
export { useDebouncedCallback } from "./hooks/useDebouncedCallback";
|
|
28
|
+
export { useDebounceCallback } from "./hooks/useDebounceCallback";
|
|
29
|
+
export { useDebounceValue } from "./hooks/useDebounceValue";
|
|
30
|
+
export { useInjectStyles } from "./hooks/useInjectStyles";
|
|
31
|
+
export { PortalContainerProvider, usePortalContainer } from "./hooks/PortalContainerContext";
|
|
32
|
+
export type { PortalContainerContextType, PortalContainerProviderProps } from "./hooks/PortalContainerContext";
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Lower-level utilities (used by framework packages)
|
|
36
|
+
// =============================================================================
|
|
37
|
+
export { debounce } from "./util/debounce";
|
|
38
|
+
export type { Cancelable } from "./util/debounce";
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// NOT exported (internal plumbing — import directly if needed):
|
|
42
|
+
//
|
|
43
|
+
// ./util/hash – hashString
|
|
44
|
+
// ./util/key_to_icon_component – keyToIconComponent
|
|
45
|
+
// =============================================================================
|
package/src/styles.ts
CHANGED
|
@@ -5,7 +5,7 @@ export const fieldBackgroundMixin = "bg-surface-accent-200/50 dark:bg-black/30";
|
|
|
5
5
|
export const fieldBackgroundInvisibleMixin = "bg-surface-accent-200/0 dark:bg-black/0";
|
|
6
6
|
export const fieldBackgroundDisabledMixin = "bg-surface-accent-200/50 dark:bg-black/20";
|
|
7
7
|
export const fieldBackgroundHoverMixin = "hover:bg-surface-accent-200/70 hover:dark:bg-black/50";
|
|
8
|
-
export const defaultBorderMixin = "border-surface-200 dark:border-surface-700 ";
|
|
8
|
+
export const defaultBorderMixin = "border-surface-200 dark:border-surface-700/60 ";
|
|
9
9
|
export const paperMixin = "bg-white rounded-lg dark:bg-surface-900 border border-surface-200 dark:border-surface-700";
|
|
10
10
|
export const cardMixin = "bg-white dark:bg-surface-900 rounded-lg border border-surface-200 dark:border-surface-700";
|
|
11
11
|
export const cardClickableMixin = "hover:bg-primary/5 dark:hover:bg-primary/5 cursor-pointer transition-colors duration-150";
|