feat: init
This commit is contained in:
15
apps/web/core/components/stickies/modal/index.tsx
Normal file
15
apps/web/core/components/stickies/modal/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { EModalWidth, ModalCore } from "@plane/ui";
|
||||
import { Stickies } from "./stickies";
|
||||
|
||||
type TProps = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
};
|
||||
export const AllStickiesModal = (props: TProps) => {
|
||||
const { isOpen, handleClose } = props;
|
||||
return (
|
||||
<ModalCore isOpen={isOpen} handleClose={handleClose} width={EModalWidth.VXL}>
|
||||
<Stickies handleClose={handleClose} />
|
||||
</ModalCore>
|
||||
);
|
||||
};
|
||||
101
apps/web/core/components/stickies/modal/search.tsx
Normal file
101
apps/web/core/components/stickies/modal/search.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { debounce } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Search, X } from "lucide-react";
|
||||
// plane hooks
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// helpers
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { cn } from "@plane/utils";
|
||||
import { useSticky } from "@/hooks/use-stickies";
|
||||
|
||||
export const StickySearch: FC = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// hooks
|
||||
const { searchQuery, updateSearchQuery, fetchWorkspaceStickies } = useSticky();
|
||||
const { t } = useTranslation();
|
||||
// refs
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
// states
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
|
||||
// outside click detector hook
|
||||
useOutsideClickDetector(inputRef, () => {
|
||||
if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false);
|
||||
});
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Escape") {
|
||||
if (searchQuery && searchQuery.trim() !== "") {
|
||||
updateSearchQuery("");
|
||||
fetchStickies();
|
||||
} else setIsSearchOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchStickies = async () => {
|
||||
await fetchWorkspaceStickies(workspaceSlug.toString());
|
||||
};
|
||||
|
||||
const debouncedSearch = useCallback(
|
||||
debounce(async () => {
|
||||
await fetchStickies();
|
||||
}, 500),
|
||||
[fetchWorkspaceStickies]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-center mr-2 my-auto">
|
||||
{!isSearchOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="-mr-1 p-1 hover:bg-custom-background-80 rounded text-custom-text-400 grid place-items-center"
|
||||
onClick={() => {
|
||||
setIsSearchOpen(true);
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<Search className=" size-4 " />
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"ml-auto flex items-center justify-start gap-1 rounded-md border border-transparent bg-custom-background-100 text-custom-text-400 w-0 transition-[width] ease-linear overflow-hidden opacity-0",
|
||||
{
|
||||
"w-30 md:w-64 px-2.5 py-1.5 border-custom-border-200 opacity-100": isSearchOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="w-full max-w-[234px] border-none bg-transparent text-sm text-custom-text-100 placeholder:text-custom-text-400 focus:outline-none"
|
||||
placeholder={t("stickies.search_placeholder")}
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
updateSearchQuery(e.target.value);
|
||||
debouncedSearch();
|
||||
}}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
{isSearchOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center"
|
||||
onClick={() => {
|
||||
updateSearchQuery("");
|
||||
setIsSearchOpen(false);
|
||||
fetchStickies();
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
74
apps/web/core/components/stickies/modal/stickies.tsx
Normal file
74
apps/web/core/components/stickies/modal/stickies.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Plus, X } from "lucide-react";
|
||||
// plane ui
|
||||
import { RecentStickyIcon } from "@plane/propel/icons";
|
||||
// hooks
|
||||
import { useSticky } from "@/hooks/use-stickies";
|
||||
// components
|
||||
import { StickiesTruncated } from "../layout/stickies-truncated";
|
||||
import { useStickyOperations } from "../sticky/use-operations";
|
||||
import { StickySearch } from "./search";
|
||||
|
||||
type TProps = {
|
||||
handleClose?: () => void;
|
||||
};
|
||||
|
||||
export const Stickies = observer((props: TProps) => {
|
||||
const { handleClose } = props;
|
||||
// navigation
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { creatingSticky, toggleShowNewSticky } = useSticky();
|
||||
// sticky operations
|
||||
const { stickyOperations } = useStickyOperations({ workspaceSlug: workspaceSlug?.toString() });
|
||||
|
||||
return (
|
||||
<div className="p-6 pb-0 min-h-[620px]">
|
||||
{/* header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
{/* Title */}
|
||||
<div className="text-custom-text-200 flex items-center gap-2">
|
||||
<RecentStickyIcon className="size-5 rotate-90 flex-shrink-0" />
|
||||
<p className="text-xl font-medium">Your stickies</p>
|
||||
</div>
|
||||
{/* actions */}
|
||||
<div className="flex gap-2">
|
||||
<StickySearch />
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleShowNewSticky(true);
|
||||
stickyOperations.create();
|
||||
}}
|
||||
className="flex gap-1 text-sm font-medium text-custom-primary-100 my-auto"
|
||||
disabled={creatingSticky}
|
||||
>
|
||||
<Plus className="size-4 my-auto" /> <span>Add sticky</span>
|
||||
{creatingSticky && (
|
||||
<div className="flex items-center justify-center ml-2">
|
||||
<div
|
||||
className={`w-4 h-4 border-2 border-t-transparent rounded-full animate-spin border-custom-primary-100`}
|
||||
role="status"
|
||||
aria-label="loading"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
{handleClose && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClose}
|
||||
className="flex-shrink-0 grid place-items-center text-custom-text-300 hover:text-custom-text-100 hover:bg-custom-background-80 rounded p-1 transition-colors my-auto"
|
||||
>
|
||||
<X className="text-custom-text-400 size-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* content */}
|
||||
<div className="mb-4 max-h-[625px] overflow-scroll">
|
||||
<StickiesTruncated handleClose={handleClose} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user