From 52f78a86af8a38c6cca53158064609d31c3bf077 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:21:14 +0530 Subject: [PATCH] [PWA-26] chore: pwa input focus improvement (#5507) * chore: pwa dropdown input focus improvement * chore: tab indices helper function updated and code refactor * chore: modal tab index refactoring * fix: PWA filters input autofocus * chore: intake tab index updated and code refactor * chore: code refactor --- .../ui/src/dropdown/common/input-search.tsx | 8 +- packages/ui/src/dropdown/common/options.tsx | 2 + packages/ui/src/dropdown/dropdown.d.ts | 1 + .../components/projects/create/attributes.tsx | 22 ++++- web/ce/components/projects/create/root.tsx | 4 +- .../command-palette/command-modal.tsx | 6 +- .../modals/existing-issues-list-modal.tsx | 4 + .../cycles/dropdowns/filters/root.tsx | 5 +- web/core/components/cycles/form.tsx | 20 ++-- web/core/components/cycles/modal.tsx | 3 + .../dropdowns/cycle/cycle-options.tsx | 8 +- .../dropdowns/member/member-options.tsx | 8 +- .../components/dropdowns/module/index.tsx | 6 +- .../dropdowns/module/module-options.tsx | 8 +- .../inbox-filter/filters/filter-selection.tsx | 4 +- .../modals/create-edit-modal/create-root.tsx | 9 ++ .../create-edit-modal/issue-description.tsx | 8 ++ .../create-edit-modal/issue-properties.tsx | 18 +++- .../modals/create-edit-modal/issue-title.tsx | 10 ++ .../label/select/label-select.tsx | 7 ++ .../header/filters/filters-selection.tsx | 4 +- .../issue-layouts/properties/labels.tsx | 4 +- .../components/default-properties.tsx | 28 +++--- .../components/description-editor.tsx | 14 ++- .../issue-modal/components/parent-tag.tsx | 12 ++- .../issue-modal/components/project-select.tsx | 10 +- .../issue-modal/components/title-input.tsx | 13 ++- .../components/issues/issue-modal/form.tsx | 17 ++-- .../issues/parent-issues-list-modal.tsx | 4 + web/core/components/issues/select/label.tsx | 6 +- .../components/labels/create-label-modal.tsx | 21 +++-- .../modules/dropdowns/filters/root.tsx | 5 +- web/core/components/modules/form.tsx | 26 +++-- web/core/components/modules/modal.tsx | 3 + .../components/pages/list/filters/root.tsx | 5 +- .../components/pages/modals/page-form.tsx | 10 +- .../project/create/common-attributes.tsx | 14 ++- web/core/components/project/create/header.tsx | 20 +++- .../project/create/project-create-buttons.tsx | 15 ++- .../project/dropdowns/filters/root.tsx | 5 +- .../views/filters/filter-selection.tsx | 5 +- web/core/components/views/form.tsx | 17 +++- web/core/constants/issue-modal.ts | 22 ----- web/core/constants/tab-indices.ts | 94 +++++++++++++++++++ web/core/hooks/use-dropdown.ts | 7 +- web/helpers/issue-modal.helper.ts | 3 - web/helpers/tab-indices.helper.ts | 10 ++ 47 files changed, 430 insertions(+), 125 deletions(-) delete mode 100644 web/core/constants/issue-modal.ts create mode 100644 web/core/constants/tab-indices.ts delete mode 100644 web/helpers/issue-modal.helper.ts create mode 100644 web/helpers/tab-indices.helper.ts diff --git a/packages/ui/src/dropdown/common/input-search.tsx b/packages/ui/src/dropdown/common/input-search.tsx index 10fc258e1..984f99735 100644 --- a/packages/ui/src/dropdown/common/input-search.tsx +++ b/packages/ui/src/dropdown/common/input-search.tsx @@ -14,10 +14,12 @@ interface IInputSearch { inputContainerClassName?: string; inputClassName?: string; inputPlaceholder?: string; + isMobile: boolean; } export const InputSearch: FC = (props) => { - const { isOpen, query, updateQuery, inputIcon, inputContainerClassName, inputClassName, inputPlaceholder } = props; + const { isOpen, query, updateQuery, inputIcon, inputContainerClassName, inputClassName, inputPlaceholder, isMobile } = + props; const inputRef = useRef(null); @@ -29,10 +31,10 @@ export const InputSearch: FC = (props) => { }; useEffect(() => { - if (isOpen) { + if (isOpen && !isMobile) { inputRef.current && inputRef.current.focus(); } - }, [isOpen]); + }, [isOpen, isMobile]); return (
@@ -38,6 +39,7 @@ export const DropdownOptions: React.FC )}
diff --git a/packages/ui/src/dropdown/dropdown.d.ts b/packages/ui/src/dropdown/dropdown.d.ts index 8264bda21..74cd0e45d 100644 --- a/packages/ui/src/dropdown/dropdown.d.ts +++ b/packages/ui/src/dropdown/dropdown.d.ts @@ -85,6 +85,7 @@ export interface IDropdownOptions { renderItem: (({ value, selected }: { value: string; selected: boolean }) => React.ReactNode) | undefined; options: TDropdownOption[] | undefined; loader?: React.ReactNode; + isMobile?: boolean; } export interface IMultiSelectDropdownOptions extends IDropdownOptions { diff --git a/web/ce/components/projects/create/attributes.tsx b/web/ce/components/projects/create/attributes.tsx index ead92208f..178275641 100644 --- a/web/ce/components/projects/create/attributes.tsx +++ b/web/ce/components/projects/create/attributes.tsx @@ -1,11 +1,25 @@ +"use client"; +import { FC } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { IProject } from "@plane/types"; +// ui import { CustomSelect } from "@plane/ui"; +// components import { MemberDropdown } from "@/components/dropdowns"; +// constants import { NETWORK_CHOICES } from "@/constants/project"; +import { ETabIndices } from "@/constants/tab-indices"; +// helpers +import { getTabIndex } from "@/helpers/tab-indices.helper"; -const ProjectAttributes = () => { +type Props = { + isMobile?: boolean; +}; + +const ProjectAttributes: FC = (props) => { + const { isMobile = false } = props; const { control } = useFormContext(); + const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile); return (
{ const currentNetwork = NETWORK_CHOICES.find((n) => n.key === value); return ( -
+
{ className="h-full" buttonClassName="h-full" noChevron - tabIndex={4} + tabIndex={getIndex("network")} > {NETWORK_CHOICES.map((network) => ( @@ -59,7 +73,7 @@ const ProjectAttributes = () => { render={({ field: { value, onChange } }) => { if (value === undefined || value === null || typeof value === "string") return ( -
+
onChange(lead === value ? null : lead)} diff --git a/web/ce/components/projects/create/root.tsx b/web/ce/components/projects/create/root.tsx index 76fea48f7..5d6741418 100644 --- a/web/ce/components/projects/create/root.tsx +++ b/web/ce/components/projects/create/root.tsx @@ -120,7 +120,7 @@ export const CreateProjectForm: FC = observer((props) => { return ( - +
@@ -130,7 +130,7 @@ export const CreateProjectForm: FC = observer((props) => { isChangeInIdentifierRequired={isChangeInIdentifierRequired} setIsChangeInIdentifierRequired={setIsChangeInIdentifierRequired} /> - +
diff --git a/web/core/components/command-palette/command-modal.tsx b/web/core/components/command-palette/command-modal.tsx index e7b65b449..d0e5010e8 100644 --- a/web/core/components/command-palette/command-modal.tsx +++ b/web/core/components/command-palette/command-modal.tsx @@ -28,6 +28,8 @@ import { EmptyState } from "@/components/empty-state"; import { EmptyStateType } from "@/constants/empty-state"; // fetch-keys import { ISSUE_DETAILS } from "@/constants/fetch-keys"; +// helpers +import { getTabIndex } from "@/helpers/tab-indices.helper"; // hooks import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; @@ -80,6 +82,8 @@ export const CommandModal: React.FC = observer(() => { const debouncedSearchTerm = useDebounce(searchTerm, 500); + const { baseTabIndex } = getTabIndex(undefined, isMobile); + // TODO: update this to mobx store const { data: issueDetails } = useSWR( workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId.toString()) : null, @@ -238,7 +242,7 @@ export const CommandModal: React.FC = observer(() => { value={searchTerm} onValueChange={(e) => setSearchTerm(e)} autoFocus - tabIndex={1} + tabIndex={baseTabIndex} />
diff --git a/web/core/components/core/modals/existing-issues-list-modal.tsx b/web/core/components/core/modals/existing-issues-list-modal.tsx index 780a8642d..238efb7a9 100644 --- a/web/core/components/core/modals/existing-issues-list-modal.tsx +++ b/web/core/components/core/modals/existing-issues-list-modal.tsx @@ -7,6 +7,8 @@ import { Combobox, Dialog, Transition } from "@headlessui/react"; import { ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types"; // ui import { Button, Loader, ToggleSwitch, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; +// helpers +import { getTabIndex } from "@/helpers/tab-indices.helper"; // hooks import useDebounce from "@/hooks/use-debounce"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -51,6 +53,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); const { isMobile } = usePlatformOS(); const debouncedSearchTerm: string = useDebounce(searchTerm, 500); + const { baseTabIndex } = getTabIndex(undefined, isMobile); const handleClose = () => { onClose(); @@ -140,6 +143,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { placeholder="Type to search..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} + tabIndex={baseTabIndex} />
diff --git a/web/core/components/cycles/dropdowns/filters/root.tsx b/web/core/components/cycles/dropdowns/filters/root.tsx index 5581f3ec1..354fea41c 100644 --- a/web/core/components/cycles/dropdowns/filters/root.tsx +++ b/web/core/components/cycles/dropdowns/filters/root.tsx @@ -4,6 +4,7 @@ import { Search, X } from "lucide-react"; import { TCycleFilters, TCycleGroups } from "@plane/types"; // components import { FilterEndDate, FilterStartDate, FilterStatus } from "@/components/cycles"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // types type Props = { @@ -16,6 +17,8 @@ export const CycleFiltersSelection: React.FC = observer((props) => { const { filters, handleFiltersUpdate, isArchived = false } = props; // states const [filtersSearchQuery, setFiltersSearchQuery] = useState(""); + // hooks + const { isMobile } = usePlatformOS(); return (
@@ -28,7 +31,7 @@ export const CycleFiltersSelection: React.FC = observer((props) => { placeholder="Search" value={filtersSearchQuery} onChange={(e) => setFiltersSearchQuery(e.target.value)} - autoFocus + autoFocus={!isMobile} /> {filtersSearchQuery !== "" && (
)} @@ -101,7 +107,7 @@ export const CycleForm: React.FC = (props) => { inputSize="md" onChange={onChange} hasError={Boolean(errors?.name)} - tabIndex={1} + tabIndex={getIndex("description")} autoFocus /> )} @@ -120,7 +126,7 @@ export const CycleForm: React.FC = (props) => { hasError={Boolean(errors?.description)} value={value} onChange={onChange} - tabIndex={2} + tabIndex={getIndex("description")} /> )} /> @@ -153,7 +159,7 @@ export const CycleForm: React.FC = (props) => { hideIcon={{ to: true, }} - tabIndex={3} + tabIndex={getIndex("date_range")} /> )} /> @@ -163,10 +169,10 @@ export const CycleForm: React.FC = (props) => {
- -
diff --git a/web/core/components/cycles/modal.tsx b/web/core/components/cycles/modal.tsx index ab0110467..f8b1590c4 100644 --- a/web/core/components/cycles/modal.tsx +++ b/web/core/components/cycles/modal.tsx @@ -13,6 +13,7 @@ import { CYCLE_CREATED, CYCLE_UPDATED } from "@/constants/event-tracker"; // hooks import { useEventTracker, useCycle, useProject } from "@/hooks/store"; import useLocalStorage from "@/hooks/use-local-storage"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // services import { CycleService } from "@/services/cycle.service"; @@ -35,6 +36,7 @@ export const CycleCreateUpdateModal: React.FC = (props) => { const { captureCycleEvent } = useEventTracker(); const { workspaceProjectIds } = useProject(); const { createCycle, updateCycleDetails } = useCycle(); + const { isMobile } = usePlatformOS(); const { setValue: setCycleTab } = useLocalStorage("cycle_tab", "active"); @@ -186,6 +188,7 @@ export const CycleCreateUpdateModal: React.FC = (props) => { projectId={activeProject ?? ""} setActiveProject={setActiveProject} data={data} + isMobile={isMobile} /> ); diff --git a/web/core/components/dropdowns/cycle/cycle-options.tsx b/web/core/components/dropdowns/cycle/cycle-options.tsx index b96bbf900..cd4f4d997 100644 --- a/web/core/components/dropdowns/cycle/cycle-options.tsx +++ b/web/core/components/dropdowns/cycle/cycle-options.tsx @@ -14,6 +14,7 @@ import { TCycleGroups } from "@plane/types"; import { ContrastIcon, CycleGroupIcon } from "@plane/ui"; // store hooks import { useCycle } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // types type DropdownOptions = @@ -41,13 +42,16 @@ export const CycleOptions: FC = observer((props) => { // store hooks const { workspaceSlug } = useParams(); const { getProjectCycleIds, fetchAllCycles, getCycleById } = useCycle(); + const { isMobile } = usePlatformOS(); useEffect(() => { if (isOpen) { onOpen(); - inputRef.current && inputRef.current.focus(); + if (!isMobile) { + inputRef.current && inputRef.current.focus(); + } } - }, [isOpen]); + }, [isOpen, isMobile]); // popper-js init const { styles, attributes } = usePopper(referenceElement, popperElement, { diff --git a/web/core/components/dropdowns/member/member-options.tsx b/web/core/components/dropdowns/member/member-options.tsx index 8e7003f24..7d0c5c0b0 100644 --- a/web/core/components/dropdowns/member/member-options.tsx +++ b/web/core/components/dropdowns/member/member-options.tsx @@ -12,6 +12,7 @@ import { Combobox } from "@headlessui/react"; import { Avatar } from "@plane/ui"; //store import { useUser, useMember } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; interface Props { projectId?: string; @@ -35,6 +36,7 @@ export const MemberOptions = observer((props: Props) => { workspace: { workspaceMemberIds }, } = useMember(); const { data: currentUser } = useUser(); + const { isMobile } = usePlatformOS(); // popper-js init const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: placement ?? "bottom-start", @@ -51,9 +53,11 @@ export const MemberOptions = observer((props: Props) => { useEffect(() => { if (isOpen) { onOpen(); - inputRef.current && inputRef.current.focus(); + if (!isMobile) { + inputRef.current && inputRef.current.focus(); + } } - }, [isOpen]); + }, [isOpen, isMobile]); const memberIds = projectId ? getProjectMemberIds(projectId) : workspaceMemberIds; const onOpen = () => { diff --git a/web/core/components/dropdowns/module/index.tsx b/web/core/components/dropdowns/module/index.tsx index 9368990d3..c4cb660d2 100644 --- a/web/core/components/dropdowns/module/index.tsx +++ b/web/core/components/dropdowns/module/index.tsx @@ -185,6 +185,8 @@ export const ModuleDropdown: React.FC = observer((props) => { const inputRef = useRef(null); // popper-js refs const [referenceElement, setReferenceElement] = useState(null); + // store hooks + const { isMobile } = usePlatformOS(); const { getModuleNameById } = useModule(); @@ -209,10 +211,10 @@ export const ModuleDropdown: React.FC = observer((props) => { if (multiple) comboboxProps.multiple = true; useEffect(() => { - if (isOpen && inputRef.current) { + if (isOpen && inputRef.current && !isMobile) { inputRef.current.focus(); } - }, [isOpen]); + }, [isOpen, isMobile]); const comboButton = ( <> diff --git a/web/core/components/dropdowns/module/module-options.tsx b/web/core/components/dropdowns/module/module-options.tsx index 8ceb94ba8..2d8d55d28 100644 --- a/web/core/components/dropdowns/module/module-options.tsx +++ b/web/core/components/dropdowns/module/module-options.tsx @@ -12,6 +12,7 @@ import { DiceIcon } from "@plane/ui"; //store import { cn } from "@/helpers/common.helper"; import { useModule } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; //hooks //icon //types @@ -42,14 +43,17 @@ export const ModuleOptions = observer((props: Props) => { // store hooks const { workspaceSlug } = useParams(); const { getProjectModuleIds, fetchModules, getModuleById } = useModule(); + const { isMobile } = usePlatformOS(); useEffect(() => { if (isOpen) { onOpen(); - inputRef.current && inputRef.current.focus(); + if (!isMobile) { + inputRef.current && inputRef.current.focus(); + } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isOpen]); + }, [isOpen, isMobile]); // popper-js init const { styles, attributes } = usePopper(referenceElement, popperElement, { diff --git a/web/core/components/inbox/inbox-filter/filters/filter-selection.tsx b/web/core/components/inbox/inbox-filter/filters/filter-selection.tsx index 1d7ddd0ed..cff75e209 100644 --- a/web/core/components/inbox/inbox-filter/filters/filter-selection.tsx +++ b/web/core/components/inbox/inbox-filter/filters/filter-selection.tsx @@ -12,9 +12,11 @@ import { } from "@/components/inbox/inbox-filter/filters"; // hooks import { useMember, useLabel, useProjectState } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; export const InboxIssueFilterSelection: FC = observer(() => { // hooks + const { isMobile } = usePlatformOS(); const { project: { projectMemberIds }, } = useMember(); @@ -34,7 +36,7 @@ export const InboxIssueFilterSelection: FC = observer(() => { placeholder="Search" value={filtersSearchQuery} onChange={(e) => setFiltersSearchQuery(e.target.value)} - autoFocus + autoFocus={!isMobile} /> {filtersSearchQuery !== "" && ( @@ -210,6 +218,7 @@ export const InboxIssueCreateRoot: FC = observer((props) type="submit" loading={formSubmitting} disabled={isTitleLengthMoreThan255Character} + tabIndex={getIndex("submit_button")} > {formSubmitting ? "Creating" : "Create Issue"} diff --git a/web/core/components/inbox/modals/create-edit-modal/issue-description.tsx b/web/core/components/inbox/modals/create-edit-modal/issue-description.tsx index 18cd40944..4daface0b 100644 --- a/web/core/components/inbox/modals/create-edit-modal/issue-description.tsx +++ b/web/core/components/inbox/modals/create-edit-modal/issue-description.tsx @@ -10,10 +10,14 @@ import { TIssue } from "@plane/types"; import { Loader } from "@plane/ui"; // components import { RichTextEditor } from "@/components/editor/rich-text-editor/rich-text-editor"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; // helpers import { getDescriptionPlaceholder } from "@/helpers/issue.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; // hooks import { useProjectInbox } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; type TInboxIssueDescription = { containerClassName?: string; @@ -32,6 +36,9 @@ export const InboxIssueDescription: FC = observer((props props; // hooks const { loader } = useProjectInbox(); + const { isMobile } = usePlatformOS(); + + const { getIndex } = getTabIndex(ETabIndices.INTAKE_ISSUE_FORM, isMobile); if (loader === "issue-loading") return ( @@ -53,6 +60,7 @@ export const InboxIssueDescription: FC = observer((props placeholder={getDescriptionPlaceholder} containerClassName={containerClassName} onEnterKeyPress={onEnterKeyPress} + tabIndex={getIndex("description_html")} /> ); }); diff --git a/web/core/components/inbox/modals/create-edit-modal/issue-properties.tsx b/web/core/components/inbox/modals/create-edit-modal/issue-properties.tsx index fed488332..b47d72c7a 100644 --- a/web/core/components/inbox/modals/create-edit-modal/issue-properties.tsx +++ b/web/core/components/inbox/modals/create-edit-modal/issue-properties.tsx @@ -15,10 +15,14 @@ import { } from "@/components/dropdowns"; import { ParentIssuesListModal } from "@/components/issues"; import { IssueLabelSelect } from "@/components/issues/select"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; // helpers import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; // hooks import { useProjectEstimates } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; type TInboxIssueProperties = { projectId: string; @@ -31,10 +35,12 @@ export const InboxIssueProperties: FC = observer((props) const { projectId, data, handleData, isVisible = false } = props; // hooks const { areEstimateEnabledByProjectId } = useProjectEstimates(); + const { isMobile } = usePlatformOS(); // states const [parentIssueModalOpen, setParentIssueModalOpen] = useState(false); const [selectedParentIssue, setSelectedParentIssue] = useState(undefined); - true; + + const { getIndex } = getTabIndex(ETabIndices.INTAKE_ISSUE_FORM, isMobile); const startDate = data?.start_date; const targetDate = data?.target_date; @@ -54,6 +60,7 @@ export const InboxIssueProperties: FC = observer((props) onChange={(stateId) => handleData("state_id", stateId)} projectId={projectId} buttonVariant="border-with-text" + tabIndex={getIndex("state_id")} />
@@ -63,6 +70,7 @@ export const InboxIssueProperties: FC = observer((props) value={data?.priority} onChange={(priority) => handleData("priority", priority)} buttonVariant="border-with-text" + tabIndex={getIndex("priority")} />
@@ -76,6 +84,7 @@ export const InboxIssueProperties: FC = observer((props) buttonClassName={(data?.assignee_ids || [])?.length > 0 ? "hover:bg-transparent" : ""} placeholder="Assignees" multiple + tabIndex={getIndex("assignee_ids")} />
@@ -87,6 +96,7 @@ export const InboxIssueProperties: FC = observer((props) value={data?.label_ids || []} onChange={(labelIds) => handleData("label_ids", labelIds)} projectId={projectId} + tabIndex={getIndex("label_ids")} /> @@ -99,6 +109,7 @@ export const InboxIssueProperties: FC = observer((props) buttonVariant="border-with-text" minDate={minDate ?? undefined} placeholder="Start date" + tabIndex={getIndex("start_date")} /> )} @@ -111,6 +122,7 @@ export const InboxIssueProperties: FC = observer((props) buttonVariant="border-with-text" minDate={minDate ?? undefined} placeholder="Due date" + tabIndex={getIndex("target_date")} /> @@ -123,6 +135,7 @@ export const InboxIssueProperties: FC = observer((props) projectId={projectId} placeholder="Cycle" buttonVariant="border-with-text" + tabIndex={getIndex("cycle_id")} /> )} @@ -138,6 +151,7 @@ export const InboxIssueProperties: FC = observer((props) buttonVariant="border-with-text" multiple showCount + tabIndex={getIndex("module_ids")} /> )} @@ -151,6 +165,7 @@ export const InboxIssueProperties: FC = observer((props) projectId={projectId} buttonVariant="border-with-text" placeholder="Estimate" + tabIndex={getIndex("estimate_point")} /> )} @@ -174,6 +189,7 @@ export const InboxIssueProperties: FC = observer((props) } placement="bottom-start" + tabIndex={getIndex("parent_id")} > <> setParentIssueModalOpen(true)}> diff --git a/web/core/components/inbox/modals/create-edit-modal/issue-title.tsx b/web/core/components/inbox/modals/create-edit-modal/issue-title.tsx index ce24bee62..f5c8f2427 100644 --- a/web/core/components/inbox/modals/create-edit-modal/issue-title.tsx +++ b/web/core/components/inbox/modals/create-edit-modal/issue-title.tsx @@ -4,6 +4,12 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { TIssue } from "@plane/types"; import { Input } from "@plane/ui"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; +// helpers +import { getTabIndex } from "@/helpers/tab-indices.helper"; +// hooks +import { usePlatformOS } from "@/hooks/use-platform-os"; type TInboxIssueTitle = { data: Partial; @@ -13,7 +19,10 @@ type TInboxIssueTitle = { export const InboxIssueTitle: FC = observer((props) => { const { data, handleData, isTitleLengthMoreThan255Character } = props; + // hooks + const { isMobile } = usePlatformOS(); + const { getIndex } = getTabIndex(ETabIndices.INTAKE_ISSUE_FORM, isMobile); return (
= observer((props) => { onChange={(e) => handleData("name", e.target.value)} placeholder="Title" className="w-full text-base" + tabIndex={getIndex("name")} required /> {isTitleLengthMoreThan255Character && ( diff --git a/web/core/components/issues/issue-detail/label/select/label-select.tsx b/web/core/components/issues/issue-detail/label/select/label-select.tsx index fdf39997b..7fd700398 100644 --- a/web/core/components/issues/issue-detail/label/select/label-select.tsx +++ b/web/core/components/issues/issue-detail/label/select/label-select.tsx @@ -3,8 +3,11 @@ import { observer } from "mobx-react"; import { usePopper } from "react-popper"; import { Check, Search, Tag } from "lucide-react"; import { Combobox } from "@headlessui/react"; +// helpers +import { getTabIndex } from "@/helpers/tab-indices.helper"; // hooks import { useLabel } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // components export interface IIssueLabelSelect { @@ -18,6 +21,7 @@ export interface IIssueLabelSelect { export const IssueLabelSelect: React.FC = observer((props) => { const { workspaceSlug, projectId, issueId, values, onSelect } = props; // store hooks + const { isMobile } = usePlatformOS(); const { fetchProjectLabels, getProjectLabels } = useLabel(); // states const [referenceElement, setReferenceElement] = useState(null); @@ -27,6 +31,8 @@ export const IssueLabelSelect: React.FC = observer((props) => const projectLabels = getProjectLabels(projectId); + const { baseTabIndex } = getTabIndex(undefined, isMobile); + const fetchLabels = () => { setIsLoading(true); if (!projectLabels && workspaceSlug && projectId) @@ -123,6 +129,7 @@ export const IssueLabelSelect: React.FC = observer((props) => placeholder="Search" displayValue={(assigned: any) => assigned?.name} onKeyDown={searchInputKeyDown} + tabIndex={baseTabIndex} />
diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx index dc7426b80..1d7d9ceb4 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx @@ -24,6 +24,7 @@ import { import { ILayoutDisplayFiltersOptions } from "@/constants/issue"; // plane web components import { FilterIssueTypes } from "@/plane-web/components/issues"; +import { usePlatformOS } from "@/hooks/use-platform-os"; type Props = { filters: IIssueFilterOptions; @@ -52,6 +53,7 @@ export const FilterSelection: React.FC = observer((props) => { moduleViewDisabled = false, } = props; // hooks + const { isMobile } = usePlatformOS(); const { moduleId, cycleId } = useParams(); // states const [filtersSearchQuery, setFiltersSearchQuery] = useState(""); @@ -72,7 +74,7 @@ export const FilterSelection: React.FC = observer((props) => { placeholder="Search" value={filtersSearchQuery} onChange={(e) => setFiltersSearchQuery(e.target.value)} - autoFocus + autoFocus={!isMobile} /> {filtersSearchQuery !== "" && ( } placement="bottom-start" - tabIndex={getTabIndex("parent_id")} + tabIndex={getIndex("parent_id")} > <> setParentIssueListModalOpen(true)}> diff --git a/web/core/components/issues/issue-modal/components/description-editor.tsx b/web/core/components/issues/issue-modal/components/description-editor.tsx index 0a16f22dd..098c5b579 100644 --- a/web/core/components/issues/issue-modal/components/description-editor.tsx +++ b/web/core/components/issues/issue-modal/components/description-editor.tsx @@ -13,12 +13,15 @@ import { Loader, setToast, TOAST_TYPE } from "@plane/ui"; // components import { GptAssistantPopover } from "@/components/core"; import { RichTextEditor } from "@/components/editor"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; // helpers -import { getTabIndex } from "@/helpers/issue-modal.helper"; import { getDescriptionPlaceholder } from "@/helpers/issue.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; // hooks import { useInstance, useWorkspace } from "@/hooks/store"; import useKeypress from "@/hooks/use-keypress"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // services import { AIService } from "@/services/ai.service"; @@ -63,6 +66,9 @@ export const IssueDescriptionEditor: React.FC = ob const { getWorkspaceBySlug } = useWorkspace(); const workspaceId = getWorkspaceBySlug(workspaceSlug?.toString())?.id as string; const { config } = useInstance(); + const { isMobile } = usePlatformOS(); + + const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile); useEffect(() => { if (descriptionHtmlData) handleDescriptionHTMLDataChange(descriptionHtmlData); @@ -171,7 +177,7 @@ export const IssueDescriptionEditor: React.FC = ob }} onEnterKeyPress={() => submitBtnRef?.current?.click()} ref={editorRef} - tabIndex={getTabIndex("description_html")} + tabIndex={getIndex("description_html")} placeholder={getDescriptionPlaceholder} containerClassName="pt-3 min-h-[120px]" /> @@ -186,7 +192,7 @@ export const IssueDescriptionEditor: React.FC = ob }`} onClick={handleAutoGenerateDescription} disabled={iAmFeelingLucky} - tabIndex={getTabIndex("feeling_lucky")} + tabIndex={getIndex("feeling_lucky")} > {iAmFeelingLucky ? ( "Generating response" @@ -214,7 +220,7 @@ export const IssueDescriptionEditor: React.FC = ob type="button" className="flex items-center gap-1 rounded px-1.5 py-1 text-xs bg-custom-background-90 hover:bg-custom-background-80" onClick={() => setGptAssistantModal((prevData) => !prevData)} - tabIndex={getTabIndex("ai_assistant")} + tabIndex={getIndex("ai_assistant")} > AI diff --git a/web/core/components/issues/issue-modal/components/parent-tag.tsx b/web/core/components/issues/issue-modal/components/parent-tag.tsx index 74772b08c..1ef97ba3b 100644 --- a/web/core/components/issues/issue-modal/components/parent-tag.tsx +++ b/web/core/components/issues/issue-modal/components/parent-tag.tsx @@ -6,8 +6,12 @@ import { Control, Controller } from "react-hook-form"; import { X } from "lucide-react"; // types import { ISearchIssueResponse, TIssue } from "@plane/types"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; // helpers -import { getTabIndex } from "@/helpers/issue-modal.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; +// hooks +import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { IssueIdentifier } from "@/plane-web/components/issues"; @@ -20,6 +24,10 @@ type TIssueParentTagProps = { export const IssueParentTag: React.FC = observer((props) => { const { control, selectedParentIssue, handleFormChange, setSelectedParentIssue } = props; + // store hooks + const { isMobile } = usePlatformOS(); + + const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile); return ( = observer((props) = handleFormChange(); setSelectedParentIssue(null); }} - tabIndex={getTabIndex("remove_parent")} + tabIndex={getIndex("remove_parent")} > diff --git a/web/core/components/issues/issue-modal/components/project-select.tsx b/web/core/components/issues/issue-modal/components/project-select.tsx index ada1e012b..d2e4ca745 100644 --- a/web/core/components/issues/issue-modal/components/project-select.tsx +++ b/web/core/components/issues/issue-modal/components/project-select.tsx @@ -7,11 +7,14 @@ import { Control, Controller } from "react-hook-form"; import { TIssue } from "@plane/types"; // components import { ProjectDropdown } from "@/components/dropdowns"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; // helpers -import { getTabIndex } from "@/helpers/issue-modal.helper"; import { shouldRenderProject } from "@/helpers/project.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; // store hooks import { useUser } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; type TIssueProjectSelectProps = { control: Control; @@ -23,6 +26,9 @@ export const IssueProjectSelect: React.FC = observer(( const { control, disabled = false, handleFormChange } = props; // store hooks const { projectsWithCreatePermissions } = useUser(); + const { isMobile } = usePlatformOS(); + + const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile); return ( = observer(( }} buttonVariant="border-with-text" renderCondition={(project) => shouldRenderProject(project)} - tabIndex={getTabIndex("project_id")} + tabIndex={getIndex("project_id")} disabled={disabled} /> diff --git a/web/core/components/issues/issue-modal/components/title-input.tsx b/web/core/components/issues/issue-modal/components/title-input.tsx index c1986f64e..2499289e1 100644 --- a/web/core/components/issues/issue-modal/components/title-input.tsx +++ b/web/core/components/issues/issue-modal/components/title-input.tsx @@ -7,8 +7,12 @@ import { Control, Controller, FieldErrors } from "react-hook-form"; import { TIssue } from "@plane/types"; // ui import { Input } from "@plane/ui"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; // helpers -import { getTabIndex } from "@/helpers/issue-modal.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; +// hooks +import { usePlatformOS } from "@/hooks/use-platform-os"; type TIssueTitleInputProps = { control: Control; @@ -19,6 +23,11 @@ type TIssueTitleInputProps = { export const IssueTitleInput: React.FC = observer((props) => { const { control, issueTitleRef, errors, handleFormChange } = props; + // store hooks + const { isMobile } = usePlatformOS(); + + const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile); + const validateWhitespace = (value: string) => { if (value.trim() === "") { return "Title is required"; @@ -52,7 +61,7 @@ export const IssueTitleInput: React.FC = observer((props) hasError={Boolean(errors.name)} placeholder="Title" className="w-full text-base" - tabIndex={getTabIndex("name")} + tabIndex={getIndex("name")} autoFocus /> )} diff --git a/web/core/components/issues/issue-modal/form.tsx b/web/core/components/issues/issue-modal/form.tsx index b060f271a..d18538b7f 100644 --- a/web/core/components/issues/issue-modal/form.tsx +++ b/web/core/components/issues/issue-modal/form.tsx @@ -19,13 +19,15 @@ import { IssueTitleInput, } from "@/components/issues/issue-modal/components"; import { CreateLabelModal } from "@/components/labels"; +import { ETabIndices } from "@/constants/tab-indices"; // helpers import { cn } from "@/helpers/common.helper"; -import { getTabIndex } from "@/helpers/issue-modal.helper"; import { getChangedIssuefields } from "@/helpers/issue.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; // hooks import { useIssueModal } from "@/hooks/context/use-issue-modal"; import { useIssueDetail, useProject, useProjectState } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties"; // plane web components import { IssueAdditionalProperties, IssueTypeSelect } from "@/plane-web/components/issues/issue-modal"; @@ -88,6 +90,7 @@ export const IssueFormRoot: FC = observer((props) => { const { getProjectById } = useProject(); const { getIssueTypeIdOnProjectChange, getActiveAdditionalPropertiesLength, handlePropertyValuesValidation } = useIssueModal(); + const { isMobile } = usePlatformOS(); const { issue: { getIssueById }, @@ -116,6 +119,8 @@ export const IssueFormRoot: FC = observer((props) => { watch: watch, }); + const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile); + //reset few fields on projectId change useEffect(() => { if (isDirty) { @@ -371,7 +376,7 @@ export const IssueFormRoot: FC = observer((props) => { onKeyDown={(e) => { if (e.key === "Enter") onCreateMoreToggleChange(!isCreateMoreToggleEnabled); }} - tabIndex={getTabIndex("create_more")} + tabIndex={getIndex("create_more")} role="button" > {}} size="sm" /> @@ -393,7 +398,7 @@ export const IssueFormRoot: FC = observer((props) => { }); } }} - tabIndex={getTabIndex("discard_button")} + tabIndex={getIndex("discard_button")} > Discard @@ -405,7 +410,7 @@ export const IssueFormRoot: FC = observer((props) => { size="sm" loading={isSubmitting} onClick={handleSubmit((data) => handleFormSubmit({ ...data, is_draft: false }))} - tabIndex={getTabIndex("draft_button")} + tabIndex={getIndex("draft_button")} > {isSubmitting ? "Moving" : "Move from draft"} @@ -415,7 +420,7 @@ export const IssueFormRoot: FC = observer((props) => { size="sm" loading={isSubmitting} onClick={handleSubmit((data) => handleFormSubmit(data, true))} - tabIndex={getTabIndex("draft_button")} + tabIndex={getIndex("draft_button")} > {isSubmitting ? "Saving" : "Save as draft"} @@ -428,7 +433,7 @@ export const IssueFormRoot: FC = observer((props) => { size="sm" ref={submitBtnRef} loading={isSubmitting} - tabIndex={isDraft ? getTabIndex("submit_button") : getTabIndex("draft_button")} + tabIndex={isDraft ? getIndex("submit_button") : getIndex("draft_button")} > {data?.id ? (isSubmitting ? "Updating" : "Update") : isSubmitting ? "Creating" : "Create"} diff --git a/web/core/components/issues/parent-issues-list-modal.tsx b/web/core/components/issues/parent-issues-list-modal.tsx index 3cff11cc7..6208503e2 100644 --- a/web/core/components/issues/parent-issues-list-modal.tsx +++ b/web/core/components/issues/parent-issues-list-modal.tsx @@ -19,6 +19,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; import { IssueIdentifier } from "@/plane-web/components/issues"; // services import { ProjectService } from "@/services/project"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; type Props = { isOpen: boolean; @@ -50,6 +51,8 @@ export const ParentIssuesListModal: React.FC = ({ const { workspaceSlug } = useParams(); + const { baseTabIndex } = getTabIndex(undefined, isMobile); + const handleClose = () => { onClose(); setSearchTerm(""); @@ -121,6 +124,7 @@ export const ParentIssuesListModal: React.FC = ({ value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} displayValue={() => ""} + tabIndex={baseTabIndex} />
diff --git a/web/core/components/issues/select/label.tsx b/web/core/components/issues/select/label.tsx index 6b8fd4011..ec2905770 100644 --- a/web/core/components/issues/select/label.tsx +++ b/web/core/components/issues/select/label.tsx @@ -12,6 +12,7 @@ import { cn } from "@/helpers/common.helper"; import { useLabel } from "@/hooks/store"; import { useDropdownKeyDown } from "@/hooks/use-dropdown-key-down"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; +import { usePlatformOS } from "@/hooks/use-platform-os"; type Props = { setIsOpen: React.Dispatch>; @@ -41,6 +42,7 @@ export const IssueLabelSelect: React.FC = observer((props) => { const { workspaceSlug } = useParams(); // store hooks const { getProjectLabels, fetchProjectLabels } = useLabel(); + const { isMobile } = usePlatformOS(); // states const [query, setQuery] = useState(""); const [referenceElement, setReferenceElement] = useState(null); @@ -91,10 +93,10 @@ export const IssueLabelSelect: React.FC = observer((props) => { useOutsideClickDetector(dropdownRef, handleClose); useEffect(() => { - if (isDropdownOpen && inputRef.current) { + if (isDropdownOpen && inputRef.current && !isMobile) { inputRef.current.focus(); } - }, [isDropdownOpen]); + }, [isDropdownOpen, isMobile]); return ( = observer((props) => { const { workspaceSlug } = useParams(); // store hooks const { createLabel } = useLabel(); + const { isMobile } = usePlatformOS(); // form info const { formState: { errors, isSubmitting }, @@ -48,6 +53,8 @@ export const CreateLabelModal: React.FC = observer((props) => { defaultValues, }); + const { getIndex } = getTabIndex(ETabIndices.CREATE_LABEL, isMobile); + /** * For setting focus on name input */ @@ -183,7 +190,7 @@ export const CreateLabelModal: React.FC = observer((props) => { value={value} onChange={onChange} ref={ref} - tabIndex={1} + tabIndex={getIndex("name")} hasError={Boolean(errors.name)} placeholder="Label title" className="w-full resize-none text-xl" diff --git a/web/core/components/modules/dropdowns/filters/root.tsx b/web/core/components/modules/dropdowns/filters/root.tsx index 9df762f25..61191626e 100644 --- a/web/core/components/modules/dropdowns/filters/root.tsx +++ b/web/core/components/modules/dropdowns/filters/root.tsx @@ -8,6 +8,7 @@ import { TModuleDisplayFilters, TModuleFilters } from "@plane/types"; import { TModuleStatus } from "@plane/ui"; import { FilterOption } from "@/components/issues"; import { FilterLead, FilterMembers, FilterStartDate, FilterStatus, FilterTargetDate } from "@/components/modules"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // types type Props = { @@ -30,6 +31,8 @@ export const ModuleFiltersSelection: React.FC = observer((props) => { } = props; // states const [filtersSearchQuery, setFiltersSearchQuery] = useState(""); + // store + const { isMobile } = usePlatformOS(); return (
@@ -42,7 +45,7 @@ export const ModuleFiltersSelection: React.FC = observer((props) => { placeholder="Search" value={filtersSearchQuery} onChange={(e) => setFiltersSearchQuery(e.target.value)} - autoFocus + autoFocus={!isMobile} /> {filtersSearchQuery !== "" && (
)} @@ -112,7 +118,7 @@ export const ModuleForm: React.FC = (props) => { hasError={Boolean(errors?.name)} placeholder="Title" className="w-full text-base" - tabIndex={1} + tabIndex={getIndex("name")} autoFocus /> )} @@ -132,7 +138,7 @@ export const ModuleForm: React.FC = (props) => { placeholder="Description" className="w-full text-base resize-none min-h-24" hasError={Boolean(errors?.description)} - tabIndex={2} + tabIndex={getIndex("description")} /> )} /> @@ -164,14 +170,14 @@ export const ModuleForm: React.FC = (props) => { hideIcon={{ to: true, }} - tabIndex={3} + tabIndex={getIndex("date_range")} /> )} /> )} />
- +
= (props) => { multiple={false} buttonVariant="border-with-text" placeholder="Lead" - tabIndex={5} + tabIndex={getIndex("lead")} />
)} @@ -203,7 +209,7 @@ export const ModuleForm: React.FC = (props) => { buttonVariant={value && value.length > 0 ? "transparent-without-text" : "border-with-text"} buttonClassName={value && value.length > 0 ? "hover:bg-transparent px-0" : ""} placeholder="Members" - tabIndex={6} + tabIndex={getIndex("member_ids")} /> )} @@ -212,10 +218,10 @@ export const ModuleForm: React.FC = (props) => {
- -
diff --git a/web/core/components/modules/modal.tsx b/web/core/components/modules/modal.tsx index 667e15da9..11df178b8 100644 --- a/web/core/components/modules/modal.tsx +++ b/web/core/components/modules/modal.tsx @@ -13,6 +13,7 @@ import { ModuleForm } from "@/components/modules"; import { MODULE_CREATED, MODULE_UPDATED } from "@/constants/event-tracker"; // hooks import { useEventTracker, useModule, useProject } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; type Props = { isOpen: boolean; @@ -38,6 +39,7 @@ export const CreateUpdateModuleModal: React.FC = observer((props) => { const { captureModuleEvent } = useEventTracker(); const { workspaceProjectIds } = useProject(); const { createModule, updateModuleDetails } = useModule(); + const { isMobile } = usePlatformOS(); const handleClose = () => { reset(defaultValues); @@ -149,6 +151,7 @@ export const CreateUpdateModuleModal: React.FC = observer((props) => { projectId={activeProject ?? ""} setActiveProject={setActiveProject} data={data} + isMobile={isMobile} /> ); diff --git a/web/core/components/pages/list/filters/root.tsx b/web/core/components/pages/list/filters/root.tsx index 9b2aaeb4b..7871b76cd 100644 --- a/web/core/components/pages/list/filters/root.tsx +++ b/web/core/components/pages/list/filters/root.tsx @@ -5,6 +5,7 @@ import { TPageFilterProps, TPageFilters } from "@plane/types"; // components import { FilterCreatedBy, FilterCreatedDate } from "@/components/common/filters"; import { FilterOption } from "@/components/issues"; +import { usePlatformOS } from "@/hooks/use-platform-os"; type Props = { filters: TPageFilters; @@ -16,6 +17,8 @@ export const PageFiltersSelection: React.FC = observer((props) => { const { filters, handleFiltersUpdate, memberIds } = props; // states const [filtersSearchQuery, setFiltersSearchQuery] = useState(""); + // store + const { isMobile } = usePlatformOS(); const handleFilters = (key: keyof TPageFilterProps, value: boolean | string | string[]) => { const newValues = filters.filters?.[key] ?? []; @@ -51,7 +54,7 @@ export const PageFiltersSelection: React.FC = observer((props) => { placeholder="Search" value={filtersSearchQuery} onChange={(e) => setFiltersSearchQuery(e.target.value)} - autoFocus + autoFocus={!isMobile} /> {filtersSearchQuery !== "" && ( diff --git a/web/core/components/project/create/common-attributes.tsx b/web/core/components/project/create/common-attributes.tsx index 127d1890c..f753d0ae7 100644 --- a/web/core/components/project/create/common-attributes.tsx +++ b/web/core/components/project/create/common-attributes.tsx @@ -2,8 +2,14 @@ import { ChangeEvent } from "react"; import { Controller, useFormContext, UseFormSetValue } from "react-hook-form"; import { Info } from "lucide-react"; import { cn } from "@plane/editor"; +// ui import { Input, TextArea, Tooltip } from "@plane/ui"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; +// helpers import { projectIdentifierSanitizer } from "@/helpers/project.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; +// plane-web types import { TProject } from "@/plane-web/types/projects"; type Props = { @@ -19,6 +25,8 @@ const ProjectCommonAttributes: React.FC = (props) => { control, } = useFormContext(); + const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile); + const handleNameChange = (onChange: (...event: any[]) => void) => (e: ChangeEvent) => { if (!isChangeInIdentifierRequired) { onChange(e); @@ -58,7 +66,7 @@ const ProjectCommonAttributes: React.FC = (props) => { hasError={Boolean(errors.name)} placeholder="Project name" className="w-full focus:border-blue-400" - tabIndex={1} + tabIndex={getIndex("name")} /> )} /> @@ -94,7 +102,7 @@ const ProjectCommonAttributes: React.FC = (props) => { className={cn("w-full text-xs focus:border-blue-400 pr-7", { uppercase: value, })} - tabIndex={2} + tabIndex={getIndex("identifier")} /> )} /> @@ -121,7 +129,7 @@ const ProjectCommonAttributes: React.FC = (props) => { onChange={onChange} className="!h-24 text-sm focus:border-blue-400" hasError={Boolean(errors?.description)} - tabIndex={3} + tabIndex={getIndex("description")} /> )} /> diff --git a/web/core/components/project/create/header.tsx b/web/core/components/project/create/header.tsx index acea8de85..6d96c6b7e 100644 --- a/web/core/components/project/create/header.tsx +++ b/web/core/components/project/create/header.tsx @@ -2,18 +2,26 @@ import { useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { X } from "lucide-react"; import { IProject } from "@plane/types"; +// ui import { CustomEmojiIconPicker, EmojiIconPickerTypes, Logo } from "@plane/ui"; +// components import { ImagePickerPopover } from "@/components/core"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; +// helpers import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper"; +import { getTabIndex } from "@/helpers/tab-indices.helper"; type Props = { handleClose: () => void; + isMobile?: boolean; }; const ProjectCreateHeader: React.FC = (props) => { - const { handleClose } = props; + const { handleClose, isMobile = false } = props; const { watch, control } = useFormContext(); const [isOpen, setIsOpen] = useState(false); + const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile); return (
@@ -26,7 +34,7 @@ const ProjectCreateHeader: React.FC = (props) => { )}
-
@@ -35,7 +43,13 @@ const ProjectCreateHeader: React.FC = (props) => { name="cover_image" control={control} render={({ field: { value, onChange } }) => ( - + )} />
diff --git a/web/core/components/project/create/project-create-buttons.tsx b/web/core/components/project/create/project-create-buttons.tsx index 7eac09583..2fa0706e0 100644 --- a/web/core/components/project/create/project-create-buttons.tsx +++ b/web/core/components/project/create/project-create-buttons.tsx @@ -1,22 +1,31 @@ import { useFormContext } from "react-hook-form"; import { IProject } from "@plane/types"; +// ui import { Button } from "@plane/ui"; +// constants +import { ETabIndices } from "@/constants/tab-indices"; +// helpers +import { getTabIndex } from "@/helpers/tab-indices.helper"; type Props = { handleClose: () => void; + isMobile?: boolean; }; + const ProjectCreateButtons: React.FC = (props) => { - const { handleClose } = props; + const { handleClose, isMobile = false } = props; const { formState: { isSubmitting }, } = useFormContext(); + const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile); + return (
- -
diff --git a/web/core/components/project/dropdowns/filters/root.tsx b/web/core/components/project/dropdowns/filters/root.tsx index c7788ba96..161d25c02 100644 --- a/web/core/components/project/dropdowns/filters/root.tsx +++ b/web/core/components/project/dropdowns/filters/root.tsx @@ -5,6 +5,7 @@ import { TProjectDisplayFilters, TProjectFilters } from "@plane/types"; // components import { FilterOption } from "@/components/issues"; import { FilterAccess, FilterCreatedDate, FilterLead, FilterMembers } from "@/components/project"; +import { usePlatformOS } from "@/hooks/use-platform-os"; // types type Props = { @@ -19,6 +20,8 @@ export const ProjectFiltersSelection: React.FC = observer((props) => { const { displayFilters, filters, handleFiltersUpdate, handleDisplayFiltersUpdate, memberIds } = props; // states const [filtersSearchQuery, setFiltersSearchQuery] = useState(""); + // store + const { isMobile } = usePlatformOS(); return (
@@ -31,7 +34,7 @@ export const ProjectFiltersSelection: React.FC = observer((props) => { placeholder="Search" value={filtersSearchQuery} onChange={(e) => setFiltersSearchQuery(e.target.value)} - autoFocus + autoFocus={!isMobile} /> {filtersSearchQuery !== "" && (
- -
diff --git a/web/core/constants/issue-modal.ts b/web/core/constants/issue-modal.ts deleted file mode 100644 index 2e16176bd..000000000 --- a/web/core/constants/issue-modal.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const ISSUE_FORM_TAB_INDICES = [ - "name", - "description_html", - "feeling_lucky", - "ai_assistant", - "state_id", - "priority", - "assignee_ids", - "label_ids", - "start_date", - "target_date", - "cycle_id", - "module_ids", - "estimate_point", - "parent_id", - "create_more", - "discard_button", - "draft_button", - "submit_button", - "project_id", - "remove_parent", -]; diff --git a/web/core/constants/tab-indices.ts b/web/core/constants/tab-indices.ts new file mode 100644 index 000000000..58a12f417 --- /dev/null +++ b/web/core/constants/tab-indices.ts @@ -0,0 +1,94 @@ +export const ISSUE_FORM_TAB_INDICES = [ + "name", + "description_html", + "feeling_lucky", + "ai_assistant", + "state_id", + "priority", + "assignee_ids", + "label_ids", + "start_date", + "target_date", + "cycle_id", + "module_ids", + "estimate_point", + "parent_id", + "create_more", + "discard_button", + "draft_button", + "submit_button", + "project_id", + "remove_parent", +]; + +export const INTAKE_ISSUE_CREATE_FORM_TAB_INDICES = [ + "name", + "description_html", + "state_id", + "priority", + "assignee_ids", + "label_ids", + "start_date", + "target_date", + "cycle_id", + "module_ids", + "estimate_point", + "parent_id", + "create_more", + "discard_button", + "submit_button", +]; + +export const CREATE_LABEL_TAB_INDICES = ["name", "color", "cancel", "submit"]; + +export const PROJECT_CREATE_TAB_INDICES = [ + "name", + "identifier", + "description", + "network", + "lead", + "cancel", + "submit", + "close", + "cover_image", + "logo_props", +]; + +export const PROJECT_CYCLE_TAB_INDICES = ["name", "description", "date_range", "cancel", "submit", "project_id"]; + +export const PROJECT_MODULE_TAB_INDICES = [ + "name", + "description", + "date_range", + "status", + "lead", + "member_ids", + "cancel", + "submit", +]; + +export const PROJECT_VIEW_TAB_INDICES = ["name", "description", "filters", "cancel", "submit"]; + +export const PROJECT_PAGE_TAB_INDICES = ["name", "public", "private", "cancel", "submit"]; + +export enum ETabIndices { + ISSUE_FORM = "issue-form", + INTAKE_ISSUE_FORM = "intake-issue-form", + CREATE_LABEL = "create-label", + PROJECT_CREATE = "project-create", + PROJECT_CYCLE = "project-cycle", + PROJECT_MODULE = "project-module", + PROJECT_VIEW = "project-view", + PROJECT_PAGE = "project-page", +} + +export const TAB_INDEX_MAP: Record = { + [ETabIndices.ISSUE_FORM]: ISSUE_FORM_TAB_INDICES, + [ETabIndices.INTAKE_ISSUE_FORM]: INTAKE_ISSUE_CREATE_FORM_TAB_INDICES, + [ETabIndices.CREATE_LABEL]: CREATE_LABEL_TAB_INDICES, + [ETabIndices.PROJECT_CREATE]: PROJECT_CREATE_TAB_INDICES, + [ETabIndices.PROJECT_CYCLE]: PROJECT_CYCLE_TAB_INDICES, + [ETabIndices.PROJECT_MODULE]: PROJECT_MODULE_TAB_INDICES, + [ETabIndices.PROJECT_VIEW]: PROJECT_VIEW_TAB_INDICES, + [ETabIndices.PROJECT_PAGE]: PROJECT_PAGE_TAB_INDICES, +}; diff --git a/web/core/hooks/use-dropdown.ts b/web/core/hooks/use-dropdown.ts index 93be06824..fa851a492 100644 --- a/web/core/hooks/use-dropdown.ts +++ b/web/core/hooks/use-dropdown.ts @@ -2,6 +2,7 @@ import { useEffect } from "react"; // hooks import { useDropdownKeyDown } from "@/hooks/use-dropdown-key-down"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; +import { usePlatformOS } from "./use-platform-os"; type TArguments = { dropdownRef: React.RefObject; @@ -17,6 +18,8 @@ type TArguments = { export const useDropdown = (args: TArguments) => { const { dropdownRef, inputRef, isOpen, onClose, onOpen, query, setIsOpen, setQuery } = args; + const { isMobile } = usePlatformOS(); + /** * @description clear the search input when the user presses the escape key, if the search input is not empty * @param {React.KeyboardEvent} e @@ -62,10 +65,10 @@ export const useDropdown = (args: TArguments) => { // focus the search input when the dropdown is open useEffect(() => { - if (isOpen && inputRef?.current) { + if (isOpen && inputRef?.current && !isMobile) { inputRef.current.focus(); } - }, [inputRef, isOpen]); + }, [inputRef, isOpen, isMobile]); return { handleClose, diff --git a/web/helpers/issue-modal.helper.ts b/web/helpers/issue-modal.helper.ts deleted file mode 100644 index 8d0f8e7b3..000000000 --- a/web/helpers/issue-modal.helper.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ISSUE_FORM_TAB_INDICES } from "@/constants/issue-modal"; - -export const getTabIndex = (key: string) => ISSUE_FORM_TAB_INDICES.findIndex((tabIndex) => tabIndex === key) + 1; diff --git a/web/helpers/tab-indices.helper.ts b/web/helpers/tab-indices.helper.ts new file mode 100644 index 000000000..6b376e7e2 --- /dev/null +++ b/web/helpers/tab-indices.helper.ts @@ -0,0 +1,10 @@ +import { ETabIndices, TAB_INDEX_MAP } from "@/constants/tab-indices"; + +export const getTabIndex = (type?: ETabIndices, isMobile: boolean = false) => { + const getIndex = (key: string) => + isMobile ? undefined : type && TAB_INDEX_MAP[type].findIndex((tabIndex) => tabIndex === key) + 1; + + const baseTabIndex = isMobile ? -1 : 1; + + return { getIndex, baseTabIndex }; +};