From 43f39eac1d6aca92469858022295a6fbcc31838d Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Mon, 17 Apr 2023 22:46:50 +0300 Subject: [PATCH 1/7] tact-305: separated input shortcuts and context menu --- components/shared/GoalsSelection/view.tsx | 44 ++++++ .../components/PriorityListItem.tsx | 91 +++++++++++++ components/shared/PrioritySelection/index.tsx | 13 ++ components/shared/PrioritySelection/store.ts | 61 +++++++++ components/shared/PrioritySelection/view.tsx | 26 ++++ components/shared/TactTaskTag/index.tsx | 79 +++++++++++ components/shared/TagsInput/index.tsx | 65 +++++++++ .../TaskQuickEditorMainMenu.tsx | 53 ++++++-- .../TaskQuickEditor/TaskQuickEditorTags.tsx | 91 +++---------- .../shared/TaskQuickEditor/modals/store.ts | 107 ++++++++++++++- .../TaskQuickEditor/modes/TagModeStore.tsx | 4 + components/shared/TaskQuickEditor/store.ts | 5 +- .../components/TaskItemMenu/index.tsx | 6 +- .../modals/TaskAddTagModal/index.tsx | 11 ++ .../TasksList/modals/TaskAddTagModal/store.ts | 92 +++++++++++++ .../TasksList/modals/TaskAddTagModal/view.tsx | 127 ++++++++++++++++++ .../modals/TaskGoalAssignModal/view.tsx | 23 +++- .../modals/TaskPriorityModal/index.tsx | 16 +++ .../modals/TaskPriorityModal/store.ts | 59 ++++++++ .../modals/TaskPriorityModal/view.tsx | 88 ++++++++++++ .../modals/TaskSpaceChangeModal/store.ts | 14 +- .../modals/TaskSpaceChangeModal/view.tsx | 23 +++- components/shared/TasksList/modals/store.ts | 60 ++++++++- 23 files changed, 1048 insertions(+), 110 deletions(-) create mode 100644 components/shared/PrioritySelection/components/PriorityListItem.tsx create mode 100644 components/shared/PrioritySelection/index.tsx create mode 100644 components/shared/PrioritySelection/store.ts create mode 100644 components/shared/PrioritySelection/view.tsx create mode 100644 components/shared/TactTaskTag/index.tsx create mode 100644 components/shared/TagsInput/index.tsx create mode 100644 components/shared/TasksList/modals/TaskAddTagModal/index.tsx create mode 100644 components/shared/TasksList/modals/TaskAddTagModal/store.ts create mode 100644 components/shared/TasksList/modals/TaskAddTagModal/view.tsx create mode 100644 components/shared/TasksList/modals/TaskPriorityModal/index.tsx create mode 100644 components/shared/TasksList/modals/TaskPriorityModal/store.ts create mode 100644 components/shared/TasksList/modals/TaskPriorityModal/view.tsx diff --git a/components/shared/GoalsSelection/view.tsx b/components/shared/GoalsSelection/view.tsx index 278fa173..7cef6362 100644 --- a/components/shared/GoalsSelection/view.tsx +++ b/components/shared/GoalsSelection/view.tsx @@ -13,6 +13,7 @@ import { GoalsSelectionProps, useGoalsSelectionStore } from './store'; import React, { useRef } from 'react'; import { LargePlusIcon } from '../Icons/LargePlusIcon'; import { GoalIcon } from '../GoalIcon'; +import { HeavyPlusIcon } from '../Icons/HeavyPlusIcon'; type GoalSelectionListItemProps = { id: string | null; @@ -112,6 +113,49 @@ export const GoalsSelectionView = observer(function GoalsSelectionView( checkboxContent={index < 9 ? index + 1 : null} /> ))} + {store.root.resources.goals.list?.length < 9 && + + props.setRefs(store.root.resources.goals.list.length + 1, el)} + isChecked={false} + size='xl' + position='relative' + width='100%' + icon={ + + + } + css={{ + '.chakra-checkbox__label': { + width: 'calc(100% - 2rem)', + }, + '.chakra-checkbox__control': { + borderRadius: '100%', + } + }} + > + + Create new goal + + + + } ) : ( diff --git a/components/shared/PrioritySelection/components/PriorityListItem.tsx b/components/shared/PrioritySelection/components/PriorityListItem.tsx new file mode 100644 index 00000000..e0d54026 --- /dev/null +++ b/components/shared/PrioritySelection/components/PriorityListItem.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { + chakra, + Checkbox, + ListItem, + forwardRef, +} from '@chakra-ui/react'; +import { usePrioritySelectionStore } from '../store'; +import { TaskPriority, TaskPriorityNames } from '../../TasksList/types'; +import { TaskPriorityIcon } from '../../Icons/TaskPriorityIcon'; + +type PriorityListItemProps = { + priority: TaskPriority; + checkboxContent?: React.ReactNode; +}; + +export const PriorityListItem = observer( + forwardRef(function PriorityListItem( + { priority, checkboxContent }: PriorityListItemProps, + ref + ) { + const store = usePrioritySelectionStore(); + + return ( + + store.handlePriorityCheck(priority)} + size='xl' + position='relative' + fontWeight='semibold' + fontSize='lg' + width='100%' + icon={checkboxContent ? <> : undefined} + css={{ + '.chakra-checkbox__label': { + width: 'calc(100% - 2rem)', + } + }} + > + {checkboxContent ? ( + + {checkboxContent} + + ) : null} + + + + + + {TaskPriorityNames[priority]} + + + + + + + ); + }) +); diff --git a/components/shared/PrioritySelection/index.tsx b/components/shared/PrioritySelection/index.tsx new file mode 100644 index 00000000..57ecf6da --- /dev/null +++ b/components/shared/PrioritySelection/index.tsx @@ -0,0 +1,13 @@ +import { observer } from 'mobx-react-lite'; +import { PrioritySelectionView } from './view'; +import { PrioritySelectionProps, PrioritySelectionStoreProvider } from './store'; + +export const PrioritySelection = observer(function GoalsSelection( + props: PrioritySelectionProps +) { + return ( + + + + ); +}); diff --git a/components/shared/PrioritySelection/store.ts b/components/shared/PrioritySelection/store.ts new file mode 100644 index 00000000..92ddd3c8 --- /dev/null +++ b/components/shared/PrioritySelection/store.ts @@ -0,0 +1,61 @@ +import { makeAutoObservable } from 'mobx'; +import { RootStore } from '../../../stores/RootStore'; +import { getProvider } from '../../../helpers/StoreProvider'; + +export type PrioritySelectionProps = { + callbacks?: { + onSelect?: (goalIds: string[]) => void; + }; + + setRefs?: (index: number, ref: HTMLElement) => void; + checked?: string[]; +}; + +export class PrioritySelectionStore { + constructor(public root: RootStore) { + makeAutoObservable(this); + } + + callbacks: PrioritySelectionProps['callbacks'] = {}; + + checkedPriority: Record = {}; + isFocused: boolean = false; + multiple: boolean = false; + + get checked() { + return Object.keys(this.checkedPriority); + } + + handlePriorityCheck = (key: string) => { + const priority = key === null ? null : key; + if (priority !== null) { + this.checkedPriority = { + [priority]: true, + }; + } else { + this.checkedPriority = {}; + } + + this.callbacks.onSelect?.(this.checked); + }; + + uncheckAll = () => { + this.checkedPriority = {}; + this.callbacks.onSelect?.(this.checked); + }; + + update = (props: PrioritySelectionProps) => { + this.callbacks = props.callbacks; + + if (props.checked) { + this.checkedPriority = { + [props.checked[0]]: true, + }; + } + }; +} + +export const { + StoreProvider: PrioritySelectionStoreProvider, + useStore: usePrioritySelectionStore, +} = getProvider(PrioritySelectionStore); diff --git a/components/shared/PrioritySelection/view.tsx b/components/shared/PrioritySelection/view.tsx new file mode 100644 index 00000000..2a76e5a4 --- /dev/null +++ b/components/shared/PrioritySelection/view.tsx @@ -0,0 +1,26 @@ +import React, { useRef } from 'react'; +import { observer } from 'mobx-react-lite'; +import { List } from '@chakra-ui/react'; +import { PrioritySelectionProps, usePrioritySelectionStore } from './store'; +import { PriorityListItem } from './components/PriorityListItem' +import { TaskPriorityArray } from '../TasksList/types'; + +export const PrioritySelectionView = observer(function SpaceSelectionView( + props: Partial +) { + const store = usePrioritySelectionStore(); + const ref = useRef(); + + return ( + + {TaskPriorityArray.map((item, index) => ( + props.setRefs(index + 1, el)} + key={item} + priority={item} + checkboxContent={index < 9 ? index + 1 : null} + /> + ))} + + ) +}); diff --git a/components/shared/TactTaskTag/index.tsx b/components/shared/TactTaskTag/index.tsx new file mode 100644 index 00000000..5a14e332 --- /dev/null +++ b/components/shared/TactTaskTag/index.tsx @@ -0,0 +1,79 @@ +import { Button, Tag, IconButton, ButtonProps } from "@chakra-ui/react"; +import { faXmark } from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import React, { FC } from "react"; + +interface TactTaskTagProps { + buttonProps?: ButtonProps; + iconButtonProps?: ButtonProps; + tagProps?: ButtonProps; + showRemoveIcon?: boolean; + title: string; +} + +export const TactTaskTag: FC = ({ + buttonProps, + iconButtonProps, + tagProps, + title, + showRemoveIcon = false }) => ( + +) diff --git a/components/shared/TagsInput/index.tsx b/components/shared/TagsInput/index.tsx new file mode 100644 index 00000000..8bcb2fe7 --- /dev/null +++ b/components/shared/TagsInput/index.tsx @@ -0,0 +1,65 @@ +import { HStack, Input, Tag } from '@chakra-ui/react'; +import React, { FC } from 'react'; +import { TactTaskTag } from '../TactTaskTag'; +import { TaskTag } from '../TasksList/types'; + +export interface TagsInputProps { + tags?: TaskTag[]; + removeTag: (tagId: string) => void; + addTag: (tag: string) => void; +} + +export const TagsInput: FC = ({ tags, addTag, removeTag }) => { + const handleKeyDown = (e) => { + if (e.code !== 'Enter' && e.code !== 'Space') return + const value = e.target.value + if (!value.trim()) { + e.target.value = '' + return + } + addTag(value) + e.target.value = '' + } + + return ( + button, input': { + 'margin-inline-start': '0px!important', + } + }} + > + {tags?.map(({ title, id }) => ( + { + e.stopPropagation(); + removeTag(id); + } + }} + /> + ))} + + + + ) +} diff --git a/components/shared/TaskQuickEditor/TaskQuickEditorMainMenu.tsx b/components/shared/TaskQuickEditor/TaskQuickEditorMainMenu.tsx index d843c301..3d786104 100644 --- a/components/shared/TaskQuickEditor/TaskQuickEditorMainMenu.tsx +++ b/components/shared/TaskQuickEditor/TaskQuickEditorMainMenu.tsx @@ -9,6 +9,13 @@ import { Portal, MenuList, } from '@chakra-ui/react'; +import { + faBullseyePointer, + faCircleExclamation, + faHashtag, + faSolarSystem, +} from '@fortawesome/pro-light-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { DotsIcon } from '../Icons/DotsIcon'; import React from 'react'; @@ -38,43 +45,67 @@ export const TaskQuickEditorMainMenu = observer(function TaskQuickEditMenu() { store.activateMode(Modes.TAG)} + p={2.5} + icon={ + + } + onClick={() => { + store.input.focus(); + store.modals.openAddTagModal(); + }} > - Add tag + Add hashtag store.activateMode(Modes.PRIORITY)} + p={2.5} + icon={ + + } + onClick={() => { + store.input.focus(); + store.modals.openPriorityModal(); + }} > Set priority {!store.disableGoalChange && ( store.activateMode(Modes.GOAL)} + p={2.5} + icon={ + + } + onClick={() => { + store.input.focus(); + store.modals.openGoalAssignModal(); + }} > - Add goal + Set goal )} {!store.disableSpaceChange && ( store.activateMode(Modes.SPACE)} + p={2.5} + icon={ + + } + onClick={(e) => { + store.input.focus(); + store.modals.openSpaceChangeModal(); + }} > - Link to space + Change space )} diff --git a/components/shared/TaskQuickEditor/TaskQuickEditorTags.tsx b/components/shared/TaskQuickEditor/TaskQuickEditorTags.tsx index f2f885a6..af10ac25 100644 --- a/components/shared/TaskQuickEditor/TaskQuickEditorTags.tsx +++ b/components/shared/TaskQuickEditor/TaskQuickEditorTags.tsx @@ -10,16 +10,13 @@ import { PopoverBody, PopoverContent, PopoverTrigger, - Tag, chakra, VStack, Portal, - IconButton, } from '@chakra-ui/react'; import React, { useEffect } from 'react'; -import { faXmark } from '@fortawesome/pro-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { TaskTag } from '../TasksList/types'; +import { TactTaskTag } from '../TactTaskTag'; export const TAGS_ID = 'task-quick-editor-tags'; @@ -33,72 +30,26 @@ const TaskQuickEditorTagsList = observer(function TaskQuickEditorTags({ const store = useTaskQuickEditorStore(); const renderContent = ({ title, id }: TaskTag) => ( - + { + e.stopPropagation(); + store.modes.tag.focusTagById(id); + }, + ref: (el) => store.modes.tag.setTagRef(el, id), + onKeyDown: (e) => store.modes.tag.handleButtonKeyDown(e, id), + onFocus: store.handleModeFocus(Modes.TAG), + ...buttonProps, + }} + iconButtonProps={{ + onClick:(e) => { + e.stopPropagation(); + store.modes.tag.removeTag(id); + } + }} + title={title} + showRemoveIcon + /> ); return ( diff --git a/components/shared/TaskQuickEditor/modals/store.ts b/components/shared/TaskQuickEditor/modals/store.ts index 67bf6da5..b5b34c44 100644 --- a/components/shared/TaskQuickEditor/modals/store.ts +++ b/components/shared/TaskQuickEditor/modals/store.ts @@ -4,18 +4,34 @@ import { SpaceCreationModal } from '../../../pages/Spaces/modals/SpaceCreationMo import { SpaceData } from '../../../pages/Spaces/types'; import { RootStore } from '../../../../stores/RootStore'; import { CreateGoalParams } from "../../../../stores/RootStore/Resources/GoalsStore"; +import { TaskGoalAssignModal } from '../../TasksList/modals/TaskGoalAssignModal'; +import { TaskSpaceChangeModal } from '../../TasksList/modals/TaskSpaceChangeModal'; +import { TaskAddTagModal } from '../../TasksList/modals/TaskAddTagModal'; +import { TaskQuickEditorStore } from '../store'; +import { TaskPriorityModal } from '../../TasksList/modals/TaskPriorityModal'; +import { TaskPriority, TaskTag } from '../../TasksList/types'; + export enum ModalsTypes { ADD_GOAL, ADD_SPACE, + GOAL_ASSIGN, + SPACE_CHANGE, + SPACE_CREATION, + ADD_TAG, + SET_PRIORITY, } export class TasksEditorModals { - constructor(public root: RootStore) { } + constructor(public root: RootStore, public parent: TaskQuickEditorStore) { } controller = new ModalsController({ [ModalsTypes.ADD_GOAL]: GoalCreationModal, [ModalsTypes.ADD_SPACE]: SpaceCreationModal, + [ModalsTypes.GOAL_ASSIGN]: TaskGoalAssignModal, + [ModalsTypes.SPACE_CHANGE]: TaskSpaceChangeModal, + [ModalsTypes.ADD_TAG]: TaskAddTagModal, + [ModalsTypes.SET_PRIORITY]: TaskPriorityModal, }); openGoalCreationModal = (cb?: () => void) => { @@ -59,4 +75,93 @@ export class TasksEditorModals { }, }); }; + + openGoalAssignModal = () => { + this.root.toggleModal(true); + this.controller.open({ + type: ModalsTypes.GOAL_ASSIGN, + props: { + callbacks: { + onClose: () => { + this.controller.close(); + this.root.toggleModal(false); + }, + onGoalCreateClick: () => { + this.openGoalCreationModal(this.openGoalAssignModal); + }, + onSelect: (goalId: string) => { + this.parent.modes.goal.selectedGoalId = goalId; + this.controller.close(); + this.root.toggleModal(false); + }, + }, + value: this.parent.modes.goal.selectedGoalId, + }, + }); + }; + + openSpaceChangeModal = () => { + this.root.toggleModal(true); + this.controller.open({ + type: ModalsTypes.SPACE_CHANGE, + props: { + callbacks: { + onClose: () => { + this.controller.close(); + this.root.toggleModal(false); + }, + onSpaceCreateClick: () => { + this.openSpaceCreationModal(this.openSpaceChangeModal); + }, + onSelect: (spaceId: string) => { + this.parent.modes.space.selectedSpaceId = spaceId; + this.controller.close(); + this.root.toggleModal(false); + }, + }, + spaceId: this.parent.modes.space.selectedSpaceId, + }, + }); + }; + + openAddTagModal = () => { + this.root.toggleModal(true); + this.controller.open({ + type: ModalsTypes.ADD_TAG, + props: { + callbacks: { + onSave: (tags: TaskTag[]) => { + this.parent.modes.tag.updateTags(tags); + this.controller.close(); + }, + onClose: () => { + this.controller.close(); + this.root.toggleModal(false); + }, + }, + tags: this.parent.modes.tag.tags.map(({ id }) => id), + }, + }); + }; + + openPriorityModal = () => { + this.root.toggleModal(true); + this.controller.open({ + type: ModalsTypes.SET_PRIORITY, + props: { + callbacks: { + onClose: () => { + this.controller.close(); + this.root.toggleModal(false); + }, + onSelect: (priority: TaskPriority) => { + this.parent.modes.priority.currentPriority = priority; + this.controller.close(); + this.root.toggleModal(false); + }, + }, + priority: this.parent.modes.priority.currentPriority, + }, + }); + }; } diff --git a/components/shared/TaskQuickEditor/modes/TagModeStore.tsx b/components/shared/TaskQuickEditor/modes/TagModeStore.tsx index f97ef709..38ec1330 100644 --- a/components/shared/TaskQuickEditor/modes/TagModeStore.tsx +++ b/components/shared/TaskQuickEditor/modes/TagModeStore.tsx @@ -215,6 +215,10 @@ export class TagModeStore { } }; + updateTags = (tags: TaskTag[]) => { + this.tags = tags + } + addTag = (tag: TaskTag) => { this.tags.push(tag); }; diff --git a/components/shared/TaskQuickEditor/store.ts b/components/shared/TaskQuickEditor/store.ts index 853b8cbf..8276ca93 100644 --- a/components/shared/TaskQuickEditor/store.ts +++ b/components/shared/TaskQuickEditor/store.ts @@ -76,7 +76,7 @@ export class TaskQuickEditorStore { onOpen: (isOpen: boolean) => this.callbacks.onSuggestionsMenuOpen?.(isOpen), }); - modals = new TasksEditorModals(this.root); + modals = new TasksEditorModals(this.root, this); order = [Modes.TAG, Modes.SPACE, Modes.PRIORITY, Modes.GOAL]; callbacks: TaskQuickEditorProps['callbacks']; @@ -701,7 +701,8 @@ export class TaskQuickEditorStore { this.disableGoalChange = disableGoalChange; this.disableReferenceChange = disableReferenceChange; - const defaultSpaceId = task?.spaceId || input?.spaceId; + const defaultSpaceId = task?.spaceId || input?.spaceId || this.modes.space.selectedSpaceId; + this.defaultSpaceId = externalDefaultSpaceId; this.defaultGoalId = defaultGoalId; diff --git a/components/shared/TasksList/components/TaskItemMenu/index.tsx b/components/shared/TasksList/components/TaskItemMenu/index.tsx index 9425450d..42e584c3 100644 --- a/components/shared/TasksList/components/TaskItemMenu/index.tsx +++ b/components/shared/TasksList/components/TaskItemMenu/index.tsx @@ -78,15 +78,15 @@ const multiTaskItems = (store: TaskItemStore) => [ const singleTaskItems = (store: TaskItemStore) => [ { onClick: () => { - store.parent.setEditingTask(store.task.id); - setTimeout(() => store.quickEdit.activateMode(Modes.PRIORITY)); + store.parent.modals.openPriorityModal(store.task.id); + // setTimeout(() => store.quickEdit.activateMode(Modes.PRIORITY)); }, title: 'Change priority', icon: faCircleExclamation, }, { onClick: () => { - store.parent.setEditingTask(store.task.id); + store.parent.modals.openAddTagModal(store.task.id); setTimeout(() => store.quickEdit.activateMode(Modes.TAG)); }, title: 'Add tag', diff --git a/components/shared/TasksList/modals/TaskAddTagModal/index.tsx b/components/shared/TasksList/modals/TaskAddTagModal/index.tsx new file mode 100644 index 00000000..3cef750c --- /dev/null +++ b/components/shared/TasksList/modals/TaskAddTagModal/index.tsx @@ -0,0 +1,11 @@ +import { observer } from 'mobx-react-lite'; +import { TaskAddTagModalView } from './view'; +import { TaskAddTagModalProps, TaskAddTagModalStoreProvider } from './store'; + +export const TaskAddTagModal = observer(function TaskAddTagModal(props: TaskAddTagModalProps) { + return ( + + + + ); +}); diff --git a/components/shared/TasksList/modals/TaskAddTagModal/store.ts b/components/shared/TasksList/modals/TaskAddTagModal/store.ts new file mode 100644 index 00000000..a7e7b3cd --- /dev/null +++ b/components/shared/TasksList/modals/TaskAddTagModal/store.ts @@ -0,0 +1,92 @@ +import { makeAutoObservable, toJS } from 'mobx'; +import { getProvider } from '../../../../../helpers/StoreProvider'; +import { ListNavigation } from '../../../../../helpers/ListNavigation'; +import { RootStore } from '../../../../../stores/RootStore'; +import { TaskTag } from '../../types'; +import { v4 as uuidv4 } from 'uuid'; + +export type TaskAddTagModalProps = { + callbacks: { + onClose: () => void; + onSave: (tags: TaskTag[]) => void; + }, + tags: string[]; +}; + +export class TaskAddTagModalStore { + constructor(public root: RootStore) { + makeAutoObservable(this); + } + selectedTags: TaskTag[]; + + keyMap = { + FORCE_ENTER: ['meta+enter'], + }; + + hotkeyHandlers = { + FORCE_ENTER: (e) => { + e.preventDefault(); + e.stopPropagation(); + this.navigation.hotkeyHandlers.FORCE_ENTER?.(e); + }, + }; + + get availableTags() { + return this.root.resources.tags.list; + } + + callbacks: TaskAddTagModalProps['callbacks']; + + removeTag = (id: string) => { + this.selectedTags = this.selectedTags.filter((tag) => tag.id !== id); + }; + + addTag = (tag: TaskTag) => { + this.selectedTags.push(tag); + }; + + createNewTag = (newTagTitle: string) => { + + const hasTag = this.availableTags.some( + ({ title: tagTitle }) => tagTitle === newTagTitle + ); + + if (!hasTag) { + const id = uuidv4(); + const newTag = { title: newTagTitle, id }; + + this.addTag(newTag); + this.root.resources.tags.add(newTag); + } + }; + + addAvailableTag = (id: string) => { + if (!this.availableTags.some(({ id: tagId }) => tagId === id)) { + const tag = this.availableTags.find((tag) => tag.id === id); + + this.addTag(tag); + } + }; + + handleSave = () => { + this.callbacks.onSave(this.selectedTags); + }; + + update = ({ callbacks, tags }: TaskAddTagModalProps) => { + this.selectedTags = this.availableTags.filter(({ id }) => tags.includes(id)); + this.callbacks = callbacks; + }; + + navigationCallbacks = { + onForceEnter: () => { + this.handleSave(); + }, + }; + + navigation = new ListNavigation(this.navigationCallbacks); +} + +export const { + StoreProvider: TaskAddTagModalStoreProvider, + useStore: useTaskAddTagModalStore, +} = getProvider(TaskAddTagModalStore); diff --git a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx new file mode 100644 index 00000000..c0627dc0 --- /dev/null +++ b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx @@ -0,0 +1,127 @@ +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import { + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/modal'; +import { + Box, + Button, + Text, +} from '@chakra-ui/react'; +import { + useTaskAddTagModalStore, +} from './store'; +import { useListNavigation } from '../../../../../helpers/ListNavigation'; +import { useHotkeysHandler } from '../../../../../helpers/useHotkeysHandler'; +import { TagsInput } from '../../../TagsInput'; +import { TactTaskTag } from '../../../TactTaskTag'; + + +export const TaskAddTagModalView = observer(function TaskAddTagModalView() { + const store = useTaskAddTagModalStore(); + + useListNavigation(store.navigation); + useHotkeysHandler(store.keyMap, store.hotkeyHandlers) + + return ( + + + + Add hashtag + + + Hashtags of the current task + + + {store.availableTags.length && ( + + All your hashtags + + + {store.availableTags.map(({ title, id }) => { + const alreadySelected = !!store.selectedTags.find(({ id: selectedId }) => selectedId === id) + return( + alreadySelected ? store.removeTag(id) : store.addTag({ title, id }) + }} + tagProps={{ + bg: alreadySelected ? 'blue.600' : 'blue.400' + }} + /> + )})} + + )} + + + + + + + + + ); +}); diff --git a/components/shared/TasksList/modals/TaskGoalAssignModal/view.tsx b/components/shared/TasksList/modals/TaskGoalAssignModal/view.tsx index 09b296d2..cc209e37 100644 --- a/components/shared/TasksList/modals/TaskGoalAssignModal/view.tsx +++ b/components/shared/TasksList/modals/TaskGoalAssignModal/view.tsx @@ -43,26 +43,41 @@ export const TaskGoalAssignModalView = observer( }} /> - + diff --git a/components/shared/TasksList/modals/TaskPriorityModal/index.tsx b/components/shared/TasksList/modals/TaskPriorityModal/index.tsx new file mode 100644 index 00000000..3162e0de --- /dev/null +++ b/components/shared/TasksList/modals/TaskPriorityModal/index.tsx @@ -0,0 +1,16 @@ +import { observer } from 'mobx-react-lite'; +import { TaskPriorityModalView } from './view'; +import { + TaskPriorityModalProps, + TaskPriorityModalStoreProvider, +} from './store'; + +export const TaskPriorityModal = observer(function TaskPriorityModal( + props: TaskPriorityModalProps +) { + return ( + + + + ); +}); diff --git a/components/shared/TasksList/modals/TaskPriorityModal/store.ts b/components/shared/TasksList/modals/TaskPriorityModal/store.ts new file mode 100644 index 00000000..fe70057a --- /dev/null +++ b/components/shared/TasksList/modals/TaskPriorityModal/store.ts @@ -0,0 +1,59 @@ +import { makeAutoObservable } from 'mobx'; +import { RootStore } from '../../../../../stores/RootStore'; +import { getProvider } from '../../../../../helpers/StoreProvider'; +import { ListNavigation } from '../../../../../helpers/ListNavigation'; +import { TaskPriority } from '../../types'; + +export type TaskPriorityModalProps = { + callbacks: { + onClose?: () => void; + onSelect?: (priority: TaskPriority) => void; + }; + priority: TaskPriority; +}; + +export class TaskPriorityModalStore { + constructor(public root: RootStore) { + makeAutoObservable(this); + } + + callbacks: TaskPriorityModalProps['callbacks'] = {}; + + selectedPriority: TaskPriority; + emptyRef: HTMLInputElement | null = null; + multiple: boolean = false; + + keyMap = { + FORCE_ENTER: ['meta+enter'], + }; + + hotkeyHandlers = { + FORCE_ENTER: (e) => { + e.preventDefault(); + e.stopPropagation(); + this.navigation.hotkeyHandlers.FORCE_ENTER?.(e); + }, + }; + + handleSelect = (priority: TaskPriority[]) => { + this.selectedPriority = priority[0]; + }; + + handleSubmit = () => { + this.callbacks.onSelect?.(this.selectedPriority); + }; + + update = ({ priority, callbacks }: TaskPriorityModalProps) => { + this.callbacks = callbacks; + this.selectedPriority = priority; + }; + + navigation = new ListNavigation({ + onForceEnter: this.handleSubmit, + }); +} + +export const { + StoreProvider: TaskPriorityModalStoreProvider, + useStore: useTaskPriorityModalStore, +} = getProvider(TaskPriorityModalStore); diff --git a/components/shared/TasksList/modals/TaskPriorityModal/view.tsx b/components/shared/TasksList/modals/TaskPriorityModal/view.tsx new file mode 100644 index 00000000..1e5f93db --- /dev/null +++ b/components/shared/TasksList/modals/TaskPriorityModal/view.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/modal'; +import { Button, Text } from '@chakra-ui/react'; +import { useTaskPriorityModalStore } from './store'; +import { useListNavigation } from '../../../../../helpers/ListNavigation'; +import { useHotkeysHandler } from '../../../../../helpers/useHotkeysHandler'; +import { PrioritySelection } from '../../../PrioritySelection'; + +export const TaskPriorityModalView = observer( + function TaskPriorityModalView() { + const store = useTaskPriorityModalStore(); + + useListNavigation(store.navigation); + useHotkeysHandler(store.keyMap, store.hotkeyHandlers) + + return ( + + + + Set priority + + + + + + + + + + ); + } +); diff --git a/components/shared/TasksList/modals/TaskSpaceChangeModal/store.ts b/components/shared/TasksList/modals/TaskSpaceChangeModal/store.ts index 7289aefe..4f638a25 100644 --- a/components/shared/TasksList/modals/TaskSpaceChangeModal/store.ts +++ b/components/shared/TasksList/modals/TaskSpaceChangeModal/store.ts @@ -1,18 +1,16 @@ -import { makeAutoObservable, toJS } from 'mobx'; +import { makeAutoObservable } from 'mobx'; import { RootStore } from '../../../../../stores/RootStore'; import { getProvider } from '../../../../../helpers/StoreProvider'; import { SpacesSelectionStore } from '../../../SpacesSelection/store'; import { ListNavigation } from '../../../../../helpers/ListNavigation'; -import { TaskData } from '../../types'; export type TaskSpaceChangeModalProps = { callbacks: { onClose?: () => void; - onSelect?: (updatedTask: TaskData) => void; + onSelect?: (spaceId: string) => void; onSpaceCreateClick?: () => void; }; multiple?: boolean; - task: TaskData; spaceId: string; }; @@ -25,7 +23,6 @@ export class TaskSpaceChangeModalStore { spacesSelection = new SpacesSelectionStore(this.root); - selectedTask: TaskData = {} as TaskData; emptyRef: HTMLInputElement | null = null; selectedSpaceId: string | null = null; multiple: boolean = false; @@ -47,11 +44,7 @@ export class TaskSpaceChangeModalStore { }; handleSubmit = () => { - this.callbacks.onSelect?.({ - ...this.selectedTask, - spaceId: this.selectedSpaceId, - tags: toJS(this.selectedTask.tags) - }); + this.callbacks.onSelect?.(this.selectedSpaceId); }; update = (props: TaskSpaceChangeModalProps) => { @@ -59,7 +52,6 @@ export class TaskSpaceChangeModalStore { this.multiple = props.multiple; this.selectedSpaceId = props.spaceId; - this.selectedTask = props.task; }; navigation = new ListNavigation({ diff --git a/components/shared/TasksList/modals/TaskSpaceChangeModal/view.tsx b/components/shared/TasksList/modals/TaskSpaceChangeModal/view.tsx index f405dde5..b3df9c7f 100644 --- a/components/shared/TasksList/modals/TaskSpaceChangeModal/view.tsx +++ b/components/shared/TasksList/modals/TaskSpaceChangeModal/view.tsx @@ -43,26 +43,41 @@ export const TaskSpaceChangeModalView = observer( }} /> - + diff --git a/components/shared/TasksList/modals/store.ts b/components/shared/TasksList/modals/store.ts index 2480520a..c9f592ce 100644 --- a/components/shared/TasksList/modals/store.ts +++ b/components/shared/TasksList/modals/store.ts @@ -5,10 +5,14 @@ import { GoalCreationModal } from '../../../pages/Goals/modals/GoalCreationModal import { TasksListStore } from '../store'; import { TaskWontDoModal } from './TaskWontDoModal'; import { TaskSpaceChangeModal } from './TaskSpaceChangeModal'; -import { TaskData } from '../types'; +import { TaskPriority, TaskTag } from '../types'; import { SpaceCreationModal } from '../../../pages/Spaces/modals/SpaceCreationModal'; import { SpaceData } from '../../../pages/Spaces/types'; import { CreateGoalParams } from "../../../../stores/RootStore/Resources/GoalsStore"; +import { TaskAddTagModal } from './TaskAddTagModal'; +import { TaskPriorityModal } from './TaskPriorityModal'; +import { toJS } from 'mobx'; + export enum ModalsTypes { DELETE_TASK, @@ -17,6 +21,8 @@ export enum ModalsTypes { GOAL_CREATION, SPACE_CHANGE, SPACE_CREATION, + ADD_TAG, + SET_PRIORITY, } export class TasksModals { @@ -29,6 +35,8 @@ export class TasksModals { [ModalsTypes.GOAL_CREATION]: GoalCreationModal, [ModalsTypes.SPACE_CHANGE]: TaskSpaceChangeModal, [ModalsTypes.SPACE_CREATION]: SpaceCreationModal, + [ModalsTypes.ADD_TAG]: TaskAddTagModal, + [ModalsTypes.SET_PRIORITY]: TaskPriorityModal, }); openVerifyDeleteModal = (ids: string[], done?: () => void) => { @@ -59,6 +67,26 @@ export class TasksModals { }); }; + openAddTagModal = (taskId: string) => { + const task = this.parent.items[taskId]; + this.controller.open({ + type: ModalsTypes.ADD_TAG, + props: { + callbacks: { + onSave: (tags: TaskTag[]) => { + this.parent.updateTask({ + ...task, + tags: tags.map(({ id }) => id), + }); + this.controller.close(); + }, + onClose: this.controller.close, + }, + tags: task.tags, + }, + }); + }; + openGoalCreationModal = (cb?: (goalId?: string) => void) => { this.controller.open({ type: ModalsTypes.GOAL_CREATION, @@ -76,6 +104,27 @@ export class TasksModals { }); }; + openPriorityModal = (taskId: string) => { + const task = this.parent.items[taskId]; + this.controller.open({ + type: ModalsTypes.SET_PRIORITY, + props: { + callbacks:{ + onClose: this.controller.close, + onSelect: (priority: TaskPriority) => { + this.parent.updateTask({ + ...task, + tags: toJS(task.tags), + priority, + }); + this.controller.close(); + }, + }, + priority: task.priority, + }, + }); + }; + openGoalAssignModal = (taskId?: string, startGoalId?: string) => { const focused = this.parent.draggableList.focused; const value = @@ -147,12 +196,15 @@ export class TasksModals { this.openSpaceChangeModal(taskId, spaceId); }); }, - onSelect: (updatedTask: TaskData) => { - this.parent.updateTask(updatedTask); + onSelect: (spaceId: string) => { + this.parent.updateTask({ + ...task, + spaceId, + tags: toJS(task.tags) + }); this.controller.close(); }, }, - task, spaceId, }, }); From cdb8e07d598b51d0868924498292678f450af451 Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Mon, 17 Apr 2023 23:29:07 +0300 Subject: [PATCH 2/7] tact-305: build fixes --- components/shared/TactTaskTag/index.tsx | 14 +- components/shared/TagsInput/index.tsx | 4 +- .../TaskQuickEditorMainMenu.tsx | 2 +- .../TaskQuickEditor/TaskQuickEditorTags.tsx | 170 +++++++++--------- .../components/TaskItemMenu/index.tsx | 1 - .../TasksList/modals/TaskAddTagModal/store.ts | 2 +- .../TasksList/modals/TaskAddTagModal/view.tsx | 2 +- 7 files changed, 100 insertions(+), 95 deletions(-) diff --git a/components/shared/TactTaskTag/index.tsx b/components/shared/TactTaskTag/index.tsx index 5a14e332..5310fbd7 100644 --- a/components/shared/TactTaskTag/index.tsx +++ b/components/shared/TactTaskTag/index.tsx @@ -1,7 +1,7 @@ import { Button, Tag, IconButton, ButtonProps } from "@chakra-ui/react"; import { faXmark } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import React, { FC } from "react"; +import React, { forwardRef } from "react"; interface TactTaskTagProps { buttonProps?: ButtonProps; @@ -11,12 +11,13 @@ interface TactTaskTagProps { title: string; } -export const TactTaskTag: FC = ({ +const TactTaskTag = forwardRef(({ buttonProps, iconButtonProps, tagProps, title, - showRemoveIcon = false }) => ( + showRemoveIcon = false +}, ref) => ( -) +)); + +TactTaskTag.displayName = 'TactTaskTag'; + +export {TactTaskTag}; diff --git a/components/shared/TagsInput/index.tsx b/components/shared/TagsInput/index.tsx index 8bcb2fe7..145e456d 100644 --- a/components/shared/TagsInput/index.tsx +++ b/components/shared/TagsInput/index.tsx @@ -1,4 +1,4 @@ -import { HStack, Input, Tag } from '@chakra-ui/react'; +import { HStack, Input } from '@chakra-ui/react'; import React, { FC } from 'react'; import { TactTaskTag } from '../TactTaskTag'; import { TaskTag } from '../TasksList/types'; @@ -40,9 +40,9 @@ export const TagsInput: FC = ({ tags, addTag, removeTag }) => { { diff --git a/components/shared/TaskQuickEditor/TaskQuickEditorMainMenu.tsx b/components/shared/TaskQuickEditor/TaskQuickEditorMainMenu.tsx index 3d786104..0130e6e4 100644 --- a/components/shared/TaskQuickEditor/TaskQuickEditorMainMenu.tsx +++ b/components/shared/TaskQuickEditor/TaskQuickEditorMainMenu.tsx @@ -1,5 +1,5 @@ import { observer } from 'mobx-react-lite'; -import { Modes, useTaskQuickEditorStore } from './store'; +import { useTaskQuickEditorStore } from './store'; import { chakra, IconButton, diff --git a/components/shared/TaskQuickEditor/TaskQuickEditorTags.tsx b/components/shared/TaskQuickEditor/TaskQuickEditorTags.tsx index af10ac25..c9ec6296 100644 --- a/components/shared/TaskQuickEditor/TaskQuickEditorTags.tsx +++ b/components/shared/TaskQuickEditor/TaskQuickEditorTags.tsx @@ -31,18 +31,18 @@ const TaskQuickEditorTagsList = observer(function TaskQuickEditorTags({ const renderContent = ({ title, id }: TaskTag) => ( store.modes.tag.setTagRef(el, id)} buttonProps={{ - onClick:(e) => { + onClick: (e) => { e.stopPropagation(); store.modes.tag.focusTagById(id); }, - ref: (el) => store.modes.tag.setTagRef(el, id), onKeyDown: (e) => store.modes.tag.handleButtonKeyDown(e, id), onFocus: store.handleModeFocus(Modes.TAG), ...buttonProps, }} iconButtonProps={{ - onClick:(e) => { + onClick: (e) => { e.stopPropagation(); store.modes.tag.removeTag(id); } @@ -55,24 +55,24 @@ const TaskQuickEditorTagsList = observer(function TaskQuickEditorTags({ return ( {store.modes.tag.tags.map((tag) => - disableAnimating - ? - - {renderContent(tag)} - - : ( - - {renderContent(tag)} - - ) + disableAnimating + ? + + {renderContent(tag)} + + : ( + + {renderContent(tag)} + + ) )} ); @@ -118,75 +118,75 @@ export const TaskQuickEditorTags = observer(function TaskQuickEditTags({ ref={store.modes.tag.setContainerRef} > {store.modes.tag.isCollapsed ? ( - - - - - - + + + + + - - - - - - - - + + + + + + + ) : ( )} diff --git a/components/shared/TasksList/components/TaskItemMenu/index.tsx b/components/shared/TasksList/components/TaskItemMenu/index.tsx index 42e584c3..0d18b8fc 100644 --- a/components/shared/TasksList/components/TaskItemMenu/index.tsx +++ b/components/shared/TasksList/components/TaskItemMenu/index.tsx @@ -79,7 +79,6 @@ const singleTaskItems = (store: TaskItemStore) => [ { onClick: () => { store.parent.modals.openPriorityModal(store.task.id); - // setTimeout(() => store.quickEdit.activateMode(Modes.PRIORITY)); }, title: 'Change priority', icon: faCircleExclamation, diff --git a/components/shared/TasksList/modals/TaskAddTagModal/store.ts b/components/shared/TasksList/modals/TaskAddTagModal/store.ts index a7e7b3cd..f3a078ad 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/store.ts +++ b/components/shared/TasksList/modals/TaskAddTagModal/store.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, toJS } from 'mobx'; +import { makeAutoObservable } from 'mobx'; import { getProvider } from '../../../../../helpers/StoreProvider'; import { ListNavigation } from '../../../../../helpers/ListNavigation'; import { RootStore } from '../../../../../stores/RootStore'; diff --git a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx index c0627dc0..35001eee 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx +++ b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx @@ -67,8 +67,8 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { return( alreadySelected ? store.removeTag(id) : store.addTag({ title, id }) From 92ffd11838a12443d65c0aa0132bb98533f46bdf Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Tue, 18 Apr 2023 08:57:51 +0300 Subject: [PATCH 3/7] tact-305: tags add modal fixes --- .../shared/TasksList/modals/TaskAddTagModal/store.ts | 11 +---------- .../shared/TasksList/modals/TaskAddTagModal/view.tsx | 6 ++---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/components/shared/TasksList/modals/TaskAddTagModal/store.ts b/components/shared/TasksList/modals/TaskAddTagModal/store.ts index f3a078ad..b4a78e82 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/store.ts +++ b/components/shared/TasksList/modals/TaskAddTagModal/store.ts @@ -1,6 +1,5 @@ import { makeAutoObservable } from 'mobx'; import { getProvider } from '../../../../../helpers/StoreProvider'; -import { ListNavigation } from '../../../../../helpers/ListNavigation'; import { RootStore } from '../../../../../stores/RootStore'; import { TaskTag } from '../../types'; import { v4 as uuidv4 } from 'uuid'; @@ -27,7 +26,7 @@ export class TaskAddTagModalStore { FORCE_ENTER: (e) => { e.preventDefault(); e.stopPropagation(); - this.navigation.hotkeyHandlers.FORCE_ENTER?.(e); + this.handleSave(); }, }; @@ -76,14 +75,6 @@ export class TaskAddTagModalStore { this.selectedTags = this.availableTags.filter(({ id }) => tags.includes(id)); this.callbacks = callbacks; }; - - navigationCallbacks = { - onForceEnter: () => { - this.handleSave(); - }, - }; - - navigation = new ListNavigation(this.navigationCallbacks); } export const { diff --git a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx index 35001eee..0053c11c 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx +++ b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx @@ -16,7 +16,6 @@ import { import { useTaskAddTagModalStore, } from './store'; -import { useListNavigation } from '../../../../../helpers/ListNavigation'; import { useHotkeysHandler } from '../../../../../helpers/useHotkeysHandler'; import { TagsInput } from '../../../TagsInput'; import { TactTaskTag } from '../../../TactTaskTag'; @@ -25,13 +24,12 @@ import { TactTaskTag } from '../../../TactTaskTag'; export const TaskAddTagModalView = observer(function TaskAddTagModalView() { const store = useTaskAddTagModalStore(); - useListNavigation(store.navigation); useHotkeysHandler(store.keyMap, store.hotkeyHandlers) return ( - + Add hashtag @@ -42,7 +40,7 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { addTag={store.createNewTag} removeTag={store.removeTag} /> - {store.availableTags.length && ( + {!!store.availableTags.length && ( All your hashtags From af06ebb8498b5f1f45dbf75d0408ef5f73a65705 Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Wed, 19 Apr 2023 18:53:52 +0300 Subject: [PATCH 4/7] tact-305: fixes without navigation --- .../modals/GoalCreationCloseSubmitModal.tsx | 2 +- .../Goals/modals/GoalCreationModal/store.ts | 2 +- .../modals/GoalWontDoSubmitModal/store.ts | 2 +- .../Spaces/modals/SpaceCreationModal/view.tsx | 5 +- components/shared/TactTaskTag/index.tsx | 10 +- components/shared/TagsInput/index.tsx | 65 ------ .../shared/TaskQuickEditor/modals/store.ts | 4 +- .../components/TaskItemMenu/index.tsx | 1 - .../TasksList/modals/TaskAddTagModal/store.ts | 83 -------- .../modals/TaskAddTagModal/store.tsx | 201 ++++++++++++++++++ .../TasksList/modals/TaskAddTagModal/view.tsx | 170 +++++++++++++-- .../modals/TaskGoalAssignModal/store.ts | 2 +- .../modals/TaskGoalAssignModal/view.tsx | 3 +- .../modals/TaskPriorityModal/store.ts | 2 +- .../modals/TaskPriorityModal/view.tsx | 3 +- .../modals/TaskSpaceChangeModal/view.tsx | 3 +- .../TasksList/modals/TaskWontDoModal/store.ts | 2 +- .../TasksList/modals/TaskWontDoModal/view.tsx | 3 +- helpers/ListNavigation.ts | 2 +- 19 files changed, 375 insertions(+), 190 deletions(-) delete mode 100644 components/shared/TagsInput/index.tsx delete mode 100644 components/shared/TasksList/modals/TaskAddTagModal/store.ts create mode 100644 components/shared/TasksList/modals/TaskAddTagModal/store.tsx diff --git a/components/pages/Goals/modals/GoalCreationModal/modals/GoalCreationCloseSubmitModal.tsx b/components/pages/Goals/modals/GoalCreationModal/modals/GoalCreationCloseSubmitModal.tsx index 3120d4e2..c46b84ec 100644 --- a/components/pages/Goals/modals/GoalCreationModal/modals/GoalCreationCloseSubmitModal.tsx +++ b/components/pages/Goals/modals/GoalCreationModal/modals/GoalCreationCloseSubmitModal.tsx @@ -21,7 +21,7 @@ export const GoalCreationCloseSubmitModal = observer( function GoalCreationCloseSubmitModal({ onClose, onSubmit }: GoalCreationCloseSubmitModalProps) { const initialRef = useRef(null); - useHotkeysHandler({ STAY: ['meta+enter'] }, { STAY: onClose }); + useHotkeysHandler({ STAY: ['meta+enter', 'ctrl+enter'] }, { STAY: onClose }); return ( ; keymap = { - SAVE: ['meta+enter'] + SAVE: ['meta+enter', 'ctrl+enter'] }; constructor() { diff --git a/components/pages/Spaces/modals/SpaceCreationModal/view.tsx b/components/pages/Spaces/modals/SpaceCreationModal/view.tsx index 6e13bc17..49128718 100644 --- a/components/pages/Spaces/modals/SpaceCreationModal/view.tsx +++ b/components/pages/Spaces/modals/SpaceCreationModal/view.tsx @@ -28,9 +28,10 @@ import { TextAreaLengthCounter } from '../../../../shared/TextAreaLengthCounter' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faAlignLeft, faTrashCan } from '@fortawesome/pro-light-svg-icons'; import { EmojiSelect } from "../../../../shared/EmojiSelect"; +import { isMac } from '../../../../../helpers/os'; const keyMap = { - CREATE: ['meta+enter', 'meta+s'], + CREATE: ['meta+enter', 'meta+s', 'ctrl+enter'], CANCEL: ['escape'], }; @@ -151,7 +152,7 @@ export const SpaceCreationModalView = observer(function SpaceCreationModal() { > Save - ⌘ + Enter + {`${isMac() ? '⌘' : 'Ctrl'} + Enter`} diff --git a/components/shared/TactTaskTag/index.tsx b/components/shared/TactTaskTag/index.tsx index 5310fbd7..87484745 100644 --- a/components/shared/TactTaskTag/index.tsx +++ b/components/shared/TactTaskTag/index.tsx @@ -9,6 +9,7 @@ interface TactTaskTagProps { tagProps?: ButtonProps; showRemoveIcon?: boolean; title: string; + selected?: boolean; } const TactTaskTag = forwardRef(({ @@ -16,7 +17,8 @@ const TactTaskTag = forwardRef(({ iconButtonProps, tagProps, title, - showRemoveIcon = false + showRemoveIcon = false, + selected = false, }, ref) => ( + ))} + + + + + )} + + + + {!!store.availableTags.length && ( All your hashtags @@ -62,20 +189,19 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { > {store.availableTags.map(({ title, id }) => { const alreadySelected = !!store.selectedTags.find(({ id: selectedId }) => selectedId === id) - return( - alreadySelected ? store.removeTag(id) : store.addTag({ title, id }) - }} - tagProps={{ - bg: alreadySelected ? 'blue.600' : 'blue.400' - }} - /> - )})} + return ( + alreadySelected ? store.removeTag(id) : store.addTag({ title, id }) + }} + /> + ) + })} )} @@ -115,7 +241,7 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { color='white' fontWeight={400} > - ⌘ + Enter + {`${isMac() ? '⌘' : 'Ctrl'} + Enter`} diff --git a/components/shared/TasksList/modals/TaskGoalAssignModal/store.ts b/components/shared/TasksList/modals/TaskGoalAssignModal/store.ts index 45184371..46d92cfa 100644 --- a/components/shared/TasksList/modals/TaskGoalAssignModal/store.ts +++ b/components/shared/TasksList/modals/TaskGoalAssignModal/store.ts @@ -29,7 +29,7 @@ export class TaskGoalAssignModalStore { keyMap = { RESET: ['backspace', 'delete'], - FORCE_ENTER: ['meta+enter'], + FORCE_ENTER: ['meta+enter', 'ctrl+enter'], }; hotkeyHandlers = { diff --git a/components/shared/TasksList/modals/TaskGoalAssignModal/view.tsx b/components/shared/TasksList/modals/TaskGoalAssignModal/view.tsx index cc209e37..ad274918 100644 --- a/components/shared/TasksList/modals/TaskGoalAssignModal/view.tsx +++ b/components/shared/TasksList/modals/TaskGoalAssignModal/view.tsx @@ -13,6 +13,7 @@ import React from 'react'; import { GoalsSelection } from '../../../GoalsSelection'; import { useListNavigation } from '../../../../../helpers/ListNavigation'; import { useHotkeysHandler } from '../../../../../helpers/useHotkeysHandler'; +import { isMac } from '../../../../../helpers/os'; export const TaskGoalAssignModalView = observer( function TaskGoalAssignModalView() { @@ -78,7 +79,7 @@ export const TaskGoalAssignModalView = observer( color='white' fontWeight={400} > - ⌘ + Enter + {`${isMac() ? '⌘' : 'Ctrl'} + Enter`} diff --git a/components/shared/TasksList/modals/TaskPriorityModal/store.ts b/components/shared/TasksList/modals/TaskPriorityModal/store.ts index fe70057a..4fbdf3c3 100644 --- a/components/shared/TasksList/modals/TaskPriorityModal/store.ts +++ b/components/shared/TasksList/modals/TaskPriorityModal/store.ts @@ -24,7 +24,7 @@ export class TaskPriorityModalStore { multiple: boolean = false; keyMap = { - FORCE_ENTER: ['meta+enter'], + FORCE_ENTER: ['meta+enter', 'ctrl+enter'], }; hotkeyHandlers = { diff --git a/components/shared/TasksList/modals/TaskPriorityModal/view.tsx b/components/shared/TasksList/modals/TaskPriorityModal/view.tsx index 1e5f93db..0c0680e8 100644 --- a/components/shared/TasksList/modals/TaskPriorityModal/view.tsx +++ b/components/shared/TasksList/modals/TaskPriorityModal/view.tsx @@ -13,6 +13,7 @@ import { useTaskPriorityModalStore } from './store'; import { useListNavigation } from '../../../../../helpers/ListNavigation'; import { useHotkeysHandler } from '../../../../../helpers/useHotkeysHandler'; import { PrioritySelection } from '../../../PrioritySelection'; +import { isMac } from '../../../../../helpers/os'; export const TaskPriorityModalView = observer( function TaskPriorityModalView() { @@ -77,7 +78,7 @@ export const TaskPriorityModalView = observer( color='white' fontWeight={400} > - ⌘ + Enter + {`${isMac() ? '⌘' : 'Ctrl'} + Enter`} diff --git a/components/shared/TasksList/modals/TaskSpaceChangeModal/view.tsx b/components/shared/TasksList/modals/TaskSpaceChangeModal/view.tsx index b3df9c7f..8a6b8c33 100644 --- a/components/shared/TasksList/modals/TaskSpaceChangeModal/view.tsx +++ b/components/shared/TasksList/modals/TaskSpaceChangeModal/view.tsx @@ -13,6 +13,7 @@ import { useTaskSpaceChangeModalStore } from './store'; import { SpacesSelection } from '../../../SpacesSelection'; import { useListNavigation } from '../../../../../helpers/ListNavigation'; import { useHotkeysHandler } from '../../../../../helpers/useHotkeysHandler'; +import { isMac } from '../../../../../helpers/os'; export const TaskSpaceChangeModalView = observer( function TaskSpaceChangeModalView() { @@ -78,7 +79,7 @@ export const TaskSpaceChangeModalView = observer( color='white' fontWeight={400} > - ⌘ + Enter + {`${isMac() ? '⌘' : 'Ctrl'} + Enter`} diff --git a/components/shared/TasksList/modals/TaskWontDoModal/store.ts b/components/shared/TasksList/modals/TaskWontDoModal/store.ts index 006279f7..745c7c81 100644 --- a/components/shared/TasksList/modals/TaskWontDoModal/store.ts +++ b/components/shared/TasksList/modals/TaskWontDoModal/store.ts @@ -27,7 +27,7 @@ export class TaskWontDoModalStore { keyMap = { RESET: ['backspace', 'delete'], - FORCE_ENTER: ['meta+enter'], + FORCE_ENTER: ['meta+enter', 'ctrl+enter'], }; hotkeyHandlers = { diff --git a/components/shared/TasksList/modals/TaskWontDoModal/view.tsx b/components/shared/TasksList/modals/TaskWontDoModal/view.tsx index 0c463198..d2e37f4d 100644 --- a/components/shared/TasksList/modals/TaskWontDoModal/view.tsx +++ b/components/shared/TasksList/modals/TaskWontDoModal/view.tsx @@ -24,6 +24,7 @@ import { } from './store'; import { useListNavigation } from '../../../../../helpers/ListNavigation'; import { useHotkeysHandler } from '../../../../../helpers/useHotkeysHandler'; +import { isMac } from '../../../../../helpers/os'; export const TaskWontDoModalView = observer(function TaskWontDoModalView({ onClose, @@ -93,7 +94,7 @@ export const TaskWontDoModalView = observer(function TaskWontDoModalView({ > Save - ⌘ + Enter + {`${isMac() ? '⌘' : 'Ctrl'} + Enter`} diff --git a/helpers/ListNavigation.ts b/helpers/ListNavigation.ts index 3b82c4f2..907dfd6a 100644 --- a/helpers/ListNavigation.ts +++ b/helpers/ListNavigation.ts @@ -43,7 +43,7 @@ export class ListNavigation { UP: ['up', 'j'], DOWN: ['down', 'k'], ENTER: ['enter'], - FORCE_ENTER: ['meta+enter'], + FORCE_ENTER: ['meta+enter', 'ctrl+enter'], FIRST: ['meta+up', 'meta+j', 'h'], LAST: ['meta+down', 'meta+k', 'l'], NUMBERS: numbers, From a9e95105085e09186c84f68b516a3952e555ed90 Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Wed, 19 Apr 2023 19:03:08 +0300 Subject: [PATCH 5/7] tact-305: build fixes --- components/shared/TasksList/components/TaskItemMenu/index.tsx | 1 - components/shared/TasksList/modals/TaskAddTagModal/view.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/components/shared/TasksList/components/TaskItemMenu/index.tsx b/components/shared/TasksList/components/TaskItemMenu/index.tsx index 5e4fad0b..0b6a8af7 100644 --- a/components/shared/TasksList/components/TaskItemMenu/index.tsx +++ b/components/shared/TasksList/components/TaskItemMenu/index.tsx @@ -2,7 +2,6 @@ import { observer } from 'mobx-react-lite'; import { TaskItemStore, useTaskItemStore } from '../TaskItem/store'; import React from 'react'; import { TaskStatus } from '../../types'; -import { Modes } from '../../../TaskQuickEditor/store'; import { faBan, faBullseyePointer, diff --git a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx index ea64af6c..85ec4f45 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx +++ b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx @@ -155,7 +155,7 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { )} - + # Date: Thu, 20 Apr 2023 08:47:23 +0300 Subject: [PATCH 6/7] tact-305: added navigation to tags modal --- .../components/TaskItemMenu/index.tsx | 2 +- .../modals/TaskAddTagModal/store.tsx | 183 +++++++++++++++--- .../TasksList/modals/TaskAddTagModal/view.tsx | 37 ++-- 3 files changed, 181 insertions(+), 41 deletions(-) diff --git a/components/shared/TasksList/components/TaskItemMenu/index.tsx b/components/shared/TasksList/components/TaskItemMenu/index.tsx index 0b6a8af7..fb71d638 100644 --- a/components/shared/TasksList/components/TaskItemMenu/index.tsx +++ b/components/shared/TasksList/components/TaskItemMenu/index.tsx @@ -86,7 +86,7 @@ const singleTaskItems = (store: TaskItemStore) => [ onClick: () => { store.parent.modals.openAddTagModal(store.task.id); }, - title: 'Add tag', + title: 'Add hashtag', icon: faHashtag, }, { diff --git a/components/shared/TasksList/modals/TaskAddTagModal/store.tsx b/components/shared/TasksList/modals/TaskAddTagModal/store.tsx index 2e191a96..ab6ef45f 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/store.tsx +++ b/components/shared/TasksList/modals/TaskAddTagModal/store.tsx @@ -1,10 +1,9 @@ -import React from 'react'; +import React, { SyntheticEvent } from 'react'; import { makeAutoObservable } from 'mobx'; import { getProvider } from '../../../../../helpers/StoreProvider'; import { RootStore } from '../../../../../stores/RootStore'; import { TaskTag } from '../../types'; import { v4 as uuidv4 } from 'uuid'; -import { TaskQuickEditorSuggestionsMenu } from '../../../TaskQuickEditor/suggestionsMenuStore'; export type TaskAddTagModalProps = { callbacks: { @@ -21,9 +20,19 @@ export class TaskAddTagModalStore { startSymbol = '#'; selectedTags: TaskTag[]; showSuggestions: boolean = false; + selectedTagsRefs = {}; + availableTagsRefs = {}; + suggestionsMenuRefs = {}; + inputRef = null; + blockInFocus: string; keyMap = { FORCE_ENTER: ['meta+enter', 'ctrl+enter'], + LEFT: ['left'], + RIGHT: ['right'], + DOWN: ['down'], + UP: ['up'], + }; hotkeyHandlers = { @@ -32,6 +41,26 @@ export class TaskAddTagModalStore { e.stopPropagation(); this.handleSave(); }, + LEFT: (e) => { + e.preventDefault(); + e.stopPropagation(); + this.modalNavigate(e); + }, + RIGHT: (e) => { + e.preventDefault(); + e.stopPropagation(); + this.modalNavigate(e); + }, + DOWN: (e) => { + e.preventDefault(); + e.stopPropagation(); + this.modalNavigate(e); + }, + UP: (e) => { + e.preventDefault(); + e.stopPropagation(); + this.modalNavigate(e); + }, }; strValue: string = '#'; @@ -68,7 +97,35 @@ export class TaskAddTagModalStore { ); } - removeTag = (id: string) => { + setRef = (type: string, index?: number) => (el) => { + switch (type) { + case 'input': + this.inputRef = el; + break; + case 'selectedTags': + this.selectedTagsRefs[index] = el; + break; + case 'availableTags': + this.availableTagsRefs[index] = el; + break; + case 'suggestionsMenu': + this.suggestionsMenuRefs[index] = el; + break; + } + } + + removeTag = (id: string, e?: SyntheticEvent) => { + if (e) { + const index = Number((e.target as HTMLButtonElement).name); + const ref = this.selectedTagsRefs + + if (this.selectedTags.length === 1) { + this.inputRef.focus(); + } else { + (ref?.[index - 1] ?? ref?.[index + 1]).focus(); + } + } + this.selectedTags = this.selectedTags.filter((tag) => tag.id !== id); }; @@ -77,35 +134,104 @@ export class TaskAddTagModalStore { }; createNewTag = (newTagTitle: string) => { - - const hasTag = this.availableTags.find( + const hasAvailableTag = this.availableTags.find( ({ title: tagTitle }) => tagTitle === newTagTitle ); - if (!hasTag) { + if (!hasAvailableTag) { const id = uuidv4(); const newTag = { title: newTagTitle, id }; this.addTag(newTag); this.root.resources.tags.add(newTag); return; } - this.addTag(hasTag); + this.addTag(hasAvailableTag); }; inputKeyDown = (e) => { const value = e.target.value const tagsLength = this.selectedTags.length + + if (this.showSuggestions && e.code === 'ArrowDown') { + e.preventDefault(); + e.stopPropagation(); + this.suggestionsMenuRefs[0].focus(); + this.changeFocusBlock('suggestionsMenu'); + } + if (e.code === 'Backspace' && !value.length && tagsLength) { - this.removeTag(this.selectedTags[tagsLength - 1].id) + this.removeTag(this.selectedTags[tagsLength - 1].id, e) } if (e.code !== 'Enter' && e.code !== 'Space') return if (!value.trim()) { e.target.value = '' return } - this.createNewTag('#' + value) - e.target.value = '' + if (!this.hasTag) { + this.createNewTag('#' + value) + this.toggleSuggestion(); + e.target.value = '' + } } + + modalNavigate = (e) => { + const focusedBlock = this.blockInFocus; + + if (focusedBlock === 'input') { + if (e.code === 'ArrowLeft' || (e.code === 'Tab' && e.shiftKey)) { + if (this.selectedTags.length) { + this.selectedTagsRefs[this.selectedTags.length - 1].focus(); + this.changeFocusBlock('selectedTags'); + } + } else if (e.code === 'ArrowRight' || e.code === 'Tab' || (!this.showSuggestions && e.code === 'ArrowDown')) { + if (this.availableTags.length) { + this.availableTagsRefs[0].focus(); + this.changeFocusBlock('availableTags'); + } + } + } else if (focusedBlock === 'suggestionsMenu') { + this.suggestionNavigate(e); + } else { + const tagsRefs = focusedBlock === 'selectedTags' ? this.selectedTagsRefs : this.availableTagsRefs + const index = Number(e.target.name); + + if (e.code === 'ArrowLeft' || (e.code === 'Tab' && e.shiftKey)) { + if (!!tagsRefs?.[index - 1]) { + tagsRefs[index - 1].focus(); + } else if (focusedBlock === 'availableTags') { + this.inputRef.focus(); + this.changeFocusBlock('input'); + } + } else if (e.code === 'ArrowRight' || e.code === 'Tab') { + if (!!tagsRefs?.[index + 1]) { + tagsRefs[index + 1].focus(); + } else if (focusedBlock === 'selectedTags') { + this.inputRef.focus(); + this.changeFocusBlock('input'); + } + } else if (e.code === 'ArrowUp' && focusedBlock === 'availableTags' && index === 0) { + this.inputRef.focus(); + this.changeFocusBlock('input'); + } + } + } + + suggestionNavigate = (e) => { + const index = Number(e.target.name); + const refs = this.suggestionsMenuRefs; + const maxIndex = this.suggestions.length - 1; + if (e.code === 'ArrowUp' || (e.code === 'Tab' && e.shiftKey)) { + if (index > 0) { + refs[index - 1].focus(); + } else { + this.inputRef.focus(); + this.changeFocusBlock('input'); + } + } else if ((e.code === 'ArrowDown' || e.code === 'Tab') && index < maxIndex) { + refs[index + 1].focus(); + } + } + handleInputChange = (event) => { const value = event.target.value if ((value.length && !this.showSuggestions) || (!value.length && this.showSuggestions)) { @@ -115,7 +241,8 @@ export class TaskAddTagModalStore { this.strValue = this.startSymbol + value } - handleFocusMenu = (event) => { + handleFocusInput = (event) => { + this.changeFocusBlock('input') if (event.target.value.length && !this.showSuggestions) { this.toggleSuggestion(); } @@ -133,11 +260,6 @@ export class TaskAddTagModalStore { this.callbacks.onSave(this.selectedTags); }; - update = ({ callbacks, tags }: TaskAddTagModalProps) => { - this.selectedTags = this.availableTags.filter(({ id }) => tags.includes(id)); - this.callbacks = callbacks; - }; - get suggestions() { const hasCreateNewTag = this.isTagCreationAvailable; const tags = this.filteredTags; @@ -146,7 +268,7 @@ export class TaskAddTagModalStore { if (hasCreateNewTag) { items.unshift( <> - Create new " + Create new " {this.strValue.slice(1)}" tag ); @@ -163,6 +285,12 @@ export class TaskAddTagModalStore { return items; } + resetInput = () => { + this.inputRef.value = ''; + this.showSuggestions = false; + this.inputRef.focus(); + } + handleSuggestionSelect = (index: number) => { if (this.strValue !== this.startSymbol || this.filteredTags.length) { if ( @@ -171,13 +299,14 @@ export class TaskAddTagModalStore { !this.hasTag && this.strValue !== this.startSymbol ) { + this.resetInput(); this.createNewTag(this.strValue); return; } if (!this.hasTag || index > 0) { const hasFirstItem = !this.currentTagMatch || this.hasTag - + this.resetInput(); this.addTag( this.filteredTags[hasFirstItem ? index - 1 : index] ); @@ -189,10 +318,20 @@ export class TaskAddTagModalStore { this.showSuggestions = !this.showSuggestions; } - suggestionsMenu = new TaskQuickEditorSuggestionsMenu({ - onSelect: this.handleSuggestionSelect, - onOpen: (isOpen: boolean) => null, - }); + changeFocusBlock = (type: string) => { + if (this.blockInFocus !== type) this.blockInFocus = type; + } + + update = ({ callbacks, tags }: TaskAddTagModalProps) => { + const selectedTags = this.availableTags.filter(({ id }) => tags.includes(id)); + this.selectedTags = selectedTags; + this.callbacks = callbacks; + if (selectedTags.length) { + this.blockInFocus = 'selectedTags'; + } else { + this.blockInFocus = 'input'; + } + }; } export const { diff --git a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx index 85ec4f45..730d1fa4 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx +++ b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx @@ -67,24 +67,27 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { } }} > - {store.selectedTags?.map(({ title, id }) => ( + {store.selectedTags?.map(({ title, id }, index) => ( store.changeFocusBlock('selectedTags'), onKeyDown: (e) => { - e.stopPropagation(); if (e.code === 'Backspace') { - store.removeTag(id) + store.removeTag(id, e) } } }} iconButtonProps={{ + name: index.toString(), onClick: (e) => { e.stopPropagation(); - store.removeTag(id); + store.removeTag(id, e); }, }} /> @@ -131,20 +134,14 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { fontWeight='normal' display='flex' justifyContent='start' - onClick={() => store.suggestionsMenu.onSelect(index)} - bg={ - store.suggestionsMenu.hoveredIndex === index - ? 'gray.100' - : 'white' - } - ref={ - store.suggestionsMenu.hoveredIndex === index - ? (el) => store.suggestionsMenu.setRef(el) - : undefined - } + onClick={() => store.handleSuggestionSelect(index)} + bg='white' + name={index.toString()} + ref={store.setRef('suggestionsMenu', index)} _focus={{ outline: 'none', boxShadow: 'none', + background: 'gray.100', }} > {child} @@ -160,9 +157,10 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { placeholder='Type in a tag' flexGrow={1} maxLength={20} + ref={store.setRef('input')} onKeyDown={store.inputKeyDown} onChange={store.handleInputChange} - onFocus={store.handleFocusMenu} + onFocus={store.handleFocusInput} variant="unstyled" /> @@ -187,17 +185,20 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { }, }} > - {store.availableTags.map(({ title, id }) => { + {store.availableTags.map(({ title, id }, index) => { const alreadySelected = !!store.selectedTags.find(({ id: selectedId }) => selectedId === id) return ( alreadySelected ? store.removeTag(id) : store.addTag({ title, id }) + name: index.toString(), + onFocus: () => store.changeFocusBlock('availableTags'), + onClick: (e) => alreadySelected ? store.removeTag(id) : store.addTag({ title, id }) }} /> ) From 9b0e5038b87e226efa1d59dcdb4c7c931a17fb8a Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Thu, 20 Apr 2023 12:23:38 +0300 Subject: [PATCH 7/7] tact-305: remove tags fixes --- components/shared/TasksList/modals/TaskAddTagModal/store.tsx | 2 +- components/shared/TasksList/modals/TaskAddTagModal/view.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/shared/TasksList/modals/TaskAddTagModal/store.tsx b/components/shared/TasksList/modals/TaskAddTagModal/store.tsx index ab6ef45f..364256f0 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/store.tsx +++ b/components/shared/TasksList/modals/TaskAddTagModal/store.tsx @@ -160,7 +160,7 @@ export class TaskAddTagModalStore { } if (e.code === 'Backspace' && !value.length && tagsLength) { - this.removeTag(this.selectedTags[tagsLength - 1].id, e) + this.removeTag(this.selectedTags[tagsLength - 1].id) } if (e.code !== 'Enter' && e.code !== 'Space') return if (!value.trim()) { diff --git a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx index 730d1fa4..34616b98 100644 --- a/components/shared/TasksList/modals/TaskAddTagModal/view.tsx +++ b/components/shared/TasksList/modals/TaskAddTagModal/view.tsx @@ -84,10 +84,9 @@ export const TaskAddTagModalView = observer(function TaskAddTagModalView() { } }} iconButtonProps={{ - name: index.toString(), onClick: (e) => { e.stopPropagation(); - store.removeTag(id, e); + store.removeTag(id); }, }} />