Simplify editable cell signature

Signed-off-by: Jonathan <contact@grafikart.fr>
This commit is contained in:
Jonathan
2025-11-14 19:22:46 +01:00
committed by Émile Ré
parent cb5daab175
commit 789775f2fe
21 changed files with 762 additions and 451 deletions

View File

@@ -0,0 +1,137 @@
import { type ReactNode, Suspense } from "react";
import { useEditableCellRef } from "@probo/ui/src/Molecules/Table/EditableCell.tsx";
import { getKey } from "@probo/ui/src/Molecules/Table/utils.ts";
import { useStateWithRef } from "@probo/hooks";
import { useEditableRowContext } from "@probo/ui/src/Molecules/Table/EditableRow.tsx";
import { EditableCell, selectCell, SelectValue, Spinner } from "@probo/ui";
import { Command } from "cmdk";
import type {
GraphQLTaggedNode,
OperationType,
VariablesOf,
} from "relay-runtime";
import { useLazyLoadQuery } from "react-relay";
import { useTranslate } from "@probo/i18n";
type Props<Q extends OperationType, T> = {
name: string;
query: GraphQLTaggedNode;
variables: VariablesOf<Q>;
items: (v: ReturnType<typeof useLazyLoadQuery<Q>>) => T[];
itemRenderer: (v: { item: T; onRemove?: (item: T) => void }) => ReactNode;
} & (
| { defaultValue?: T; multiple?: undefined }
| { defaultValue: T[]; multiple: true }
);
export function GraphQLCell<Q extends OperationType, T>(props: Props<Q, T>) {
const [value, setValue, valueRef] = useStateWithRef<T | T[] | undefined>(
props.defaultValue,
);
const cellRef = useEditableCellRef();
const { __ } = useTranslate();
const usedKeys = new Set<string>(
Array.isArray(value) ? value.map(getKey) : [getKey(value)],
);
const { onUpdate } = useEditableRowContext();
const onSelect = (item: T) => {
if (props.multiple) {
setValue([...((valueRef.current as T[]) ?? []), item]);
return;
}
setValue(item);
cellRef.current?.close();
};
const onClose = () => {
if (valueRef.current === props.defaultValue) {
return;
}
// Only send ids when updating the value
onUpdate(
props.name,
Array.isArray(valueRef.current)
? valueRef.current.map(getKey)
: getKey(valueRef.current),
);
};
const classNames = selectCell();
return (
<EditableCell
name={props.name}
label={<SelectValue value={value} itemRenderer={props.itemRenderer} />}
onClose={onClose}
ref={cellRef}
>
<Command className={classNames.command()}>
<div
className={classNames.value()}
style={{
paddingLeft: "var(--padding)",
minHeight: "var(--height)",
}}
>
{" "}
<SelectValue
onValueChange={setValue}
value={value}
itemRenderer={props.itemRenderer}
/>
</div>{" "}
{props.multiple && (
<Command.Input
className={classNames.input()}
placeholder={__("Search")}
/>
)}
<Command.List>
<Suspense
fallback={
<div className="py-2 px-3 flex items-center justify-center">
<Spinner />
</div>
}
>
<ItemList
{...props}
usedKeys={usedKeys}
className={classNames.item()}
onSelect={onSelect}
/>
</Suspense>
</Command.List>
</Command>
</EditableCell>
);
}
function ItemList<Q extends OperationType, T>(
props: Props<Q, T> & {
className: string;
onSelect: (item: T) => void;
usedKeys: Set<string>;
},
) {
const data = useLazyLoadQuery<Q>(props.query, props.variables, {
fetchPolicy: "network-only",
});
const items = props.items(data);
return (
<>
{items
.filter((item) => !props.usedKeys.has(getKey(item)))
.map((item) => (
<Command.Item
key={getKey(item)}
className={props.className}
onSelect={() => props.onSelect(item)}
>
{props.itemRenderer({ item })}
</Command.Item>
))}
</>
);
}