mirror of
https://github.com/devonthesofa/obsidian-note-status.git
synced 2026-07-22 12:30:24 +00:00
fix: search filter forward ref; rename grouped by tag component
This commit is contained in:
parent
bf6a1e967d
commit
200e8b4abd
5 changed files with 187 additions and 136 deletions
|
|
@ -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<Props> = ({
|
|||
}) => {
|
||||
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<Props> = ({
|
|||
<h1>Change note status {filesQuantity}</h1>
|
||||
|
||||
{currentStatuses.map(([frontmatterTagName, statusList]) => (
|
||||
<StatusSelectorGroupedByTag
|
||||
<StatusSelectorGroup
|
||||
key={frontmatterTagName}
|
||||
frontmatterTagName={frontmatterTagName}
|
||||
availableStatuses={availableStatuses}
|
||||
|
|
|
|||
37
components/ChangeStatusModal/CurrentStatusChips.tsx
Normal file
37
components/ChangeStatusModal/CurrentStatusChips.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import { NoteStatus } from "@/types/noteStatus";
|
||||
import { StatusChip } from "../atoms/StatusChip";
|
||||
import { SettingItem } from "../SettingsUI.tsx/SettingItem";
|
||||
|
||||
interface Props {
|
||||
currentStatuses: NoteStatus[];
|
||||
onRemoveStatus: (status: NoteStatus) => void;
|
||||
}
|
||||
|
||||
export const CurrentStatusChips: React.FC<Props> = ({
|
||||
currentStatuses,
|
||||
onRemoveStatus,
|
||||
}) => {
|
||||
return (
|
||||
<SettingItem name="Available statuses" vertical>
|
||||
<div
|
||||
className="note-status-chips"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "6px",
|
||||
minHeight: "32px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{currentStatuses.map((status) => (
|
||||
<StatusChip
|
||||
key={status.name}
|
||||
status={status}
|
||||
onRemove={() => onRemoveStatus(status)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SettingItem>
|
||||
);
|
||||
};
|
||||
96
components/ChangeStatusModal/StatusSelectorGroup.tsx
Normal file
96
components/ChangeStatusModal/StatusSelectorGroup.tsx
Normal file
|
|
@ -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<Props> = ({
|
||||
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 (
|
||||
<div
|
||||
ref={containerRef}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
style={{ outline: "none" }}
|
||||
>
|
||||
<SearchFilter
|
||||
ref={searchRef}
|
||||
value={searchFilter}
|
||||
onFilterChange={setSearchFilter}
|
||||
/>
|
||||
|
||||
<SettingItem name="Current statuses" vertical>
|
||||
{filteredStatuses.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
padding: "16px",
|
||||
textAlign: "center",
|
||||
color: "var(--text-muted)",
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
{searchFilter
|
||||
? `No statuses match "${searchFilter}"`
|
||||
: "No statuses found"}
|
||||
</div>
|
||||
) : (
|
||||
<StatusSelector
|
||||
availableStatuses={filteredStatuses}
|
||||
currentStatuses={currentStatuses}
|
||||
focusedIndex={focusedIndex}
|
||||
onToggleStatus={(status, selected) =>
|
||||
selected
|
||||
? handleSelectStatus(status)
|
||||
: handleRemoveStatus(status)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SettingItem>
|
||||
|
||||
<CurrentStatusChips
|
||||
currentStatuses={currentStatuses}
|
||||
onRemoveStatus={handleRemoveStatus}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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<Props> = ({
|
||||
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<HTMLDivElement>(null);
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
|
|
@ -33,14 +25,6 @@ export const StatusSelectorGroupedByTag: React.FC<Props> = ({
|
|||
)
|
||||
: 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<Props> = ({
|
|||
(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<Props> = ({
|
|||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
style={{ outline: "none" }}
|
||||
>
|
||||
<SearchFilter
|
||||
ref={searchRef}
|
||||
value={searchFilter}
|
||||
onFilterChange={(value) => setSearchFilter(value)}
|
||||
/>
|
||||
<SettingItem name="Current statuses" vertical>
|
||||
{filteredStatuses.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
padding: "16px",
|
||||
textAlign: "center",
|
||||
color: "var(--text-muted)",
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
{searchFilter
|
||||
? `No statuses match "${searchFilter}"`
|
||||
: "No statuses found"}
|
||||
</div>
|
||||
) : (
|
||||
<StatusSelector
|
||||
availableStatuses={filteredStatuses}
|
||||
currentStatuses={currentStatuses}
|
||||
focusedIndex={focusedIndex}
|
||||
onToggleStatus={(status, selected) =>
|
||||
selected
|
||||
? handleSelectStatus(status)
|
||||
: handleRemoveStatus(status)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SettingItem>
|
||||
<SettingItem name="Available statuses" vertical>
|
||||
<div
|
||||
className="note-status-chips"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "6px",
|
||||
minHeight: "32px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{currentStatuses.map((s) => (
|
||||
<StatusChip
|
||||
key={s.name}
|
||||
status={s}
|
||||
onRemove={() => handleRemoveStatus(s)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SettingItem>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
focusedIndex,
|
||||
searchFilter,
|
||||
filteredStatuses,
|
||||
containerRef,
|
||||
searchRef,
|
||||
handleKeyDown,
|
||||
setSearchFilter,
|
||||
};
|
||||
};
|
||||
|
|
@ -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<Props> = ({ value, onFilterChange }) => {
|
||||
const [searchFilter, setSearchFilter] = useState(value);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// TODO:: Focus on render?
|
||||
// if (searchInputRef.current) {
|
||||
// setTimeout(() => searchInputRef.current?.focus(), 100);
|
||||
// }
|
||||
//
|
||||
setSearchFilter(value);
|
||||
}, [value]);
|
||||
export const SearchFilter = React.forwardRef<HTMLInputElement, Props>(
|
||||
({ value, onFilterChange, placeholder = "Search..." }, ref) => {
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => 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 (
|
||||
<div className="setting-item">
|
||||
<div className="setting-item-info">
|
||||
<div className="setting-item-name">Filter statuses</div>
|
||||
</div>
|
||||
<div className="setting-item-control">
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
placeholder="Search statuses..."
|
||||
className="note-status-search-input"
|
||||
value={searchFilter}
|
||||
onChange={(e) => 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)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
SearchFilter.displayName = "SearchFilter";
|
||||
|
|
|
|||
Loading…
Reference in a new issue