Files
stroy-bot-monorepo/src/webparts/stroyBot/pages/hub/ProjectPicker.tsx
T
Тимур Городецкий c69a11d167 feat: add clear button to the project search field
Clears only the search text and reopens the list, the selected project
stays. Requested during manual testing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 12:33:38 +05:00

103 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as React from 'react';
import { Icon } from '@tnsu/ui-kit';
import { useProject } from '../../app/project/ProjectContext';
import { ProjectStatusBadge } from '../../app/project/ProjectStatusBadge';
import { IAccessibleProject } from '../../app/project/projectTypes';
import { useOutsideClose } from '../../shared/hooks/useOutsideClose';
import { ProjectList } from './ProjectList';
import { HUB_TEXTS } from './texts';
import styles from './ProjectPicker.module.scss';
const TEXTS = HUB_TEXTS.picker;
/** Поле выбора проекта с поиском — как в stroy-bot-main. */
export const ProjectPicker: React.FC = () => {
const { projects, loadState, currentProject, selectProject } = useProject();
const selectedTitle = currentProject?.title ?? '';
const [query, setQuery] = React.useState<string>(selectedTitle);
const [isOpen, setIsOpen] = React.useState<boolean>(false);
const wrapperRef = React.useRef<HTMLDivElement>(null);
const inputRef = React.useRef<HTMLInputElement>(null);
// Выбор мог поменяться извне (адрес, восстановление из сессии) — пока список закрыт, поле показывает его.
React.useEffect(() => {
if (!isOpen) setQuery(selectedTitle);
}, [selectedTitle, isOpen]);
useOutsideClose(wrapperRef, isOpen, (reason) => {
setIsOpen(false);
setQuery(selectedTitle);
if (reason === 'escape') inputRef.current?.blur();
});
const onFocus = (event: React.FocusEvent<HTMLInputElement>): void => {
event.target.select();
setIsOpen(true);
};
const onChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
setQuery(event.target.value);
setIsOpen(true);
};
// Очистка не сбрасывает выбранный проект — только текст поиска, чтобы сразу искать другой.
const onClear = (event: React.MouseEvent): void => {
event.preventDefault();
setQuery('');
setIsOpen(true);
inputRef.current?.focus();
};
// mousedown, а не click: иначе поле теряет фокус раньше, чем срабатывает выбор.
const onPick = (event: React.MouseEvent, project: IAccessibleProject): void => {
event.preventDefault();
selectProject(project);
setIsOpen(false);
};
return (
<div className={styles.picker}>
<label className={styles.label} htmlFor="stroybot-project-search">
{TEXTS.label}
</label>
<div className={styles.field} ref={wrapperRef}>
<span className={styles.searchIcon} aria-hidden="true">
<Icon name="search" size="sm" />
</span>
<input
id="stroybot-project-search"
ref={inputRef}
type="text"
className={styles.input}
placeholder={TEXTS.placeholder}
value={query}
autoComplete="off"
onFocus={onFocus}
onChange={onChange}
/>
{query && (
<button
type="button"
className={styles.clearButton}
title={TEXTS.clear}
aria-label={TEXTS.clear}
onMouseDown={onClear}
>
<Icon name="close" size="sm" />
</button>
)}
{currentProject?.status && (
<span className={styles.selectedBadge}>
<ProjectStatusBadge status={currentProject.status} />
</span>
)}
{isOpen && (
<div className={styles.dropdown}>
<ProjectList loadState={loadState} projects={projects} query={query} onPick={onPick} />
</div>
)}
</div>
</div>
);
};