mirror of
https://github.com/docmost/docmost.git
synced 2026-05-18 07:24:04 +08:00
feat(base): draft flow with save and cancel for new view sorts
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user