diff --git a/components/ChangeStatusModal/ChangeStatusModal.tsx b/components/ChangeStatusModal/ChangeStatusModal.tsx index cbeb8bf..f34587f 100644 --- a/components/ChangeStatusModal/ChangeStatusModal.tsx +++ b/components/ChangeStatusModal/ChangeStatusModal.tsx @@ -1,9 +1,9 @@ import React from "react"; import { GroupedStatuses, NoteStatus } from "@/types/noteStatus"; import { - StatusSelectorGroupedByTag, - Props as SSGByTagProps, -} from "./StatusSelectorGroupedByTag"; + StatusSelectorGroup, + Props as StatusSelectorGroupProps, +} from "./StatusSelectorGroup"; export interface Props { currentStatuses: GroupedStatuses; @@ -28,7 +28,7 @@ export const ChangeStatusModal: React.FC = ({ }) => { const currentStatuses = Object.entries(initialStatuses); - const handleSelectedState: SSGByTagProps["onSelectedState"] = ( + const handleSelectedState: StatusSelectorGroupProps["onSelectedState"] = ( frontmatterTagName, status, action, @@ -45,7 +45,7 @@ export const ChangeStatusModal: React.FC = ({

Change note status {filesQuantity}

{currentStatuses.map(([frontmatterTagName, statusList]) => ( - void; +} + +export const CurrentStatusChips: React.FC = ({ + currentStatuses, + onRemoveStatus, +}) => { + return ( + +
+ {currentStatuses.map((status) => ( + onRemoveStatus(status)} + /> + ))} +
+
+ ); +}; diff --git a/components/ChangeStatusModal/StatusSelectorGroup.tsx b/components/ChangeStatusModal/StatusSelectorGroup.tsx new file mode 100644 index 0000000..791d2af --- /dev/null +++ b/components/ChangeStatusModal/StatusSelectorGroup.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { NoteStatus } from "@/types/noteStatus"; +import { SearchFilter } from "../atoms/SearchFilter"; +import { StatusSelector } from "../atoms/StatusSelector"; +import { SettingItem } from "../SettingsUI.tsx/SettingItem"; +import { CurrentStatusChips } from "./CurrentStatusChips"; +import { useKeyboardNavigation } from "./useKeyboardNavigation"; + +export interface Props { + frontmatterTagName: string; + currentStatuses: NoteStatus[]; + availableStatuses: NoteStatus[]; + onSelectedState: ( + frontmatterTagName: string, + status: NoteStatus, + action: "select" | "unselected", + ) => void; +} + +export const StatusSelectorGroup: React.FC = ({ + currentStatuses, + availableStatuses, + frontmatterTagName, + onSelectedState, +}) => { + const handleRemoveStatus = (status: NoteStatus) => { + onSelectedState(frontmatterTagName, status, "unselected"); + }; + + const handleSelectStatus = (status: NoteStatus) => { + onSelectedState(frontmatterTagName, status, "select"); + }; + + const { + focusedIndex, + searchFilter, + filteredStatuses, + containerRef, + searchRef, + handleKeyDown, + setSearchFilter, + } = useKeyboardNavigation({ + availableStatuses, + currentStatuses, + onSelectStatus: handleSelectStatus, + onRemoveStatus: handleRemoveStatus, + }); + + return ( +
+ + + + {filteredStatuses.length === 0 ? ( +
+ {searchFilter + ? `No statuses match "${searchFilter}"` + : "No statuses found"} +
+ ) : ( + + selected + ? handleSelectStatus(status) + : handleRemoveStatus(status) + } + /> + )} +
+ + +
+ ); +}; diff --git a/components/ChangeStatusModal/StatusSelectorGroupedByTag.tsx b/components/ChangeStatusModal/useKeyboardNavigation.tsx similarity index 52% rename from components/ChangeStatusModal/StatusSelectorGroupedByTag.tsx rename to components/ChangeStatusModal/useKeyboardNavigation.tsx index 3cab589..8ca7cec 100644 --- a/components/ChangeStatusModal/StatusSelectorGroupedByTag.tsx +++ b/components/ChangeStatusModal/useKeyboardNavigation.tsx @@ -1,29 +1,21 @@ -import React, { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef } from "react"; import { NoteStatus } from "@/types/noteStatus"; -import { SearchFilter } from "../atoms/SearchFilter"; -import { StatusChip } from "../atoms/StatusChip"; -import { StatusSelector } from "../atoms/StatusSelector"; -import { SettingItem } from "../SettingsUI.tsx/SettingItem"; -export interface Props { - frontmatterTagName: string; - currentStatuses: NoteStatus[]; +interface UseKeyboardNavigationProps { availableStatuses: NoteStatus[]; - onSelectedState: ( - frontmatterTagName: string, - status: NoteStatus, - action: "select" | "unselected", - ) => void; + currentStatuses: NoteStatus[]; + onSelectStatus: (status: NoteStatus) => void; + onRemoveStatus: (status: NoteStatus) => void; } -export const StatusSelectorGroupedByTag: React.FC = ({ - currentStatuses, +export const useKeyboardNavigation = ({ availableStatuses, - frontmatterTagName, - onSelectedState, -}) => { - const [searchFilter, setSearchFilter] = useState(""); + currentStatuses, + onSelectStatus, + onRemoveStatus, +}: UseKeyboardNavigationProps) => { const [focusedIndex, setFocusedIndex] = useState(-1); + const [searchFilter, setSearchFilter] = useState(""); const containerRef = useRef(null); const searchRef = useRef(null); @@ -33,14 +25,6 @@ export const StatusSelectorGroupedByTag: React.FC = ({ ) : availableStatuses; - const handleRemoveStatus = async (status: NoteStatus) => { - onSelectedState(frontmatterTagName, status, "unselected"); - }; - - const handleSelectStatus = async (status: NoteStatus) => { - onSelectedState(frontmatterTagName, status, "select"); - }; - const handleKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case "ArrowDown": @@ -87,9 +71,9 @@ export const StatusSelectorGroupedByTag: React.FC = ({ (s) => s.name === status.name, ); if (isSelected) { - handleRemoveStatus(status); + onRemoveStatus(status); } else { - handleSelectStatus(status); + onSelectStatus(status); } } break; @@ -131,65 +115,13 @@ export const StatusSelectorGroupedByTag: React.FC = ({ } }, []); - return ( -
- setSearchFilter(value)} - /> - - {filteredStatuses.length === 0 ? ( -
- {searchFilter - ? `No statuses match "${searchFilter}"` - : "No statuses found"} -
- ) : ( - - selected - ? handleSelectStatus(status) - : handleRemoveStatus(status) - } - /> - )} -
- -
- {currentStatuses.map((s) => ( - handleRemoveStatus(s)} - /> - ))} -
-
-
- ); + return { + focusedIndex, + searchFilter, + filteredStatuses, + containerRef, + searchRef, + handleKeyDown, + setSearchFilter, + }; }; diff --git a/components/atoms/SearchFilter.tsx b/components/atoms/SearchFilter.tsx index f3024be..3d6aace 100644 --- a/components/atoms/SearchFilter.tsx +++ b/components/atoms/SearchFilter.tsx @@ -1,46 +1,32 @@ -import { FC, useEffect, useRef, useState } from "react"; +import React from "react"; -export type Props = { +interface Props { value: string; onFilterChange: (value: string) => void; -}; -export const SearchFilter: FC = ({ value, onFilterChange }) => { - const [searchFilter, setSearchFilter] = useState(value); - const searchInputRef = useRef(null); + placeholder?: string; +} - useEffect(() => { - // TODO:: Focus on render? - // if (searchInputRef.current) { - // setTimeout(() => searchInputRef.current?.focus(), 100); - // } - // - setSearchFilter(value); - }, [value]); +export const SearchFilter = React.forwardRef( + ({ value, onFilterChange, placeholder = "Search..." }, ref) => { + return ( + onFilterChange(e.target.value)} + placeholder={placeholder} + style={{ + width: "100%", + padding: "8px 12px", + border: "1px solid var(--background-modifier-border)", + borderRadius: "4px", + backgroundColor: "var(--background-primary)", + color: "var(--text-normal)", + fontSize: "14px", + }} + /> + ); + }, +); - // TODO: Move the style to its css file - return ( -
-
-
Filter statuses
-
-
- onFilterChange(e.target.value)} - style={{ - width: "200px", - padding: "6px 12px", - border: "1px solid var(--background-modifier-border)", - borderRadius: "var(--radius-s)", - background: "var(--background-primary)", - color: "var(--text-normal)", - }} - /> -
-
- ); -}; +SearchFilter.displayName = "SearchFilter";