[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
This commit is contained in:
parent
c84c37805c
commit
52f78a86af
47 changed files with 430 additions and 125 deletions
|
|
@ -14,10 +14,12 @@ interface IInputSearch {
|
|||
inputContainerClassName?: string;
|
||||
inputClassName?: string;
|
||||
inputPlaceholder?: string;
|
||||
isMobile: boolean;
|
||||
}
|
||||
|
||||
export const InputSearch: FC<IInputSearch> = (props) => {
|
||||
const { isOpen, query, updateQuery, inputIcon, inputContainerClassName, inputClassName, inputPlaceholder } = props;
|
||||
const { isOpen, query, updateQuery, inputIcon, inputContainerClassName, inputClassName, inputPlaceholder, isMobile } =
|
||||
props;
|
||||
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
|
|
@ -29,10 +31,10 @@ export const InputSearch: FC<IInputSearch> = (props) => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
if (isOpen && !isMobile) {
|
||||
inputRef.current && inputRef.current.focus();
|
||||
}
|
||||
}, [isOpen]);
|
||||
}, [isOpen, isMobile]);
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
|
|||
value,
|
||||
renderItem,
|
||||
loader,
|
||||
isMobile = false,
|
||||
} = props;
|
||||
return (
|
||||
<>
|
||||
|
|
@ -38,6 +39,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
|
|||
inputPlaceholder={inputPlaceholder}
|
||||
inputClassName={inputClassName}
|
||||
inputContainerClassName={inputContainerClassName}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
|
|
|
|||
1
packages/ui/src/dropdown/dropdown.d.ts
vendored
1
packages/ui/src/dropdown/dropdown.d.ts
vendored
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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> = (props) => {
|
||||
const { isMobile = false } = props;
|
||||
const { control } = useFormContext<IProject>();
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile);
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Controller
|
||||
|
|
@ -15,7 +29,7 @@ const ProjectAttributes = () => {
|
|||
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === value);
|
||||
|
||||
return (
|
||||
<div className="flex-shrink-0 h-7" tabIndex={4}>
|
||||
<div className="flex-shrink-0 h-7" tabIndex={getIndex("network")}>
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
|
|
@ -35,7 +49,7 @@ const ProjectAttributes = () => {
|
|||
className="h-full"
|
||||
buttonClassName="h-full"
|
||||
noChevron
|
||||
tabIndex={4}
|
||||
tabIndex={getIndex("network")}
|
||||
>
|
||||
{NETWORK_CHOICES.map((network) => (
|
||||
<CustomSelect.Option key={network.key} value={network.key}>
|
||||
|
|
@ -59,7 +73,7 @@ const ProjectAttributes = () => {
|
|||
render={({ field: { value, onChange } }) => {
|
||||
if (value === undefined || value === null || typeof value === "string")
|
||||
return (
|
||||
<div className="flex-shrink-0 h-7" tabIndex={5}>
|
||||
<div className="flex-shrink-0 h-7" tabIndex={getIndex("lead")}>
|
||||
<MemberDropdown
|
||||
value={value}
|
||||
onChange={(lead) => onChange(lead === value ? null : lead)}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
|
|||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<ProjectCreateHeader handleClose={handleClose} />
|
||||
<ProjectCreateHeader handleClose={handleClose} isMobile={isMobile} />
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="px-3">
|
||||
<div className="mt-9 space-y-6 pb-5">
|
||||
|
|
@ -130,7 +130,7 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
|
|||
isChangeInIdentifierRequired={isChangeInIdentifierRequired}
|
||||
setIsChangeInIdentifierRequired={setIsChangeInIdentifierRequired}
|
||||
/>
|
||||
<ProjectAttributes />
|
||||
<ProjectAttributes isMobile={isMobile} />
|
||||
</div>
|
||||
<ProjectCreateButtons handleClose={handleClose} />
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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> = (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> = (props) => {
|
|||
placeholder="Type to search..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
tabIndex={baseTabIndex}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Props> = observer((props) => {
|
|||
const { filters, handleFiltersUpdate, isArchived = false } = props;
|
||||
// states
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||
|
|
@ -28,7 +31,7 @@ export const CycleFiltersSelection: React.FC<Props> = observer((props) => {
|
|||
placeholder="Search"
|
||||
value={filtersSearchQuery}
|
||||
onChange={(e) => setFiltersSearchQuery(e.target.value)}
|
||||
autoFocus
|
||||
autoFocus={!isMobile}
|
||||
/>
|
||||
{filtersSearchQuery !== "" && (
|
||||
<button type="button" className="grid place-items-center" onClick={() => setFiltersSearchQuery("")}>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ import { ICycle } from "@plane/types";
|
|||
import { Button, Input, TextArea } from "@plane/ui";
|
||||
// components
|
||||
import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns";
|
||||
// constants
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { shouldRenderProject } from "@/helpers/project.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
|
||||
type Props = {
|
||||
handleFormSubmit: (values: Partial<ICycle>, dirtyFields: any) => Promise<void>;
|
||||
|
|
@ -19,6 +22,7 @@ type Props = {
|
|||
projectId: string;
|
||||
setActiveProject: (projectId: string) => void;
|
||||
data?: ICycle | null;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
const defaultValues: Partial<ICycle> = {
|
||||
|
|
@ -29,7 +33,7 @@ const defaultValues: Partial<ICycle> = {
|
|||
};
|
||||
|
||||
export const CycleForm: React.FC<Props> = (props) => {
|
||||
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data } = props;
|
||||
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data, isMobile = false } = props;
|
||||
// form data
|
||||
const {
|
||||
formState: { errors, isSubmitting, dirtyFields },
|
||||
|
|
@ -46,6 +50,8 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||
},
|
||||
});
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_CYCLE, isMobile);
|
||||
|
||||
useEffect(() => {
|
||||
reset({
|
||||
...defaultValues,
|
||||
|
|
@ -71,7 +77,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||
}}
|
||||
buttonVariant="border-with-text"
|
||||
renderCondition={(project) => shouldRenderProject(project)}
|
||||
tabIndex={7}
|
||||
tabIndex={getIndex("cover_image")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -101,7 +107,7 @@ export const CycleForm: React.FC<Props> = (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> = (props) => {
|
|||
hasError={Boolean(errors?.description)}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
tabIndex={2}
|
||||
tabIndex={getIndex("description")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -153,7 +159,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||
hideIcon={{
|
||||
to: true,
|
||||
}}
|
||||
tabIndex={3}
|
||||
tabIndex={getIndex("date_range")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -163,10 +169,10 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={4}>
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={getIndex("cancel")}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={5}>
|
||||
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={getIndex("submit")}>
|
||||
{data ? (isSubmitting ? "Updating" : "Update Cycle") : isSubmitting ? "Creating" : "Create Cycle"}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<CycleModalProps> = (props) => {
|
|||
const { captureCycleEvent } = useEventTracker();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { createCycle, updateCycleDetails } = useCycle();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const { setValue: setCycleTab } = useLocalStorage<TCycleTabOptions>("cycle_tab", "active");
|
||||
|
||||
|
|
@ -186,6 +188,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||
projectId={activeProject ?? ""}
|
||||
setActiveProject={setActiveProject}
|
||||
data={data}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
</ModalCore>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<CycleOptionsProps> = 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, {
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -185,6 +185,8 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
|
|||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
// popper-js refs
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
// store hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const { getModuleNameById } = useModule();
|
||||
|
||||
|
|
@ -209,10 +211,10 @@ export const ModuleDropdown: React.FC<Props> = 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 = (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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 !== "" && (
|
||||
<button type="button" className="grid place-items-center" onClick={() => setFiltersSearchQuery("")}>
|
||||
|
|
|
|||
|
|
@ -16,12 +16,15 @@ import {
|
|||
} from "@/components/inbox/modals/create-edit-modal";
|
||||
// constants
|
||||
import { ISSUE_CREATED } from "@/constants/event-tracker";
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useProjectInbox, useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import useKeypress from "@/hooks/use-keypress";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
type TInboxIssueCreateRoot = {
|
||||
workspaceSlug: string;
|
||||
|
|
@ -53,6 +56,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
const { createInboxIssue } = useProjectInbox();
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
||||
const { isMobile } = usePlatformOS();
|
||||
// states
|
||||
const [createMore, setCreateMore] = useState<boolean>(false);
|
||||
const [formSubmitting, setFormSubmitting] = useState(false);
|
||||
|
|
@ -67,6 +71,8 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
[formData]
|
||||
);
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.INTAKE_ISSUE_FORM, isMobile);
|
||||
|
||||
const handleEscKeyDown = (event: KeyboardEvent) => {
|
||||
if (descriptionEditorRef.current?.isEditorReadyToDiscard()) {
|
||||
handleModalClose();
|
||||
|
|
@ -180,6 +186,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
className="inline-flex items-center gap-1.5 cursor-pointer"
|
||||
onClick={() => setCreateMore((prevData) => !prevData)}
|
||||
role="button"
|
||||
tabIndex={getIndex("create_more")}
|
||||
>
|
||||
<ToggleSwitch value={createMore} onChange={() => {}} size="sm" />
|
||||
<span className="text-xs">Create more</span>
|
||||
|
|
@ -200,6 +207,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
});
|
||||
}
|
||||
}}
|
||||
tabIndex={getIndex("discard_button")}
|
||||
>
|
||||
Discard
|
||||
</Button>
|
||||
|
|
@ -210,6 +218,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
type="submit"
|
||||
loading={formSubmitting}
|
||||
disabled={isTitleLengthMoreThan255Character}
|
||||
tabIndex={getIndex("submit_button")}
|
||||
>
|
||||
{formSubmitting ? "Creating" : "Create Issue"}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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<TInboxIssueDescription> = 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<TInboxIssueDescription> = observer((props
|
|||
placeholder={getDescriptionPlaceholder}
|
||||
containerClassName={containerClassName}
|
||||
onEnterKeyPress={onEnterKeyPress}
|
||||
tabIndex={getIndex("description_html")}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<TInboxIssueProperties> = 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<ISearchIssueResponse | undefined>(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<TInboxIssueProperties> = observer((props)
|
|||
onChange={(stateId) => handleData("state_id", stateId)}
|
||||
projectId={projectId}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getIndex("state_id")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -63,6 +70,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
value={data?.priority}
|
||||
onChange={(priority) => handleData("priority", priority)}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getIndex("priority")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -76,6 +84,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
buttonClassName={(data?.assignee_ids || [])?.length > 0 ? "hover:bg-transparent" : ""}
|
||||
placeholder="Assignees"
|
||||
multiple
|
||||
tabIndex={getIndex("assignee_ids")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -87,6 +96,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
value={data?.label_ids || []}
|
||||
onChange={(labelIds) => handleData("label_ids", labelIds)}
|
||||
projectId={projectId}
|
||||
tabIndex={getIndex("label_ids")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -99,6 +109,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
buttonVariant="border-with-text"
|
||||
minDate={minDate ?? undefined}
|
||||
placeholder="Start date"
|
||||
tabIndex={getIndex("start_date")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -111,6 +122,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
buttonVariant="border-with-text"
|
||||
minDate={minDate ?? undefined}
|
||||
placeholder="Due date"
|
||||
tabIndex={getIndex("target_date")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -123,6 +135,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
projectId={projectId}
|
||||
placeholder="Cycle"
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getIndex("cycle_id")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -138,6 +151,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
buttonVariant="border-with-text"
|
||||
multiple
|
||||
showCount
|
||||
tabIndex={getIndex("module_ids")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -151,6 +165,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
projectId={projectId}
|
||||
buttonVariant="border-with-text"
|
||||
placeholder="Estimate"
|
||||
tabIndex={getIndex("estimate_point")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -174,6 +189,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
|||
</button>
|
||||
}
|
||||
placement="bottom-start"
|
||||
tabIndex={getIndex("parent_id")}
|
||||
>
|
||||
<>
|
||||
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueModalOpen(true)}>
|
||||
|
|
|
|||
|
|
@ -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<TIssue>;
|
||||
|
|
@ -13,7 +19,10 @@ type TInboxIssueTitle = {
|
|||
|
||||
export const InboxIssueTitle: FC<TInboxIssueTitle> = observer((props) => {
|
||||
const { data, handleData, isTitleLengthMoreThan255Character } = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.INTAKE_ISSUE_FORM, isMobile);
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
|
|
@ -24,6 +33,7 @@ export const InboxIssueTitle: FC<TInboxIssueTitle> = observer((props) => {
|
|||
onChange={(e) => handleData("name", e.target.value)}
|
||||
placeholder="Title"
|
||||
className="w-full text-base"
|
||||
tabIndex={getIndex("name")}
|
||||
required
|
||||
/>
|
||||
{isTitleLengthMoreThan255Character && (
|
||||
|
|
|
|||
|
|
@ -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<IIssueLabelSelect> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, values, onSelect } = props;
|
||||
// store hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { fetchProjectLabels, getProjectLabels } = useLabel();
|
||||
// states
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
|
|
@ -27,6 +31,8 @@ export const IssueLabelSelect: React.FC<IIssueLabelSelect> = 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<IIssueLabelSelect> = observer((props) =>
|
|||
placeholder="Search"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
onKeyDown={searchInputKeyDown}
|
||||
tabIndex={baseTabIndex}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<Props> = 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<Props> = observer((props) => {
|
|||
placeholder="Search"
|
||||
value={filtersSearchQuery}
|
||||
onChange={(e) => setFiltersSearchQuery(e.target.value)}
|
||||
autoFocus
|
||||
autoFocus={!isMobile}
|
||||
/>
|
||||
{filtersSearchQuery !== "" && (
|
||||
<button type="button" className="grid place-items-center" onClick={() => setFiltersSearchQuery("")}>
|
||||
|
|
|
|||
|
|
@ -109,10 +109,10 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && inputRef.current) {
|
||||
if (isOpen && inputRef.current && !isMobile) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [isOpen]);
|
||||
}, [isOpen, isMobile]);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
|
|
|
|||
|
|
@ -20,11 +20,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 { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { getTabIndex } from "@/helpers/issue-modal.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// hooks
|
||||
import { useProjectEstimates, useProject } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
|
||||
|
|
@ -63,9 +66,12 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
// store hooks
|
||||
const { areEstimateEnabledByProjectId } = useProjectEstimates();
|
||||
const { getProjectById } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
// derived values
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile);
|
||||
|
||||
const minDate = getDate(startDate);
|
||||
minDate?.setDate(minDate.getDate());
|
||||
|
||||
|
|
@ -87,7 +93,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
}}
|
||||
projectId={projectId ?? undefined}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getTabIndex("state_id")}
|
||||
tabIndex={getIndex("state_id")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -104,7 +110,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
handleFormChange();
|
||||
}}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getTabIndex("priority")}
|
||||
tabIndex={getIndex("priority")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -125,7 +131,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
buttonClassName={value?.length > 0 ? "hover:bg-transparent" : ""}
|
||||
placeholder="Assignees"
|
||||
multiple
|
||||
tabIndex={getTabIndex("assignee_ids")}
|
||||
tabIndex={getIndex("assignee_ids")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -143,7 +149,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
handleFormChange();
|
||||
}}
|
||||
projectId={projectId ?? undefined}
|
||||
tabIndex={getTabIndex("label_ids")}
|
||||
tabIndex={getIndex("label_ids")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -162,7 +168,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
buttonVariant="border-with-text"
|
||||
maxDate={maxDate ?? undefined}
|
||||
placeholder="Start date"
|
||||
tabIndex={getTabIndex("start_date")}
|
||||
tabIndex={getIndex("start_date")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -181,7 +187,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
buttonVariant="border-with-text"
|
||||
minDate={minDate ?? undefined}
|
||||
placeholder="Due date"
|
||||
tabIndex={getTabIndex("target_date")}
|
||||
tabIndex={getIndex("target_date")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -201,7 +207,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
placeholder="Cycle"
|
||||
value={value}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getTabIndex("cycle_id")}
|
||||
tabIndex={getIndex("cycle_id")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -222,7 +228,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
}}
|
||||
placeholder="Modules"
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getTabIndex("module_ids")}
|
||||
tabIndex={getIndex("module_ids")}
|
||||
multiple
|
||||
showCount
|
||||
/>
|
||||
|
|
@ -244,7 +250,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
}}
|
||||
projectId={projectId}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={getTabIndex("estimate_point")}
|
||||
tabIndex={getIndex("estimate_point")}
|
||||
placeholder="Estimate"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -270,7 +276,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
</button>
|
||||
}
|
||||
placement="bottom-start"
|
||||
tabIndex={getTabIndex("parent_id")}
|
||||
tabIndex={getIndex("parent_id")}
|
||||
>
|
||||
<>
|
||||
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}>
|
||||
|
|
|
|||
|
|
@ -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<TIssueDescriptionEditorProps> = 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<TIssueDescriptionEditorProps> = 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<TIssueDescriptionEditorProps> = 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<TIssueDescriptionEditorProps> = 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")}
|
||||
>
|
||||
<Sparkle className="h-4 w-4" />
|
||||
AI
|
||||
|
|
|
|||
|
|
@ -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<TIssueParentTagProps> = observer((props) => {
|
||||
const { control, selectedParentIssue, handleFormChange, setSelectedParentIssue } = props;
|
||||
// store hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
|
|
@ -54,7 +62,7 @@ export const IssueParentTag: React.FC<TIssueParentTagProps> = observer((props) =
|
|||
handleFormChange();
|
||||
setSelectedParentIssue(null);
|
||||
}}
|
||||
tabIndex={getTabIndex("remove_parent")}
|
||||
tabIndex={getIndex("remove_parent")}
|
||||
>
|
||||
<X className="h-3 w-3 cursor-pointer" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -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<TIssue>;
|
||||
|
|
@ -23,6 +26,9 @@ export const IssueProjectSelect: React.FC<TIssueProjectSelectProps> = observer((
|
|||
const { control, disabled = false, handleFormChange } = props;
|
||||
// store hooks
|
||||
const { projectsWithCreatePermissions } = useUser();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
|
|
@ -42,7 +48,7 @@ export const IssueProjectSelect: React.FC<TIssueProjectSelectProps> = observer((
|
|||
}}
|
||||
buttonVariant="border-with-text"
|
||||
renderCondition={(project) => shouldRenderProject(project)}
|
||||
tabIndex={getTabIndex("project_id")}
|
||||
tabIndex={getIndex("project_id")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<TIssue>;
|
||||
|
|
@ -19,6 +23,11 @@ type TIssueTitleInputProps = {
|
|||
|
||||
export const IssueTitleInput: React.FC<TIssueTitleInputProps> = 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<TIssueTitleInputProps> = observer((props)
|
|||
hasError={Boolean(errors.name)}
|
||||
placeholder="Title"
|
||||
className="w-full text-base"
|
||||
tabIndex={getTabIndex("name")}
|
||||
tabIndex={getIndex("name")}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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<IssueFormProps> = observer((props) => {
|
|||
const { getProjectById } = useProject();
|
||||
const { getIssueTypeIdOnProjectChange, getActiveAdditionalPropertiesLength, handlePropertyValuesValidation } =
|
||||
useIssueModal();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
|
|
@ -116,6 +119,8 @@ export const IssueFormRoot: FC<IssueFormProps> = 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<IssueFormProps> = observer((props) => {
|
|||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") onCreateMoreToggleChange(!isCreateMoreToggleEnabled);
|
||||
}}
|
||||
tabIndex={getTabIndex("create_more")}
|
||||
tabIndex={getIndex("create_more")}
|
||||
role="button"
|
||||
>
|
||||
<ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => {}} size="sm" />
|
||||
|
|
@ -393,7 +398,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||
});
|
||||
}
|
||||
}}
|
||||
tabIndex={getTabIndex("discard_button")}
|
||||
tabIndex={getIndex("discard_button")}
|
||||
>
|
||||
Discard
|
||||
</Button>
|
||||
|
|
@ -405,7 +410,7 @@ export const IssueFormRoot: FC<IssueFormProps> = 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"}
|
||||
</Button>
|
||||
|
|
@ -415,7 +420,7 @@ export const IssueFormRoot: FC<IssueFormProps> = 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"}
|
||||
</Button>
|
||||
|
|
@ -428,7 +433,7 @@ export const IssueFormRoot: FC<IssueFormProps> = 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"}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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<Props> = ({
|
|||
|
||||
const { workspaceSlug } = useParams();
|
||||
|
||||
const { baseTabIndex } = getTabIndex(undefined, isMobile);
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setSearchTerm("");
|
||||
|
|
@ -121,6 +124,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
displayValue={() => ""}
|
||||
tabIndex={baseTabIndex}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex p-2 sm:justify-end">
|
||||
|
|
|
|||
|
|
@ -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<React.SetStateAction<boolean>>;
|
||||
|
|
@ -41,6 +42,7 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
|
|||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { getProjectLabels, fetchProjectLabels } = useLabel();
|
||||
const { isMobile } = usePlatformOS();
|
||||
// states
|
||||
const [query, setQuery] = useState("");
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
|
|
@ -91,10 +93,10 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
|
|||
useOutsideClickDetector(dropdownRef, handleClose);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDropdownOpen && inputRef.current) {
|
||||
if (isDropdownOpen && inputRef.current && !isMobile) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [isDropdownOpen]);
|
||||
}, [isDropdownOpen, isMobile]);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
|
|
|
|||
|
|
@ -7,14 +7,18 @@ import { TwitterPicker } from "react-color";
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { Dialog, Popover, Transition } from "@headlessui/react";
|
||||
import type { IIssueLabel, IState } from "@plane/types";
|
||||
// hooks
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { LABEL_COLOR_OPTIONS, getRandomLabelColor } from "@/constants/label";
|
||||
import { useLabel } from "@/hooks/store";
|
||||
// ui
|
||||
// types
|
||||
import type { IIssueLabel, IState } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { LABEL_COLOR_OPTIONS, getRandomLabelColor } from "@/constants/label";
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// hooks
|
||||
import { useLabel } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
// types
|
||||
type Props = {
|
||||
|
|
@ -35,6 +39,7 @@ export const CreateLabelModal: React.FC<Props> = 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<Props> = 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<Props> = 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"
|
||||
|
|
|
|||
|
|
@ -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<Props> = observer((props) => {
|
|||
} = props;
|
||||
// states
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
// store
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||
|
|
@ -42,7 +45,7 @@ export const ModuleFiltersSelection: React.FC<Props> = observer((props) => {
|
|||
placeholder="Search"
|
||||
value={filtersSearchQuery}
|
||||
onChange={(e) => setFiltersSearchQuery(e.target.value)}
|
||||
autoFocus
|
||||
autoFocus={!isMobile}
|
||||
/>
|
||||
{filtersSearchQuery !== "" && (
|
||||
<button type="button" className="grid place-items-center" onClick={() => setFiltersSearchQuery("")}>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ import { Button, Input, TextArea } from "@plane/ui";
|
|||
// components
|
||||
import { DateRangeDropdown, ProjectDropdown, MemberDropdown } from "@/components/dropdowns";
|
||||
import { ModuleStatusSelect } from "@/components/modules";
|
||||
// constants
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { shouldRenderProject } from "@/helpers/project.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
|
|
@ -20,6 +23,7 @@ type Props = {
|
|||
projectId: string;
|
||||
setActiveProject: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
data?: IModule;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
const defaultValues: Partial<IModule> = {
|
||||
|
|
@ -31,7 +35,7 @@ const defaultValues: Partial<IModule> = {
|
|||
};
|
||||
|
||||
export const ModuleForm: React.FC<Props> = (props) => {
|
||||
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data } = props;
|
||||
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data, isMobile = false } = props;
|
||||
// form info
|
||||
const {
|
||||
formState: { errors, isSubmitting, dirtyFields },
|
||||
|
|
@ -49,6 +53,8 @@ export const ModuleForm: React.FC<Props> = (props) => {
|
|||
},
|
||||
});
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_MODULE, isMobile);
|
||||
|
||||
const handleCreateUpdateModule = async (formData: Partial<IModule>) => {
|
||||
await handleFormSubmit(formData, dirtyFields);
|
||||
|
||||
|
|
@ -82,7 +88,7 @@ export const ModuleForm: React.FC<Props> = (props) => {
|
|||
}}
|
||||
buttonVariant="border-with-text"
|
||||
renderCondition={(project) => shouldRenderProject(project)}
|
||||
tabIndex={10}
|
||||
tabIndex={getIndex("cover_image")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -112,7 +118,7 @@ export const ModuleForm: React.FC<Props> = (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> = (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> = (props) => {
|
|||
hideIcon={{
|
||||
to: true,
|
||||
}}
|
||||
tabIndex={3}
|
||||
tabIndex={getIndex("date_range")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="h-7">
|
||||
<ModuleStatusSelect control={control} error={errors.status} tabIndex={4} />
|
||||
<ModuleStatusSelect control={control} error={errors.status} tabIndex={getIndex("status")} />
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -185,7 +191,7 @@ export const ModuleForm: React.FC<Props> = (props) => {
|
|||
multiple={false}
|
||||
buttonVariant="border-with-text"
|
||||
placeholder="Lead"
|
||||
tabIndex={5}
|
||||
tabIndex={getIndex("lead")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -203,7 +209,7 @@ export const ModuleForm: React.FC<Props> = (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")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -212,10 +218,10 @@ export const ModuleForm: React.FC<Props> = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={7}>
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={getIndex("cancel")}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={8}>
|
||||
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={getIndex("submit")}>
|
||||
{status ? (isSubmitting ? "Updating" : "Update Module") : isSubmitting ? "Creating" : "Create Module"}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<Props> = 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<Props> = observer((props) => {
|
|||
projectId={activeProject ?? ""}
|
||||
setActiveProject={setActiveProject}
|
||||
data={data}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
</ModalCore>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<Props> = 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<Props> = observer((props) => {
|
|||
placeholder="Search"
|
||||
value={filtersSearchQuery}
|
||||
onChange={(e) => setFiltersSearchQuery(e.target.value)}
|
||||
autoFocus
|
||||
autoFocus={!isMobile}
|
||||
/>
|
||||
{filtersSearchQuery !== "" && (
|
||||
<button type="button" className="grid place-items-center" onClick={() => setFiltersSearchQuery("")}>
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ import { Logo } from "@/components/common";
|
|||
// constants
|
||||
import { AccessField } from "@/components/common/access-field";
|
||||
import { EPageAccess, PAGE_ACCESS_SPECIFIERS } from "@/constants/page";
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// hooks
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
|
|
@ -30,6 +32,8 @@ export const PageForm: React.FC<Props> = (props) => {
|
|||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_PAGE, isMobile);
|
||||
|
||||
const handlePageFormSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
|
|
@ -99,7 +103,7 @@ export const PageForm: React.FC<Props> = (props) => {
|
|||
onChange={(e) => handleFormData("name", e.target.value)}
|
||||
placeholder="Title"
|
||||
className="w-full resize-none text-base"
|
||||
tabIndex={1}
|
||||
tabIndex={getIndex("name")}
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
|
|
@ -122,7 +126,7 @@ export const PageForm: React.FC<Props> = (props) => {
|
|||
</h6>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleModalClose} tabIndex={4}>
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleModalClose} tabIndex={getIndex("cancel")}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
|
|
@ -131,7 +135,7 @@ export const PageForm: React.FC<Props> = (props) => {
|
|||
type="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={isTitleLengthMoreThan255Character}
|
||||
tabIndex={5}
|
||||
tabIndex={getIndex("submit")}
|
||||
>
|
||||
{isSubmitting ? "Creating" : "Create Page"}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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> = (props) => {
|
|||
control,
|
||||
} = useFormContext<TProject>();
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile);
|
||||
|
||||
const handleNameChange = (onChange: (...event: any[]) => void) => (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!isChangeInIdentifierRequired) {
|
||||
onChange(e);
|
||||
|
|
@ -58,7 +66,7 @@ const ProjectCommonAttributes: React.FC<Props> = (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> = (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> = (props) => {
|
|||
onChange={onChange}
|
||||
className="!h-24 text-sm focus:border-blue-400"
|
||||
hasError={Boolean(errors?.description)}
|
||||
tabIndex={3}
|
||||
tabIndex={getIndex("description")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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> = (props) => {
|
||||
const { handleClose } = props;
|
||||
const { handleClose, isMobile = false } = props;
|
||||
const { watch, control } = useFormContext<IProject>();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile);
|
||||
|
||||
return (
|
||||
<div className="group relative h-44 w-full rounded-lg bg-custom-background-80">
|
||||
|
|
@ -26,7 +34,7 @@ const ProjectCreateHeader: React.FC<Props> = (props) => {
|
|||
)}
|
||||
|
||||
<div className="absolute right-2 top-2 p-2">
|
||||
<button data-posthog="PROJECT_MODAL_CLOSE" type="button" onClick={handleClose} tabIndex={8}>
|
||||
<button data-posthog="PROJECT_MODAL_CLOSE" type="button" onClick={handleClose} tabIndex={getIndex("close")}>
|
||||
<X className="h-5 w-5 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -35,7 +43,13 @@ const ProjectCreateHeader: React.FC<Props> = (props) => {
|
|||
name="cover_image"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ImagePickerPopover label="Change Cover" onChange={onChange} control={control} value={value} tabIndex={9} />
|
||||
<ImagePickerPopover
|
||||
label="Change Cover"
|
||||
onChange={onChange}
|
||||
control={control}
|
||||
value={value}
|
||||
tabIndex={getIndex("cover_image")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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> = (props) => {
|
||||
const { handleClose } = props;
|
||||
const { handleClose, isMobile = false } = props;
|
||||
const {
|
||||
formState: { isSubmitting },
|
||||
} = useFormContext<IProject>();
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile);
|
||||
|
||||
return (
|
||||
<div className="flex justify-end gap-2 py-4 border-t border-custom-border-100">
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={6}>
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={getIndex("cancel")}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" type="submit" size="sm" loading={isSubmitting} tabIndex={7}>
|
||||
<Button variant="primary" type="submit" size="sm" loading={isSubmitting} tabIndex={getIndex("submit")}>
|
||||
{isSubmitting ? "Creating" : "Create project"}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<Props> = observer((props) => {
|
|||
const { displayFilters, filters, handleFiltersUpdate, handleDisplayFiltersUpdate, memberIds } = props;
|
||||
// states
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
// store
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||
|
|
@ -31,7 +34,7 @@ export const ProjectFiltersSelection: React.FC<Props> = observer((props) => {
|
|||
placeholder="Search"
|
||||
value={filtersSearchQuery}
|
||||
onChange={(e) => setFiltersSearchQuery(e.target.value)}
|
||||
autoFocus
|
||||
autoFocus={!isMobile}
|
||||
/>
|
||||
{filtersSearchQuery !== "" && (
|
||||
<button type="button" className="grid place-items-center" onClick={() => setFiltersSearchQuery("")}>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { FilterOption } from "@/components/issues";
|
|||
// constants
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
import { FilterByAccess } from "@/plane-web/components/views/filters/access-filter";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
type Props = {
|
||||
filters: TViewFilters;
|
||||
|
|
@ -19,6 +20,8 @@ export const ViewFiltersSelection: React.FC<Props> = observer((props) => {
|
|||
const { filters, handleFiltersUpdate, memberIds } = props;
|
||||
// states
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
// store
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
// handles filter update
|
||||
const handleFilters = (key: keyof TViewFilterProps, value: boolean | string | EViewAccess | string[]) => {
|
||||
|
|
@ -55,7 +58,7 @@ export const ViewFiltersSelection: React.FC<Props> = observer((props) => {
|
|||
placeholder="Search"
|
||||
value={filtersSearchQuery}
|
||||
onChange={(e) => setFiltersSearchQuery(e.target.value)}
|
||||
autoFocus
|
||||
autoFocus={!isMobile}
|
||||
/>
|
||||
{filtersSearchQuery !== "" && (
|
||||
<button type="button" className="grid place-items-center" onClick={() => setFiltersSearchQuery("")}>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,16 @@ import { Logo } from "@/components/common";
|
|||
import { AppliedFiltersList, DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
|
||||
// constants
|
||||
import { EIssueLayoutTypes, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
// helpers
|
||||
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
||||
import { getComputedDisplayFilters, getComputedDisplayProperties } from "@/helpers/issue.helper";
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
// hooks
|
||||
import { useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
import { AccessController } from "@/plane-web/components/views/access-controller";
|
||||
import { LayoutDropDown } from "../dropdowns/layout";
|
||||
|
||||
|
|
@ -48,6 +52,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
|||
const {
|
||||
project: { projectMemberIds },
|
||||
} = useMember();
|
||||
const { isMobile } = usePlatformOS();
|
||||
// form info
|
||||
const {
|
||||
control,
|
||||
|
|
@ -62,6 +67,8 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
|||
|
||||
const logoValue = watch("logo_props");
|
||||
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_VIEW, isMobile);
|
||||
|
||||
const selectedFilters: IIssueFilterOptions = {};
|
||||
Object.entries(watch("filters") ?? {}).forEach(([key, value]) => {
|
||||
if (!value) return;
|
||||
|
|
@ -194,7 +201,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
|||
hasError={Boolean(errors.name)}
|
||||
placeholder="Title"
|
||||
className="w-full text-base"
|
||||
tabIndex={1}
|
||||
tabIndex={getIndex("name")}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
|
|
@ -215,7 +222,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
|||
hasError={Boolean(errors?.description)}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
tabIndex={2}
|
||||
tabIndex={getIndex("descriptions")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -243,7 +250,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
|||
control={control}
|
||||
name="filters"
|
||||
render={({ field: { onChange, value: filters } }) => (
|
||||
<FiltersDropdown title="Filters" tabIndex={3}>
|
||||
<FiltersDropdown title="Filters" tabIndex={getIndex("filters")}>
|
||||
<FilterSelection
|
||||
filters={filters ?? {}}
|
||||
handleFiltersUpdate={(key, value) => {
|
||||
|
|
@ -322,10 +329,10 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={4}>
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={getIndex("cancel")}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" size="sm" type="submit" tabIndex={5} loading={isSubmitting}>
|
||||
<Button variant="primary" size="sm" type="submit" tabIndex={getIndex("submit")} loading={isSubmitting}>
|
||||
{data ? (isSubmitting ? "Updating" : "Update View") : isSubmitting ? "Creating" : "Create View"}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
];
|
||||
94
web/core/constants/tab-indices.ts
Normal file
94
web/core/constants/tab-indices.ts
Normal file
|
|
@ -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, string[]> = {
|
||||
[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,
|
||||
};
|
||||
|
|
@ -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<HTMLDivElement>;
|
||||
|
|
@ -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<HTMLInputElement>} 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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
10
web/helpers/tab-indices.helper.ts
Normal file
10
web/helpers/tab-indices.helper.ts
Normal file
|
|
@ -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 };
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue