feat(base): draft flow with save and cancel for new view sorts

This commit is contained in:
Philipinho
2026-04-18 22:38:28 +01:00
parent 321184394d
commit fd1257f61c
@@ -1,4 +1,4 @@
import { useCallback } from "react"; import { useCallback, useEffect, useState } from "react";
import { import {
Popover, Popover,
Stack, Stack,
@@ -7,8 +7,9 @@ import {
ActionIcon, ActionIcon,
Text, Text,
UnstyledButton, UnstyledButton,
Button,
} from "@mantine/core"; } from "@mantine/core";
import { IconPlus, IconTrash, IconSortAscending } from "@tabler/icons-react"; import { IconPlus, IconTrash } from "@tabler/icons-react";
import { import {
IBaseProperty, IBaseProperty,
ViewSortConfig, ViewSortConfig,
@@ -33,6 +34,12 @@ export function ViewSortConfigPopover({
children, children,
}: ViewSortConfigProps) { }: ViewSortConfigProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [draft, setDraft] = useState<ViewSortConfig | null>(null);
// Discard any half-configured draft when the popover closes.
useEffect(() => {
if (!opened) setDraft(null);
}, [opened]);
const propertyOptions = properties.map((p) => ({ const propertyOptions = properties.map((p) => ({
value: p.id, value: p.id,
@@ -44,12 +51,22 @@ export function ViewSortConfigPopover({
{ value: "desc", label: t("Descending") }, { value: "desc", label: t("Descending") },
]; ];
const handleAdd = useCallback(() => { const handleStartDraft = useCallback(() => {
const usedIds = new Set(sorts.map((s) => s.propertyId)); const usedIds = new Set(sorts.map((s) => s.propertyId));
const available = properties.find((p) => !usedIds.has(p.id)); const available = properties.find((p) => !usedIds.has(p.id));
if (!available) return; if (!available) return;
onChange([...sorts, { propertyId: available.id, direction: "asc" }]); setDraft({ propertyId: available.id, direction: "asc" });
}, [sorts, properties, onChange]); }, [sorts, properties]);
const handleSaveDraft = useCallback(() => {
if (!draft) return;
onChange([...sorts, draft]);
setDraft(null);
}, [draft, sorts, onChange]);
const handleCancelDraft = useCallback(() => {
setDraft(null);
}, []);
const handleRemove = useCallback( const handleRemove = useCallback(
(index: number) => { (index: number) => {
@@ -82,6 +99,8 @@ export function ViewSortConfigPopover({
[sorts, onChange], [sorts, onChange],
); );
const canAddMore = properties.length > sorts.length + (draft ? 1 : 0);
return ( return (
<Popover <Popover
opened={opened} opened={opened}
@@ -99,7 +118,7 @@ export function ViewSortConfigPopover({
{t("Sort by")} {t("Sort by")}
</Text> </Text>
{sorts.length === 0 && ( {sorts.length === 0 && !draft && (
<Text size="xs" c="dimmed"> <Text size="xs" c="dimmed">
{t("No sorts applied")} {t("No sorts applied")}
</Text> </Text>
@@ -132,20 +151,63 @@ export function ViewSortConfigPopover({
</Group> </Group>
))} ))}
<UnstyledButton {draft && (
onClick={handleAdd} <Stack gap={6}>
style={{ <Group gap="xs" wrap="nowrap">
display: "flex", <Select
alignItems: "center", size="xs"
gap: 6, data={propertyOptions}
padding: "4px 0", value={draft.propertyId}
fontSize: "var(--mantine-font-size-xs)", onChange={(val) =>
color: "var(--mantine-color-blue-6)", val && setDraft({ ...draft, propertyId: val })
}} }
> style={{ flex: 1 }}
<IconPlus size={14} /> />
{t("Add sort")} <Select
</UnstyledButton> size="xs"
data={directionOptions}
value={draft.direction}
onChange={(val) =>
val &&
setDraft({
...draft,
direction: val as "asc" | "desc",
})
}
w={110}
/>
</Group>
<Group justify="flex-end" gap="xs">
<Button
variant="default"
size="xs"
onClick={handleCancelDraft}
>
{t("Cancel")}
</Button>
<Button size="xs" onClick={handleSaveDraft}>
{t("Save")}
</Button>
</Group>
</Stack>
)}
{!draft && canAddMore && (
<UnstyledButton
onClick={handleStartDraft}
style={{
display: "flex",
alignItems: "center",
gap: 6,
padding: "4px 0",
fontSize: "var(--mantine-font-size-xs)",
color: "var(--mantine-color-blue-6)",
}}
>
<IconPlus size={14} />
{t("Add sort")}
</UnstyledButton>
)}
</Stack> </Stack>
</Popover.Dropdown> </Popover.Dropdown>
</Popover> </Popover>