mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat(base): keyboard navigation for status cell dropdown
This commit is contained in:
@@ -7,6 +7,8 @@ import {
|
|||||||
} from "@/features/base/types/base.types";
|
} from "@/features/base/types/base.types";
|
||||||
import { choiceColor } from "@/features/base/components/cells/choice-color";
|
import { choiceColor } from "@/features/base/components/cells/choice-color";
|
||||||
import cellClasses from "@/features/base/styles/cells.module.css";
|
import cellClasses from "@/features/base/styles/cells.module.css";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useListKeyboardNav } from "@/features/base/hooks/use-list-keyboard-nav";
|
||||||
|
|
||||||
type CellStatusProps = {
|
type CellStatusProps = {
|
||||||
value: unknown;
|
value: unknown;
|
||||||
@@ -73,6 +75,19 @@ export function CellStatus({
|
|||||||
return result;
|
return result;
|
||||||
}, [choices, search]);
|
}, [choices, search]);
|
||||||
|
|
||||||
|
const flatChoices = useMemo(
|
||||||
|
() => groups.flatMap((g) => g.choices),
|
||||||
|
[groups],
|
||||||
|
);
|
||||||
|
const choiceIdxMap = useMemo(() => {
|
||||||
|
const m = new Map<string, number>();
|
||||||
|
flatChoices.forEach((c, i) => m.set(c.id, i));
|
||||||
|
return m;
|
||||||
|
}, [flatChoices]);
|
||||||
|
|
||||||
|
const { activeIndex, setActiveIndex, handleNavKey, setOptionRef } =
|
||||||
|
useListKeyboardNav(flatChoices.length, [search, isEditing]);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(choice: Choice) => {
|
(choice: Choice) => {
|
||||||
onCommit(choice.id === selectedId ? null : choice.id);
|
onCommit(choice.id === selectedId ? null : choice.id);
|
||||||
@@ -85,9 +100,16 @@ export function CellStatus({
|
|||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onCancel();
|
onCancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (handleNavKey(e)) return;
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
if (activeIndex < 0 || activeIndex >= flatChoices.length) return;
|
||||||
|
e.preventDefault();
|
||||||
|
handleSelect(flatChoices[activeIndex]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onCancel],
|
[onCancel, handleNavKey, activeIndex, flatChoices, handleSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
@@ -129,24 +151,33 @@ export function CellStatus({
|
|||||||
<div className={cellClasses.selectCategoryLabel}>
|
<div className={cellClasses.selectCategoryLabel}>
|
||||||
{group.label}
|
{group.label}
|
||||||
</div>
|
</div>
|
||||||
{group.choices.map((choice) => (
|
{group.choices.map((choice) => {
|
||||||
<div
|
const idx = choiceIdxMap.get(choice.id) ?? -1;
|
||||||
key={choice.id}
|
const isSelected = choice.id === selectedId;
|
||||||
className={`${cellClasses.selectOption} ${
|
return (
|
||||||
choice.id === selectedId
|
<div
|
||||||
? cellClasses.selectOptionActive
|
key={choice.id}
|
||||||
: ""
|
ref={setOptionRef(idx)}
|
||||||
}`}
|
className={clsx(
|
||||||
onClick={() => handleSelect(choice)}
|
cellClasses.selectOption,
|
||||||
>
|
isSelected && cellClasses.selectOptionActive,
|
||||||
<span
|
idx === activeIndex && cellClasses.selectOptionKeyboardActive,
|
||||||
className={cellClasses.badge}
|
)}
|
||||||
style={choiceColor(choice.color)}
|
onMouseEnter={() => setActiveIndex(idx)}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
onClick={() => handleSelect(choice)}
|
||||||
>
|
>
|
||||||
{choice.name}
|
<span
|
||||||
</span>
|
className={cellClasses.badge}
|
||||||
</div>
|
style={choiceColor(choice.color)}
|
||||||
))}
|
>
|
||||||
|
{choice.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user